Control Android devices on PC
- Device list — one alias per device, mapped to a USB serial or
IP:port; add, rename and remove, with live online/connected status - Wireless pairing and discovery — pair once with the on-device code, and list the devices on your network with wireless debugging on in one click
- Parameter panels — the common options of six groups as controls, each mapped to its scrcpy long option name; every field has a "?" detail, plus a custom-arguments box
- Device parameters — a device's own parameter set, taking priority over global parameters, switched on or off as a whole
- Connection control — connect or disconnect one device or all of them; several devices can mirror at once, with processes and output managed by the app
- Runtime — scrcpy and adb fetched on demand, channels tried in order and downloads verified against the official checksums; view the path, switch to an existing directory, check for updates
- In-app updates — check for and download a new version from "About", verify it and replace in place; portable builds restart to finish, and a startup check can be enabled
- Logs — per-session output persisted, kept for 7 days / 30 days / forever (the cards live on the Settings page)
- Command preview — the exact command about to run, shown at the bottom and copyable
- Appearance — light/dark themes and accent color, following the system by default, with a sidebar that collapses to an icon rail
- Interface scale — enlarge the whole interface from the Settings page in four steps (100% / 115% / 130% / 150%), with text, icons and spacing growing together; the step is saved with your configuration
- Interface and code fonts — choose separate fonts for the interface and code areas on the Settings page, with missing glyphs falling back to the matching system stack
- Interface language — switch the UI between Chinese and English on the Settings page, or follow the system
Devices — device list and connection control:
Parameters — the common option controls:
Runtime — where scrcpy and adb are fetched:
Settings — appearance, fonts, interface language, updates and log settings:
To run: Windows 10 / 11 (one portable single file each for x64 and ARM64) · the WebView2 runtime that ships with the system (usually preinstalled on 10 / 11; installable via Microsoft's Evergreen bootstrapper if missing) · an Android device with USB debugging enabled. Linux gets AppImage and deb; macOS 11 and later gets dmg (Safari 16.2 or newer).
Get the latest version from Releases: on Windows it is a single portable file — pick the one whose name ends with _windows_x64.exe or _windows_arm64.exe for your architecture and put it in any writable directory, with no installer; on Linux it ships as AppImage and deb release artifacts; on macOS it ships as dmg (one each for Apple silicon and Intel), which you open and drag the app into Applications.
If Gatekeeper blocks the first launch on macOS, clear it either way: open System Settings → Privacy & Security and click Open Anyway, or run xattr -dr com.apple.quarantine /Applications/FreeControl.app.
- Get the runtime — on first launch, go to the "Runtime" page and fetch scrcpy and adb.
- Add a device — on the "Devices" page, add one: for USB, enter the serial listed by
adb devices; for wireless, choose "Manual (IP:Port)" to enter the address, or use "Network scan" / "Pairing code" to discover and pair a device. You can set an optional alias for the device or for each scan result. - Connect and mirror — click "Connect" on the device card. Several devices can be connected at once; the command preview at the bottom shows the exact command being run.
- From there you can adjust the global parameters on the parameter pages, or set device parameters for a single device (they take priority over global parameters).
You additionally need Node.js 24 and npm · a stable Rust toolchain · the MSVC build tools on Windows, system libraries such as webkit2gtk on Linux, or the Xcode command-line tools on macOS (see .github/workflows/build.yml for the list).
npm installWindows (single portable executable):
npm run app:buildThe artifact is written to src-tauri/target/release/FreeControl.exe.
Linux (AppImage and deb):
npm run tauri build -- --bundles appimage,debThe artifacts are written to src-tauri/target/release/bundle/.
macOS (dmg):
npm run tauri build -- --bundles dmgThe artifact is written to src-tauri/target/release/bundle/dmg/. The macOS artifacts are ad-hoc signed and not notarized, so the first launch needs the clearance described above.
When you trigger .github/workflows/build.yml manually, the artifacts are collected into a draft release (never published automatically). The draft body is the version's Chinese and English sections from Update.md and Update.en.md, and it is refreshed when you rebuild the same version. If the same version has already been published, re-running a build will not overwrite it: the artifacts land in a v<version>-draft pickup draft instead — that draft only exists to retrieve artifacts and is never published, and its body stays the pickup note.
npm install
npm run app:devThat one command starts the whole app, with the frontend dev server brought up by the build config (beforeDevCommand in src-tauri/tauri.conf.json; the UI runs at http://localhost:1420). The common commands are registered as editor tasks in .vscode/tasks.json, so you can also just pick them by name from the task list.
FreeControl/
├─ src/ Frontend (Svelte + Vite + TypeScript)
│ ├─ components/ UI components (devices, parameter pages, settings and logs, runtime, about dialog…)
│ └─ lib/ Frontend logic (parameter model, command building, validation, config)
├─ src-tauri/ Backend (Rust + Tauri 2)
│ ├─ src/ Command layer and platform code (device, scrcpy, config, assets, download, session, logging)
│ └─ tauri.conf.json App and bundling config — the single source of the version number
├─ assets/icons/ Brand asset package (single source, with the icon build pipeline and its docs)
├─ scripts/ Icon sync script (mirrors the bundling manifest into src-tauri/icons/)
├─ .vscode/ Editor task entries (they only reference the script names in package.json)
├─ ui-design/ UI baseline prototype (read-only reference)
├─ openspec/ Specs and change artifacts
└─ Update.md Changelog
USB — make sure the device has "Developer options" and "USB debugging" enabled, the cable is intact, the driver is fine on Windows (no yellow warning in Device Manager), and adb devices lists it. If "Developer options" is missing, tap "Build number" seven times in "Settings" - "About phone".
Wireless — in "Add device", choose "Network scan" or "Pairing code"; you can also choose "Manual (IP:Port)" to enter the connection address directly. On Android 11 and above, tap "Pair device with pairing code" in the device's "Wireless debugging"; the app finds the device and fills in the pairing address (pick one if several are found), so you only enter the 6-digit code. If nothing is found, enter the pairing address manually. The app then resolves the connection port and connects. You can set an optional alias for the device or for each scan result while adding, so there is no need to rename it on the device card. The port changes when the device restarts or wireless debugging is toggled, so click "Resolve port again" on the device card when it shows as offline. Devices still using adb tcpip 5555 are found by the scan, or you can enter IP:port yourself. The device and the computer must be on the same network.
Fetch them on the "Runtime" page. Neither ships with the app; both are downloaded into the application data directory on demand, or you can point that page at an existing custom directory.
Open "About" and click "Check for updates", or turn on "Check for updates automatically" on the "Settings" page — the app downloads and verifies the new version, then restarts to finish. On Linux the package build is handed to the system installer once downloaded. On macOS the dmg is downloaded and verified, then opened by Finder so you can drag the app into Applications over the old version; the app does not quit on its own.
Version numbers look like v<major>.<minor>.<patch>. A higher minor number means the release brings new capabilities (for example, the font selection on the Settings page); a higher patch number means fixes and polish only; the major number moves only for incompatible changes. Every release is recorded in the changelog.
A failed direct connection falls back to the next channel; you can also switch channels in place from the progress area. Channels that cannot be reached are skipped for the rest of the run instead of retried.
Make sure the WebView2 runtime is installed (usually included with Windows 10 / 11).
On setups such as virtual machines, software rendering or remote sessions, the window may draw its first frame and then stop updating (clicks still take effect). The app detects this at launch and avoids that rendering path on its own, so most such setups need nothing from you; if you still hit it, start the app with WEBKIT_DISABLE_DMABUF_RENDERER=1 FreeControl — the detection is heuristic, so some setups fall through.
All of it is under the application data root (%APPDATA%/FreeControl2 on Windows, a directory of the same name under the XDG data root on Linux, ~/Library/Application Support/FreeControl2 on macOS):
| Path | Purpose |
|---|---|
config.json |
The single configuration file (with a schemaVersion): device list, global parameters, device parameters, log retention |
logs/yyyy-MM-dd.log |
Persisted session output and key actions of the app itself |
scrcpy-{OS}-v{version}/ |
The scrcpy and adb runtime downloaded on demand (when no custom directory is set) |
Everything you can change in the UI lands in config.json.
Delete config.json and start the app again: the device list, the parameters and the retention policy all go back to their factory values.
The "Settings" page has a one-click shortcut to the log directory; the files live under logs/ in the application data root and are named by date.
That is intentional: quitting does not stop the adb server, so wireless connections survive the exit and are reused on the next launch.



