Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4485bde
save code
Mar 31, 2026
77a3d25
Merge branch 'master' into feat/control
Jul 3, 2026
12f0925
Rename binaries to scrcpy-auto / scrcpy-auto-server
Jul 3, 2026
b28181e
Add design document for persistent daemon mode
Jul 3, 2026
8c373c6
Implement persistent daemon mode (doc/daemon.md v1)
Jul 3, 2026
e66c1cd
Fix issues found by code review of daemon mode
Jul 3, 2026
83881d1
Document fork divergence and merge-safety rules
Jul 3, 2026
5f0ccdd
Fix touch injection in daemon mode and --control argument parsing
Jul 3, 2026
19f3986
Reject extra arguments in click/swipe commands
Jul 3, 2026
b13091c
Return a clear client-side error for malformed control commands
Jul 3, 2026
a229c02
Implement click as a zero-distance swipe, default 300ms
Jul 3, 2026
9b64fb7
Change default click duration to 200ms
Jul 3, 2026
aaee9e3
Daemon: add video subscription and realtime input injection
Jul 4, 2026
731e9f2
Daemon: request a keyframe on video subscribe
Jul 4, 2026
3686513
Test-report platform: daemon recording + operation logging (Phase A)
Jul 4, 2026
58a54ec
Test report: add --note standalone annotations
Jul 4, 2026
79b3743
Add plugin add-on system (--add-on / custom --<command>)
Jul 5, 2026
8b8b703
Report: timestamp events by wall clock, not frozen frame PTS
Jul 5, 2026
14f1aac
Add Unified Plugin Protocol: --json, --daemon-host, result channel
Jul 6, 2026
e604803
daemon: record plugin result as a report event
Jul 6, 2026
d696131
daemon: preserve plugin reference images in the report
Jul 6, 2026
e200492
Plugins: qualified --<command>.<arg> to resolve shared arg names
Jul 6, 2026
a5670ee
Add clip extraction: save a recording segment as MP4 while recording
Jul 9, 2026
0ec9500
daemon: --daemon-status reports plugins, report dir, and capture config
Jul 9, 2026
49938f5
daemon: inject capture config into the plugin environment
Jul 9, 2026
b631bb5
report: create --auto-test-report parents (mkdir -p)
Jul 9, 2026
9085d64
Add Homebrew formula (HomebrewFormula/scrcpy-auto.rb)
Jul 9, 2026
2933ee8
feat(banner): point startup URL to this fork, credit upstream scrcpy
dapangyu-fish Jul 9, 2026
8e5086a
feat(plugins): plugin manager — install/upgrade by git repo + --add-o…
dapangyu-fish Jul 9, 2026
c9b452f
fix(plugins): chdir to $HOME before git clone
dapangyu-fish Jul 9, 2026
79c1af7
fix(plugins): resolve --add-on names by absolute path, not cwd
dapangyu-fish Jul 9, 2026
0589709
homebrew: don't install freedesktop icons on macOS
Jul 9, 2026
f8e780b
daemon: manage long-running "service" add-ons
Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,19 @@ build/
/x/
local.properties
/scrcpy-server

# --- AI plugins & example/demo assets: LOCAL ONLY, never committed ---
# Only the plugin *framework* (the --add-on / plugin-protocol code) belongs in
# git. Concrete plugin implementations and every example/demo asset they carry
# (prompts, API keys, endpoint examples, sample paths, sample configs, test
# plugin code) must stay local. Keep such files outside the repo (e.g. under a
# temp dir); these patterns are a safety net if any land inside the tree.
exec_prompt*
assert_prompt*
ai_common.py
*_prompt.py
*-entrypoint.sh
/plugins/
/addons/
DESIGN-ai-plugins.md
*.plugin.local.*
52 changes: 52 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# scrcpy-auto (fork of Genymobile/scrcpy)

This is NOT upstream scrcpy. It is a fork renamed to `scrcpy-auto`, adding
automation features. **Read `doc/fork.md` before resolving any merge conflict
or touching the files listed there** — it is the authoritative inventory of
every divergence from upstream and the per-hotspot resolution rules.

- `master` mirrors upstream; `feat/control` carries all fork work.
- Daemon mode design/spec: `doc/daemon.md`.

## Invariants that must survive every upstream merge

Never resolve a conflict by taking only the upstream side in a file that
`doc/fork.md` lists as modified. In particular:

