CodeVetter · Repo Unpack · Behavior traces
Genymobile/scrcpy
Three end-to-end flows were traced. (1) Startup: CLI parse -> SDL init -> server thread does adb start-server, device selection, push, tunnel open, app_process exec, socket handshake, device meta read -> demuxer/decoder/screen init -> SDL event loop. (2) Input: SDL key/mouse event -> input_manager/keyboard_sdk/mouse_sdk -> sc_control_msg -> controller queue -> serialized write -> server ControlMessageReader -> Controller.handleEvent -> Device.injectEvent or UhidManager. (3) Shutdown/disconnect: SDL_QUIT or demuxer EOS -> sc_server_stop interrupts sockets -> adb process watchdog (1s grace then SIGKILL) -> threads joined in careful order (demuxers before screen destroy).
- Startup order in main_scrcpy(): parse args -> set log level -> optional terminal title -> net_init() -> sc_main_thread_init() -> scrcpy(&opts); scrcpy() then SDL_Init(EVENTS) -> sc_server_init + sc_sdl_set_hints -> sc_server_start.
- Server-connection trace (run_server thread): sc_adb_start_server -> sc_adb_select_device (serial/-e/-d/$ANDROID_SERIAL) -> optional TCP/IP switch -> push_server -> sc_adb_tunnel_open -> execute_server -> sc_process_observer_init -> sc_server_connect_to -> cbs->on_connected.
- Tunnel connect: in reverse mode the client accept()s video then audio then control sockets on the adb-forwarded local socket; in forward mode it connect()s with retry (100 attempts x 100ms) and a dummy-byte read to detect dead tunnels; control socket gets TCP_NODELAY.
- Post-connect init order in scrcpy(): file_pusher -> video/audio demuxer init -> decoders attached as packet sinks -> recorder init+start -> controller configure/start -> screen init -> demuxer start -> optional screen-off msg, timeout, start-app msg -> event_loop().
- Server-side startup: Server.scrcpy() applies Workarounds, opens DesktopConnection, sends device meta, creates Controller (control), audio capture+encoder, SurfaceCapture+SurfaceEncoder, then starts each AsyncProcessor and blocks in Looper.loop() until Completion counts them down or a fatal error quits the looper.
- Keyboard trace: SDL key event -> sc_input_manager (shortcut_mods decide local shortcut vs device key) -> keyboard_sdk builds INJECT_KEYCODE/INJECT_TEXT msgs -> sc_controller_push_msg -> run_controller -> sc_control_msg_serialize -> socket -> ControlMessageReader.parseInjectKeycode -> Controller.injectKeycode -> Device.injectEvent(ASYNC).
- Clipboard device->host trace: ClipboardManager.addPrimaryClipChangedListener -> Device.getClipboardText -> DeviceMessageSender.send -> control socket -> client receiver thread -> sc_device_msg_deserialize -> task_set_clipboard on main thread -> SDL_SetClipboardText.
- Event loop dispatch: SC_EVENT_DEVICE_DISCONNECTED/CONTROLLER_ERROR/DEMUXER_ERROR/RECORDER_ERROR/AOA_OPEN_ERROR/TIME_LIMIT_REACHED map to exit codes; all other events (including SC_EVENT_NEW_FRAME, SC_EVENT_OPEN_WINDOW pushed by screen) go to sc_screen_handle_event().
- Demuxer end-of-stream posts SC_EVENT_DEVICE_DISCONNECTED (EOS) or SC_EVENT_DEMUXER_ERROR; audio demuxer tolerates DISABLED unless --require-audio; video DISABLED is asserted impossible.
- Shutdown: event_loop returns -> sc_main_thread_stop drains pending runnables -> sc_server_stop sets stopped + interrupts -> run_server net_interrupt()s the three sockets, waits 1s WATCHDOG_DELAY then sc_process_terminate -> demuxers/screen/controller joined in dependency order (screen destroyed only after demuxer join).
- Recording trace: --record -> sc_recorder_init + start before demuxer start; demuxer packet_source pushes AVPacket refs into recorder sinks; recorder thread dequeues, rescales PTS, writes via av_interleaved_write_frame; on_ended failure posts SC_EVENT_RECORDER_ERROR.
- UHID input trace (e.g. --keyboard=uhid): sc_keyboard_uhid_init registers a virtual device; key events -> HID report -> UHID_INPUT control msg -> server UhidManager.writeInput -> /dev/uhid; device output reports come back as DEVICE_MSG_TYPE_UHID_OUTPUT handled by receiver -> uhid_output task.