CodeVetter · Repo Unpack · Contributor handoff
Genymobile/scrcpy
Build with meson+ninja (tests only exist in debug buildtype); the server needs the Android SDK via Gradle or can be skipped with -Dprebuilt_server=path. The code is dense C11 with an sc_ prefix convention, callback-struct vtables, vecdeque containers, and a strict init/start/stop/join/destroy lifecycle; every thread posts SDL events rather than touching shared UI state. The single most important invariant is that the C wire protocol and the Java wire protocol are manually kept in sync — always change both sides. Read doc/develop.md, app/src/scrcpy.c, app/src/server.c and Server.java before editing anything cross-cutting.
- Read first: doc/develop.md (architecture overview), app/src/scrcpy.c (wiring of all components), app/src/server.c (adb/tunnel/exec logic), server/.../Server.java + Options.java (server lifecycle and params), app/src/control_msg.h + ControlMessageReader.java (protocol contract).
- Build/test commands: 'meson setup builddir && ninja -C builddir'; tests need -Dbuildtype=debug then 'meson test -C builddir'; run helper './run builddir <args>'; server alone via server/build_without_gradle.sh or gradle; CI equivalents are release/test_client.sh and release/test_server.sh.
- Convention: every subsystem exposes sc_<name>_init/start/stop/join/destroy and a cbs struct of function pointers (on_ended, on_connected, ...); follow this exact lifecycle when adding components — teardown order in scrcpy() is load-bearing.
- Any protocol change must be applied twice: enum/serialize in app/src/control_msg.[ch] AND ControlMessage/ControlMessageReader server-side; same for stream headers (demuxer.c vs Streamer.java) and device messages (device_msg.c vs DeviceMessageWriter).
- When adding a server parameter you MUST also update VALIDATE_STRING usage if the value is user-controlled — values are interpolated into an adb shell command and are deliberately restricted from containing shell metacharacters.
- SCRCPY_SERVER_PATH (env) and devenv SCRCPY_SERVER_PATH let devs point the client at a locally-built server jar; PORTABLE builds instead look for scrcpy-server next to the binary.
- assert() is used as a real correctness mechanism client-side and is compiled out in release (b_ndebug=if-release) — never put side effects in asserts; debug builds memset the scrcpy struct to 42 to catch missed initializations.
- Threads never call SDL functions off the main thread; they sc_push_event(SC_EVENT_*) and the event_loop dispatches — keep this rule or races in SDL will follow (comment at sc_sdl_set_hints).
- Windows-specific traps: argv arrives UTF-16 and is converted to UTF-8 in main(); process spawning lives in sys/win/process.c and does not shell-escape args; stdout/stderr are unbuffered for logging.
- cli.c is ~3600 lines but highly regular — never hand-edit getopt strings; the optstring and longopts are generated from the options[] table by sc_getopt_adapter_create_optstring().
- Safe edit boundaries: util/ helpers and trait/ interfaces are self-contained; adding a feature typically means options.c default + cli.c row + server.c ADD_PARAM + Options.java case + a Server.java wiring line — touch all five or the flag silently does nothing. (inferred)