1. Binary names: client `scrcpy-auto`, server `scrcpy-auto-server`, device
path `/data/local/tmp/scrcpy-auto-server.jar`, install dir
`share/scrcpy-auto/` (defines at the top of `app/src/server.c`; names in
`app/meson.build` and `server/meson.build`).
2. Fork CLI options and their plumbing in `app/src/cli.c`,
`app/src/options.{h,c}`: `--screencap`, `--control`, `--daemon-port`,
`--client-port`, `--daemon-host`, `--json`, `--daemon-stop`,
`--daemon-status`, `--daemon-reconnect`, `--auto-test-report`, `--action`,
`--note`, `--add-on`, `--clip-start`, `--clip-end`, `--output`, plus the
unknown-`--name=value` plugin-call pre-scan in `scrcpy_parse_args`.
3. Fork events in `app/src/events.h` (`SC_EVENT_SCREENCAP_*`).
4. `SC_PACKET_SOURCE_MAX_SINKS` ≥ 4 in `app/src/trait/packet_source.h`.
5. `libswscale` dependency and the fork source files in `app/meson.build`.
6. Fork-owned modules (`app/src/daemon/`, `app/src/control_exec.*`,
`app/src/screencap.*`) compile against upstream-internal APIs — a merge
can break them with NO textual conflict. Two known traps:
- packet/frame sink `open()` signatures change across upstream versions
(happened in 4.0);
- `app/src/daemon/daemon.c` duplicates `scrcpy.c`'s
`struct sc_server_params` initializer — mirror any new field there.

## After any upstream merge

Run the checklist in `doc/fork.md` §6: no conflict markers, meson parses,
full tree compiles with zero `-Wall -Wextra` warnings (a toolchain-free
zig-cc recipe is documented there), invariant greps pass, then a device
smoke test of `--screencap`, `--control`, and the daemon flow.

## Conventions

- C11, upstream scrcpy style (4-space indent, `sc_` prefixes, one-shot
goto-cleanup ladders). Match surrounding code.
- The tree must stay warning-free under `-Wall -Wextra`.
- Do not add external runtime dependencies; small vendored single-header
libraries go to `app/src/third_party/` (jsmn lives there).
- Do not modify `release/` packaging scripts to fork names without also
testing them; they intentionally still use upstream names (doc/fork.md §3.2).
97 changes: 97 additions & 0 deletions HomebrewFormula/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Installing scrcpy-auto with Homebrew (macOS)

