Mirror and control an Android phone from your Mac, inside a realistic device frame.
Handset is a small native Rust app. It talks to the unmodified scrcpy server on the phone and replaces the desktop client, so it can draw things a plain window cannot: phone bezels on a transparent window, a toolbar, and framed screenshots and recordings with audio.
Status: early work in progress. It works on the author's Mac with one phone. Expect rough edges. It has been tested on macOS and on Windows 11. Linux builds and is checked in CI, but has not been run on a real desktop yet (see Linux). iPhone and iPad mirroring is experimental and view only (see iPhone and iPad).
Contents: Install · Connecting a phone · Features · iPhone and iPad · Options · Updates · Building from source · Troubleshooting · Roadmap · Contributing
On every platform you need:
- An Android phone with USB debugging enabled (audio needs Android 11 or later)
adb(Android platform-tools)ffmpegfor recording (optional)
Releases are on the releases page; check a download against
the release's SHA256SUMS if you like.
macOS 12 or later, on Apple Silicon or Intel. With Homebrew (installs the signed, notarised
app, the handset command, and the adb and ffmpeg it needs):
brew install --cask davidamunga/tap/handsetRecent Homebrew versions only load casks from third-party taps you have trusted. If it says the tap is
not trusted, run brew trust davidamunga/tap and install again.
Or download Handset-<version>-macos.zip from the releases page, unzip it and move Handset.app to
Applications. The zip is a universal build (Apple Silicon and Intel). Install adb yourself in that case
(brew install --cask android-platform-tools), and ffmpeg if you want recordings.
Windows 10 or 11. With Scoop (this repository is the bucket, and adb comes with it):
scoop bucket add handset https://github.com/davidamunga/handset
scoop install handset/handsetOr download Handset-<version>-windows.zip from the releases page, unzip it and run handset.exe.
The build is not code-signed yet, so SmartScreen warns on first run (More info, Run anyway), and a PC
with Smart App Control turned on blocks it. Install adb (winget install Google.PlatformTools) and
ffmpeg for recordings (winget install Gyan.FFmpeg); the app finds both without any setup.
adb and ffmpeg are found on PATH, in the Android SDK (%LOCALAPPDATA%\Android\Sdk\platform-tools), in
winget, scoop or Chocolatey shims, or next to handset.exe (also in a platform-tools folder there). Set ADB
or FFMPEG to override. Captures go to Pictures\handset and Videos\handset; settings live in
%USERPROFILE%\.config\handset. Only the Dock icon states are macOS-only.
It builds and passes its tests on Ubuntu 22.04 in CI, but nobody has run the window on a Linux desktop yet, so expect problems with the transparent, borderless frame on some compositors.
Arch, Omarchy, EndeavourOS and Manjaro: build the package from the repository's PKGBUILD. It builds the latest
release, installs handset, a launcher entry and the icon, and pulls in alsa-lib and android-tools:
sudo pacman -S --needed base-devel git
git clone https://github.com/davidamunga/handset
cd handset/app/packaging/linux
makepkg -siTo update, git pull in that folder and run makepkg -si again.
The packages are not on the AUR yet. Once they are, yay -S handset will build from source and
yay -S handset-bin will install the prebuilt binary from the release. Install ffmpeg, zenity and
wl-clipboard (or xclip on X11) for recording, prompts and clipboard sync.
Other distributions: each release has a tarball for x86_64 and one for aarch64
(Handset-<version>-linux-<arch>.tar.gz, not code-signed; SHA256SUMS-linux and its signature cover them). They need glibc 2.35 or newer (Ubuntu 22.04, Debian 12 and newer)
and ALSA (libasound2 or alsa-lib); install ffmpeg too if you want recordings. Unpack it and put handset on your PATH:
tar -xzf Handset-<version>-linux-x86_64.tar.gz
install -Dm755 Handset-<version>-linux-x86_64/handset ~/.local/bin/handsetFor a launcher entry, also copy the desktop entry and the icon from the same folder:
install -Dm644 Handset-<version>-linux-x86_64/handset.desktop ~/.local/share/applications/handset.desktop
install -Dm644 Handset-<version>-linux-x86_64/handset.png ~/.local/share/icons/hicolor/512x512/apps/handset.pngTo update, download the new tarball and do the same again.
Or install it from source with Cargo. You need Rust 1.91 or newer, cmake, nasm and the ALSA headers.
Debian and Ubuntu (install Rust with rustup: the Rust in Ubuntu 22.04 and Debian 12 is too old):
sudo apt install build-essential pkg-config cmake nasm libasound2-dev adb ffmpeg zenity
cargo install --git https://github.com/davidamunga/handset --locked handsetThat puts handset in ~/.cargo/bin (put that on your PATH). Run the same cargo install command with
--force to update. This does not add a launcher entry: copy app/packaging/linux/handset.desktop and app/packaging/icon/handset-icon-512.png into
~/.local/share/applications and ~/.local/share/icons/hicolor/512x512/apps/handset.png if you want one.
Run handset --doctor to check your setup, then handset --audio.
At run time, adb and ffmpeg are found on PATH, in ~/.local/bin or in ~/Android/Sdk/platform-tools.
Your user needs USB access to the phone (udev rules for Android, usually the plugdev group or the
android-udev-rules package). Captures go to handset inside your Pictures and Videos folders (~/Pictures and ~/Videos, or wherever
~/.config/user-dirs.dirs points them). The right-click menu
is drawn by Handset in its window (right-click, or the toolbar's … button), since there is no native one here;
About Handset shows the version in a toast.
Optional helpers (handset --doctor lists any that are missing and says what to install):
xdg-open: open links and captureswl-clipboardon Wayland orxclip/xselon X11: the clipboard and "copy capture"zenityorkdialog: error dialogs, when Handset is started without a terminal to print them in. The address and pairing-code fields are drawn in the window and need neither.
File dialogs go through the desktop portal (xdg-desktop-portal with a backend for your desktop), not GTK.
On a tiling compositor such as Hyprland, float the window or it is tiled to fit. The class is handset on Wayland
and X11, and Hyprland's rule syntax differs by version: windowrulev2 = float, class:^(handset)$ on older ones,
windowrule = float on, match:class ^(handset)$ on newer ones (check hyprctl version and the Hyprland wiki).
Which systems:
- Ubuntu 22.04 and newer, Debian 12 and newer, Fedora, and other glibc systems at least that new: the release
tarball needs glibc 2.35 (
scripts/package-linux.shrefuses to build one that needs more) and ALSA (libasound2). On older systems (Ubuntu 20.04, Debian 11) build from source. - Arch and its derivatives (Omarchy, EndeavourOS, Manjaro): besides
cargo install,app/packaging/linux/PKGBUILDbuilds a package from a release tag (makepkg -si; not on the AUR yet). - Alpine (musl): build from source with a dynamic link, because Handset loads the window and GPU libraries at
run time and a static musl binary cannot:
apk add build-base cmake nasm pkgconf alsa-lib-dev rustup, thenRUSTFLAGS='-C target-feature=-crt-static' cargo build --releaseinapp. No prebuilt file for musl. - NixOS: see
app/packaging/nix/package.nix(needs a nixpkgs with Rust 1.91 or newer). - Flatpak and Snap are not supported yet: both sandboxes make
adbover USB and mDNS harder, and they cannot be built or tested here (seedocs/adr/0032-support-older-glibc-musl-and-nix-and-leave-out-flatpak-and-snap.md).
With no phone connected, Handset opens on a connect screen inside the frame. It watches adb, says what it sees (nothing yet, a phone waiting for you to tap Allow, an offline connection) and starts mirroring by itself as soon as a phone is ready. While it looks for a phone, the eyes in the logo glance about.
- USB: the steps on the home screen update as you plug the phone in.
- Wi-Fi lists paired phones found on your network (tap one to connect), asks for an address if you want to type one, and, when a phone is plugged in, switches it to Wi-Fi so you can unplug the cable.
- Reconnect to ... appears on the home screen once you have used a phone over Wi-Fi, and the Wi-Fi view lists your recent phones, so a phone you used before is one click away.
- QR code shows a code to scan on the phone (Developer options, Wireless debugging, Pair device with QR code; Android 11 or later). Handset pairs with the phone that scans it and connects, with nothing to type.
- Run checks runs the same checks as
handset --doctorand shows what to fix (a missingadb, a phone that has not authorised this computer, a sleeping screen) right in the frame.
With a pairing code, from the command line: on Android 11 or later, open Developer options > Wireless debugging >
Pair device with pairing code, run handset --pair <ip:port> <code> with what it shows, then
handset --connect <ip:port> with the address on the main Wireless debugging screen. On any version,
handset --wifi does it from a USB connection in one step. The phone and the Mac must be on the same network.
A running session has a Phone submenu (right-click): Switch to Wi-Fi for a USB phone, Connect Another Phone, New Window (a second window for a second phone, or an iPhone or iPad beside an Android phone) and Disconnect. After Disconnect nothing restarts by itself; the connect screen offers the phone that is plugged in, or the Wi-Fi and QR options.
- Live video and device audio over adb (USB), decoded in software and drawn with wgpu
- Twenty-three device frames (punch-hole, notch, island, tablet, foldable, flip, rugged and more, see docs/frames.md), or your own PNG, on a transparent window, switchable while running
- Touch, scroll, keyboard and text input; landscape rotation
- Framed screenshots (transparent PNG) and recordings (MP4 with audio, or GIF) via ffmpeg
- A style editor for captures (see Styling captures)
- A Dock icon that shows what Handset is doing: grey eyes while waiting for a phone, amber while connecting, white while mirroring, red with a dot while recording
- A toolbar beside the phone and a native right-click menu, with an About Handset item that credits the author
- Clipboard text is shared with the phone: copy on the phone and paste on the Mac, or copy on the Mac, click the Handset window and paste on the phone
- A clickable "saved" toast, a recent captures menu and automatic clipboard copy
- A single binary (under 20 MB, including the style editor) with the scrcpy server embedded
- Right-click the phone, or use the toolbar beside it: Back, Home, Recents, Power, Volume, the notification shade, frames, Open App, screenshot, recording, recent captures, rotate, quit. Hover a toolbar button for its name. Open App lists the phone's apps (yours first, system apps in a submenu), fetched in the background a few seconds after the phone connects.
- Shortcuts (Cmd or Ctrl):
Sscreenshot,Estart or stop recording,Rrotate the phone.Escis Back,Homeis Home,Endis Recents. - Show Touches (right-click menu, or
--show-touches) draws a translucent dot where you touch the phone, fading when you let go. It is part of screenshots and recordings too, which makes demos easier to follow. - Tilt (right-click menu, or
--tilt yaw,pitch,rollin degrees, for example--tilt -25,10,0) turns the phone in 3D with perspective, and touch still lands where you point. Screenshots and recordings of the live window stay flat; the editor's Tilt sliders turn a saved screenshot.
- Drop a file on the window: an
.apkis installed, anything else is copied to the phone's Downloads folder. - Move the window by dragging the bezel. Resize by dragging a corner of the phone (it keeps the frame's
proportions), with Cmd or Ctrl and scroll, or
+,-and0(default size), also in the menu.
Screenshots are saved to ~/Pictures/handset, recordings to ~/Movies/handset (the folders on Windows and
Linux are under Install), and both are copied to the clipboard. Click the toast to show the file in
Finder.
Put a screenshot or recording on a gradient, wallpaper, colour or your own picture, with padding, shadow, rounded corners and an aspect ratio (1:1, 16:9, 4:5 ...); trim and play recordings with sound; keep your own presets. Your original is kept. Click EDIT on the toast after a capture is saved (or Recent Captures > Edit Style...) to open the editor, or turn on Recording > Save a styled copy of each capture. The guide is in docs/styling.md.
On macOS, Handset can also show an iPhone or iPad connected with a cable, with its sound. Plug it in while the connect screen is open and click Mirror iPhone (view only), or start it directly:
handset --ios --audioIt uses the same screen capture as QuickTime, so nothing is installed on the device and it does not need developer mode. Unlock the device and tap Trust if it asks. The first time, macOS asks to let Handset use the camera and microphone, because it treats the device's screen and sound as those. The frame, screenshots, recordings and styling work as they do for Android phones. iOS shows a clean status bar (9:41, full signal and battery) while it is captured.
For now it is:
- macOS only. Windows has no equivalent capture API.
- USB only. There is no Wi-Fi option. A plugged-in device is offered on the connect screen, never started by itself, so an iPhone that is only charging does not get in the way of an Android phone.
- View only. You cannot tap, type or press the device's buttons from Handset. The toolbar keeps only screenshot, record, next frame, menu and quit, the menu leaves out the phone's controls, and the frame list shows only the iPhone and iPad shapes. If you unplug the device, Handset saves any recording and goes back to the connect screen.
--ios <name> picks a device when several are connected. To open the app straight into this mode, put --ios --audio
in ~/.config/handset/args. Why it works this way: ADR 0025.
| Option | What it does |
|---|---|
--frame <name|path|none> |
Built-in frame, your own PNG (with a .frame file), or no frame |
--max-size <px> |
Limit the video size (default 1920) |
--audio |
Capture the phone's audio and play it here |
--mute |
With --audio, record the audio but do not play it |
--gain <dB>, --no-normalize |
Recorded audio is normalised to about -16 LUFS; adjust or turn off |
--no-clipboard |
Do not copy captures to the clipboard |
--no-clipboard-sync |
Do not share clipboard text with the phone (also in the right-click menu) |
--no-update-check |
Do not look for a newer release this time (also Check for Updates on Start in the right-click menu) |
--version |
Print the version and quit |
--record-for <seconds> |
Record from the first frame, then quit |
--record-format mp4|gif |
Recording format |
--record-size <px> |
Longest side of recordings (default 1280) |
--record-bg #rrggbb |
Recording background colour |
--style <look> |
The look used to style captures: a saved look's name (for example "Aurora 16:9") or a style such as "bg=gradient:dusk;padding=12;aspect=1:1" (see docs/styling.md) |
--style-captures |
Save a styled copy beside each new screenshot and recording (also in the Recording menu) |
--restyle <file> |
Style a saved PNG, MP4 or GIF with --style (or your last look), print the new path and quit |
--edit [file] |
Open the style editor on a capture (a file dialog opens if you give none) |
--connect <ip[:port]> |
Connect to a phone over Wi-Fi (adb connect, default port 5555) and mirror it |
--last |
Reconnect to the phone you last used over Wi-Fi (Handset remembers the last few in ~/.config/handset/recent) |
--pair <ip:port> <code> |
Pair with a phone using its Wireless debugging pairing code, then exit |
--verify-release <folder> |
Check downloaded release files against the signed SHA256SUMS and say whether they can be trusted |
--doctor |
Check adb, ffmpeg and each connected phone (authorised, online, Android version, screen on) and say what to fix; exits 1 when something blocks mirroring |
--wifi |
Switch a USB-connected phone to Wi-Fi, connect, and mirror (you can unplug the cable) |
--start-app <package> |
Open an app on the phone once it is mirrored, e.g. --start-app com.android.chrome (a leading ? searches app names) |
--ios [name] |
Mirror an iPhone or iPad connected by USB instead of an Android phone (macOS, experimental, view only; see above) |
[serial] |
Pick a device when several are connected |
With no arguments, ~/.config/handset/args is used if it exists. HANDSET_DEBUG=1 prints diagnostics, and
HANDSET_LANDING=wifi or qr opens that view of the connect screen first. ADB and FFMPEG override where the
tools are found.
Choices made in the menus (frame, tilt, toolbar, touches, capture format, audio normalisation, clipboard copy and
sync, mute, styled copies of captures) are remembered in ~/.config/handset/settings, a plain key=value file
you can edit. Command-line options win for the run they are given and are never saved.
On every start, Handset asks GitHub whether a newer release exists and
shows a blue banner at the bottom of the phone and a green update button in the toolbar; click either to update,
or the banner's X to hide it for three days. Check for Updates... in the menu asks right away, and Check for
Updates on Start turns the automatic check off (or use --no-update-check, or update-check=off in the settings
file). The request goes to api.github.com through curl and carries only a handset/<version> User-Agent.
Handset knows how it was installed: with Homebrew, Scoop or winget the banner gives the command to run
(brew upgrade --cask handset, scoop update handset, winget upgrade davidamunga.Handset) and a click
copies it, so the package manager stays in charge. A downloaded app (macOS) or unzipped release (Windows) can
install the update itself: it checks the release's signature and checksum, replaces the app and restarts, and puts the
old one back if anything fails. The menu also offers What's New, Skip (that version stays quiet) and Remind
Me in 3 Days. On Linux an Arch package copies yay -S, paru -S or the AUR's makepkg steps, cargo install
copies cargo install --git https://github.com/davidamunga/handset --locked handset --force, the Nix package is
told to update through Nix, and an unpacked release tarball (next to its handset.desktop, or in ~/.local/bin)
installs the update itself like the macOS app.
How it works: docs/updates.md.
You need Rust (stable; on Linux 1.91 or newer and the build packages under Linux).
git clone https://github.com/davidamunga/handset.git
cd handset/app
cargo run --release -- --audioTo install the command line tool instead: cargo install --path app --locked.
macOS: build the app bundle and open it:
scripts/package-macos.sh
open dist/Handset.appA bundle you build yourself is ad-hoc signed, so the first time you may need to right-click it and choose Open (the released app is signed and notarised and does not).
Windows: build with cargo rustc --release --bin handset -- -C link-arg=packaging/windows/handset.res in app
(plain cargo build --release also works, but the .exe file then has no icon); the binary is
app\target\release\handset.exe. scripts\package-windows.ps1 builds dist\windows\Handset-<version>-windows.zip
(unsigned; pass -ToolsDir to bundle adb and ffmpeg).
Linux: run cargo build --release in app; the binary is app/target/release/handset.
scripts/package-linux.sh builds dist/linux/Handset-<version>-linux-<arch>.tar.gz (the binary, a
handset.desktop entry, the icon and the licences) and, when appimagetool is installed, an AppImage; CI
attaches the tarball to each run and each release carries it for x86_64 and aarch64 (the AppImage is not released).
Neither bundles ALSA, so it must be installed.
adb forwards a port to the scrcpy server on the phone. Handset reads the video, audio and control sockets, decodes H.264 on a video thread, converts and draws the frame on the GPU, and writes touch and key messages straight back. The reasons behind each choice are in docs/adr; procedures for rebuilding the server, releasing and debugging are in docs/runbooks.
Run handset --doctor. It checks adb, ffmpeg and each connected phone (authorised, online, Android version,
screen on), says what to fix, and exits 1 when something blocks mirroring; on Linux it also lists missing optional
helpers. Run checks on the connect screen shows the same checks in the frame. HANDSET_DEBUG=1 prints
diagnostics.
Ideas, not promises:
- Keyboard-to-touch mapping profiles (a long-standing request in the scrcpy world)
- A device picker
- OBS-friendly output (Syphon or a virtual camera)
- Microphone to phone
- A self-contained Linux bundle (AppImage or Flatpak)
- Touch control for iPhone and iPad
See CONTRIBUTING.md and AGENTS.md. Please use Conventional Commits.
Handset embeds the scrcpy server by Genymobile and Romain Vimont (Apache-2.0); see NOTICE. Handset is not affiliated with Genymobile. Licensed under Apache-2.0, see LICENSE.