`scrcpy-auto` is an automation-focused fork of [scrcpy](https://github.com/Genymobile/scrcpy).
This directory holds a Homebrew formula so you can install the client with a
single command. Everything — dependencies, the client build, and the device-side
`scrcpy-auto-server` — is handled automatically.

## Quick install (from the GitHub URL)

```sh
brew install https://raw.githubusercontent.com/fish-dapangyu-dev/scrcpy/feat/control/HomebrewFormula/scrcpy-auto.rb
```

That installs a pinned, reproducible build (the `revision` in the formula).
To build the very latest fork work instead:

```sh
brew install --HEAD https://raw.githubusercontent.com/fish-dapangyu-dev/scrcpy/feat/control/HomebrewFormula/scrcpy-auto.rb
```

Then verify:

```sh
scrcpy-auto --version
adb devices # your device should be listed and "authorized"
```

## Install as a tap (optional, enables `brew install scrcpy-auto`)

Because the formula lives in `HomebrewFormula/` at the repo root, Homebrew can
tap this repository directly:

```sh
brew tap fish-dapangyu-dev/scrcpy https://github.com/fish-dapangyu-dev/scrcpy
brew install scrcpy-auto
brew upgrade scrcpy-auto
```

## What the formula does

| Piece | How it's handled |
|---|---|
| **Client** (`scrcpy-auto`) | Built from source with meson/ninja against Homebrew's SDL3, FFmpeg (incl. libswscale), and libusb. |
| **Device server** (`scrcpy-auto-server`) | **Not built** — building the `.jar` needs the Android SDK. The formula downloads the matching upstream release artifact (`scrcpy-server-v4.0`, SHA-256 pinned) and hands it to meson via `-Dprebuilt_server=…`, which installs it as `share/scrcpy-auto/scrcpy-auto-server`. |
| **adb** | Provided by the `android-platform-tools` cask, declared as a dependency. |
| **Server discovery** | The client looks for the server at `<prefix>/share/scrcpy-auto/scrcpy-auto-server` (baked in at build time). Override with `SCRCPY_SERVER_PATH` if needed. |

### Why the server is downloaded, not built

The client and the device server communicate over a versioned wire protocol, so
the server must **match the client version**. The fork does not modify the
server — it only renames the file on install — so the upstream `scrcpy-server-v4.0`
release artifact is byte-compatible. Building it locally would require a full
Android SDK + Gradle toolchain for no benefit, so the formula fetches the pinned
release instead. This is the same `prebuilt_server` mechanism documented in
`doc/build.md`.

## Requirements handled for you

- `meson`, `ninja`, `pkgconf` (build)
- `ffmpeg`, `sdl3`, `libusb` (runtime libraries)
- `android-platform-tools` cask (`adb`)

## First-time device setup

1. On the Android device: **Settings → Developer options → USB debugging** (on).
2. Connect via USB and accept the **"Allow USB debugging"** prompt.
3. `adb devices` should show the device as `device` (not `unauthorized`).

## Maintainer notes (cutting a new release)

The formula pins two things that must stay in lock-step:

1. **`revision`** (and `version`) — the fork source commit to build.
2. The **`server` resource** `url` + `sha256` — the matching
`scrcpy-server-v<VER>` upstream release artifact.

When you merge new fork work or bump the base scrcpy version:

- Update `revision` to the new commit SHA (and `version` if it changed).
- If the base scrcpy version changed, point the `server` resource at the new
`scrcpy-server-v<VER>` and update its `sha256` (shown on scrcpy's release
page / `doc/build.md`).

A mismatched server ("daemon protocol mismatch" / connection failures) almost
always means these two drifted apart. For a cleaner stable source you can also
create a git tag and switch the formula `url` to `tag:`/`revision:`.

## Troubleshooting

- **`Error: ... cask ... android-platform-tools`** — if your Homebrew rejects a
cask dependency from a formula, remove the `depends_on cask:` line and run
`brew install --cask android-platform-tools` manually.
- **`ERROR: Could not find scrcpy-auto-server`** — set
`SCRCPY_SERVER_PATH=$(brew --prefix)/share/scrcpy-auto/scrcpy-auto-server`.
- **`adb: no devices/emulators found`** — re-check USB debugging and the
authorization prompt; try a different cable/port.
80 changes: 80 additions & 0 deletions HomebrewFormula/scrcpy-auto.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
class ScrcpyAuto < Formula
desc "Automation-focused scrcpy fork: screen mirroring + scriptable control/daemon"
homepage "https://github.com/fish-dapangyu-dev/scrcpy"
# Stable install pins a source revision (a git checkout; no tarball needed).
# Bump `revision` (and `version` when the fork version changes) per release.
url "https://github.com/fish-dapangyu-dev/scrcpy.git",
revision: "b631bb5e55a322449183e5ce9cff9d0a017bef75"
version "4.0"
license "Apache-2.0"

# `brew install --HEAD ...` builds the latest fork work instead of the pin.
head "https://github.com/fish-dapangyu-dev/scrcpy.git", branch: "feat/control"

depends_on "meson" => :build
depends_on "ninja" => :build
depends_on "pkgconf" => :build

depends_on "ffmpeg" # provides libavcodec/format/util + libswscale (fork dep)
depends_on "libusb"
depends_on "sdl3"
# `adb` is required at runtime; the android-platform-tools cask provides it.
# If your Homebrew rejects a cask dependency here, delete this line and run
# `brew install --cask android-platform-tools` yourself (see the README).
depends_on cask: "android-platform-tools"

# The device-side server (a .jar) needs the Android SDK to build, so ship the
# matching UPSTREAM release artifact instead. Its wire protocol must match the
# client version, so this is pinned alongside the source revision above; bump
# both together. `scrcpy-server-v4.0` is byte-identical for the fork — the
# fork only renames the file on install, it does not modify the server.
resource "server" do
url "https://github.com/Genymobile/scrcpy/releases/download/v4.0/scrcpy-server-v4.0"
sha256 "84924bd564a1eb6089c872c7521f968058977f91f5ff02514a8c74aff3210f3a"
end

def install
# Stage the prebuilt server jar, then let meson's `prebuilt_server` copy it
# to share/scrcpy-auto/scrcpy-auto-server (no Android toolchain needed).
resource("server").stage do
buildpath.install Dir["*"].first => "scrcpy-server"
end

system "meson", "setup", "build",
"-Dprebuilt_server=#{buildpath}/scrcpy-server",
*std_meson_args
system "meson", "compile", "-C", "build"
system "meson", "install", "-C", "build"

# Freedesktop icons do nothing on macOS and are a frequent `brew link`
# conflict source (a prior manual `sudo ninja install` leaves the icons
# directory root-owned, which Homebrew cannot overwrite). Drop them; the
# man page and shell completions are kept.
(share/"icons").rmtree if (share/"icons").exist?
end

def caveats
<<~EOS
scrcpy-auto drives an Android device through ADB.

1. adb comes from the android-platform-tools cask (a dependency).
Verify it is on your PATH: adb version
2. On the device: enable Developer options > USB debugging, connect it,
and accept the "Allow USB debugging" prompt. Confirm with: adb devices

The device-side server was installed to:
#{opt_pkgshare}/scrcpy-auto-server
scrcpy-auto finds it automatically; override with SCRCPY_SERVER_PATH.

This is a fork (binary: scrcpy-auto, server: scrcpy-auto-server) and does
NOT conflict with an upstream `scrcpy` install.
EOS
end

test do
# --version prints the version and the linked dependency versions, exit 0.
assert_match version.to_s, shell_output("#{bin}/scrcpy-auto --version")
# the prebuilt server must have been installed alongside the client
assert_path_exists pkgshare/"scrcpy-auto-server"
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
_scrcpy() {
_scrcpy_auto() {
local cur prev words cword
local opts="
--always-on-top
Expand All @@ -22,8 +22,14 @@ _scrcpy() {
--camera-torch
--camera-zoom=
--capture-orientation=
--client-port=
--control=
--crop=
-d --select-usb
--daemon-port=
--daemon-reconnect=
--daemon-status
--daemon-stop
--disable-screensaver
--display-id=
--display-ime-policy=
Expand Down Expand Up @@ -87,6 +93,7 @@ _scrcpy() {
--require-audio
-s --serial=
-S --turn-screen-off
--screencap=
--screen-off-timeout=
--shortcut-mod=
--start-app=
Expand Down Expand Up @@ -243,4 +250,4 @@ _scrcpy() {
[[ $COMPREPLY == *= ]] && compopt -o nospace
}

complete -F _scrcpy scrcpy
complete -F _scrcpy_auto scrcpy-auto
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[Desktop Entry]
Name=scrcpy (console)
Name=scrcpy-auto (console)
GenericName=Android Remote Control
Comment=Display and control your Android device
# For some users, the PATH or ADB environment variables are set from the shell
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized.
Exec=/bin/sh -c "\\$SHELL -i -c 'scrcpy --pause-on-exit=if-error'"
Icon=scrcpy
Exec=/bin/sh -c "\\$SHELL -i -c 'scrcpy-auto --pause-on-exit=if-error'"
Icon=scrcpy-auto
Terminal=true
Type=Application
Categories=Utility;RemoteAccess;
Expand Down
File renamed without changes
6 changes: 3 additions & 3 deletions app/data/scrcpy.desktop → app/data/scrcpy-auto.desktop
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[Desktop Entry]
Name=scrcpy
Name=scrcpy-auto
GenericName=Android Remote Control
Comment=Display and control your Android device
# For some users, the PATH or ADB environment variables are set from the shell
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized.
Exec=/bin/sh -c "\\$SHELL -i -c scrcpy"
Icon=scrcpy
Exec=/bin/sh -c "\\$SHELL -i -c scrcpy-auto"
Icon=scrcpy-auto
Terminal=false
Type=Application
Categories=Utility;RemoteAccess;
Expand Down
File renamed without changes
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#compdef scrcpy scrcpy.exe
#compdef scrcpy-auto scrcpy-auto.exe
#
# name: scrcpy
# auth: hltdev [hltdev8642@gmail.com]
Expand Down Expand Up @@ -28,6 +28,13 @@ arguments=(
'--camera-size=[Specify an explicit camera capture size]'
'--camera-torch[Turn on the camera torch when the camera starts]'
'--camera-zoom[Specify the camera zoom initial value]'
'--client-port=[Run as a thin client of a scrcpy-auto daemon on 127.0.0.1\:port]'
'*--control=[Send a control command to the device \(click, swipe, input\)]'
'--daemon-port=[Run as a persistent daemon listening on 127.0.0.1\:port]'
'--daemon-reconnect=[Set the daemon reconnection policy]:policy:(auto none)'
'--daemon-status[Print the daemon state as JSON \(requires --client-port\)]'
'--daemon-stop[Ask the daemon to shut down gracefully \(requires --client-port\)]'
'--screencap=[Take a screenshot and save it to file]:file:_files'
'--capture-orientation=[Set the capture video orientation]:orientation:(0 90 180 270 flip0 flip90 flip180 flip270 @0 @90 @180 @270 @flip0 @flip90 @flip180 @flip270)'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]'
Expand Down
Loading