This page documents the client-side input processing subsystem: how SDL input events are received, classified as either shortcut actions or device input, and dispatched to the appropriate processor. The primary component is sc_input_manager, defined in app/src/input_manager.h and implemented in app/src/input_manager.c
This page covers only the routing and translation layer within the normal (non-OTG) scrcpy session. For the OTG/USB direct-input path (which uses sc_screen_otg and AOA HID descriptors instead), see OTG Mode and USB Input. For a comparison of SDK, UHID, and AOA injection strategies, see Input Injection Modes. For the control message wire format that is produced by this layer, see Control Message Protocol.
The input processing layer sits between the SDL event loop (in sc_screen_handle_event) and the device. It is responsible for:
sc_key_event, sc_mouse_click_event, etc.) and forwarding them to pluggable processor traits (sc_key_processor, sc_mouse_processor, sc_gamepad_processor).The processors themselves decide how to deliver the input to the device — via control messages over the ADB socket (SDK mode), UHID virtual HID devices, or AOA over USB.
Three levels of input events are described in app/src/input_events.h13-43:
| Level | Description |
|---|---|
| 1. SDL events | Raw events received from SDL (SDL_KeyboardEvent, SDL_MouseButtonEvent, etc.) |
| 2. scrcpy input events | Normalized events with device frame coordinates (sc_key_event, sc_mouse_click_event, etc.) |
| 3. Processor events | Android API keycodes / HID reports forwarded to the server or USB device |
sc_input_manager app/src/input_manager.h18-47 holds all runtime state for input handling:
| Field | Type | Purpose |
|---|---|---|
controller | sc_controller * | Sends control messages to the Android server |
fp | sc_file_pusher * | Receives drag-and-dropped files |
screen | sc_screen * | Used for coordinate conversion and display actions |
kp | sc_key_processor * | Pluggable keyboard handler (SDK/UHID) |
mp | sc_mouse_processor * | Pluggable mouse handler (SDK/UHID) |
gp | sc_gamepad_processor * | Pluggable gamepad handler (SDK/UHID) |
mouse_bindings | sc_mouse_bindings | Configurable per-button actions |
legacy_paste | bool | Use text-injection rather than clipboard sync for paste |
clipboard_autosync | bool | Sync host clipboard before forwarding Ctrl+V |
vfinger_down | bool | Whether pinch-to-zoom virtual finger is active |
vfinger_invert_x/y | bool | Determines axis inversion for the virtual finger |
mouse_buttons_state | uint8_t | Tracks currently-pressed mouse buttons |
key_repeat | unsigned | Counts consecutive shortcut key presses (not SDL autorepeat) |
next_sequence | uint64_t | Sequence counter for clipboard sync acknowledgements |
sc_input_manager is initialized by sc_input_manager_init app/src/input_manager.c16-49 and owned by sc_screen app/src/screen.h35
Initialization occurs inside sc_screen_init app/src/screen.c432-445 which populates an sc_input_manager_params struct from sc_screen_params and calls sc_input_manager_init.
Sources: app/src/input_manager.h app/src/screen.c432-445 app/src/screen.h25-72
Title: SDL event dispatch through sc_input_manager
Sources: app/src/screen.c864-871 app/src/input_manager.c370-695
The three processor traits define the interface contracts between sc_input_manager and the injection backends. Each is a vtable struct.
Title: Processor trait structs and their op tables
Relevant fields on the traits:
sc_key_processor.hid — if true, the MOD+K shortcut (open hard keyboard settings) is enabled.sc_key_processor.async_paste — if true, clipboard sync uses sequenced acknowledgements so the key processor waits for the clipboard to be set before injecting Ctrl+V.sc_mouse_processor.relative_mode — if true, positions are not computed (only xrel/yrel deltas are used), and virtual finger simulation is disabled.Sources: app/src/trait/gamepad_processor.h app/src/input_manager.c560-612 app/src/input_manager.h18-47
sc_input_manager_process_key app/src/input_manager.c370-613 is the most complex handler. Its logic is:
Determine if the event is a shortcut. A key event is a shortcut if the active modifier state includes at least one configured shortcut modifier (sdl_shortcut_mods), or if the key itself is a modifier key being released app/src/input_manager.c388-390
Dispatch shortcuts. If it is a shortcut, the key is matched in a large switch statement and dispatched to a local action or a control message. Non-matching shortcut keys are silently dropped.
Handle clipboard autosync for Ctrl+V. If the event is not a shortcut but matches Ctrl+V and clipboard_autosync is enabled, the host clipboard is serialized into a SC_CONTROL_MSG_TYPE_SET_CLIPBOARD message before the key event is forwarded app/src/input_manager.c565-591
Forward to key processor. A sc_key_event is built (keycode, scancode, modifiers, repeat flag) and passed to kp->ops->process_key.
Text input events (SDL_TEXTINPUT) are handled separately by sc_input_manager_process_text_input app/src/input_manager.c313-331 which discards events generated while a shortcut modifier is held.
All shortcuts use a configurable modifier (MOD, default Alt on Linux/Windows, Cmd on macOS).
| Shortcut | Action | Function called |
|---|---|---|
MOD+H | Android Home | action_home |
MOD+B / MOD+Backspace | Android Back | action_back |
MOD+S | App switch | action_app_switch |
MOD+M | Menu | action_menu |
MOD+P | Power toggle | action_power |
MOD+O / MOD+Shift+O | Display off / on | set_display_power |
MOD+Z / MOD+Shift+Z | Pause / resume display | sc_screen_set_paused |
MOD+↑ / MOD+↓ | Volume up / down | action_volume_up/down |
MOD+← / MOD+→ | Rotate display 270°/90° | apply_orientation_transform |
MOD+Shift+←/→/↑/↓ | Flip display | apply_orientation_transform |
MOD+C | Copy from device clipboard | get_device_clipboard(SC_COPY_KEY_COPY) |
MOD+X | Cut from device clipboard | get_device_clipboard(SC_COPY_KEY_CUT) |
MOD+V | Paste to device | clipboard_paste or set_device_clipboard |
MOD+Shift+V | Paste via text injection | clipboard_paste |
MOD+F | Toggle fullscreen | sc_screen_toggle_fullscreen |
MOD+W | Resize window to fit | sc_screen_resize_to_fit |
MOD+G | Resize to pixel-perfect | sc_screen_resize_to_pixel_perfect |
MOD+I | Toggle FPS counter | switch_fps_counter_state |
MOD+N / MOD+Shift+N | Notification / settings panel | expand_notification_panel / collapse_panels |
MOD+R / MOD+Shift+R | Rotate device / Reset video | rotate_device / reset_video |
MOD+K | Open hard keyboard settings (HID only) | open_hard_keyboard_settings |
Sources: app/src/input_manager.c402-557
Before any mouse or touch position is passed to a processor, it is converted from SDL window-space to device frame-space. This accounts for:
The conversion functions are sc_screen_convert_window_to_frame_coords and sc_screen_convert_drawable_to_frame_coords app/src/screen.c875-935 Both ultimately use the content rectangle screen->rect and the current screen->orientation to map coordinates.
In relative mode (mp->relative_mode == true), no position is computed; only xrel/yrel deltas are provided.
Right-click, middle-click, and extra buttons (X1, X2) are configurable via sc_mouse_bindings app/src/input_manager.c697-714 Bindings are resolved by sc_input_manager_get_binding, which checks mouse_bindings.pri or mouse_bindings.sec depending on whether Shift is held.
| Binding value | Action |
|---|---|
SC_MOUSE_BINDING_CLICK | Forwarded as a regular click |
SC_MOUSE_BINDING_BACK | press_back_or_turn_screen_on |
SC_MOUSE_BINDING_HOME | action_home |
SC_MOUSE_BINDING_APP_SWITCH | action_app_switch |
SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL | expand_notification_panel / expand_settings_panel |
SC_MOUSE_BINDING_DISABLED | Event is dropped |
Left-click is always SC_MOUSE_BINDING_CLICK app/src/input_manager.c701-702
A double-click on the black border area (outside screen->rect) triggers sc_screen_resize_to_fit app/src/input_manager.c788-801
When the left mouse button is pressed while Ctrl or Shift is held, a virtual second finger (SC_POINTER_ID_VIRTUAL_FINGER) is injected at the mirror position of the real cursor app/src/input_manager.c836-880 This simulates multi-touch gestures without requiring a touchscreen.
| Modifier held | vfinger_invert_x | vfinger_invert_y | Gesture simulated |
|---|---|---|---|
| Neither | false | false | (normal click) |
| Shift only | true | false | Vertical two-finger tilt |
| Ctrl only | true | true | Pinch-to-zoom / rotate |
| Ctrl+Shift | false | true | Horizontal two-finger tilt |
The virtual finger position is computed by inverse_point app/src/input_manager.c357-367 and injected via simulate_virtual_finger app/src/input_manager.c334-355 which sends SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT messages directly through the controller.
Sources: app/src/input_manager.c334-881 app/src/screen.c875-947
sc_input_manager_process_touch app/src/input_manager.c668-695 handles SDL_FINGERMOTION, SDL_FINGERDOWN, and SDL_FINGERUP events. SDL normalizes touch coordinates to [0.0, 1.0], so they are scaled by the drawable dimensions and then passed through sc_screen_convert_drawable_to_frame_coords.
The resulting sc_touch_event carries finger_id as pointer_id and pressure from the SDL event. It is forwarded to mp->ops->process_touch if the processor implements that operation (it is optional in the sc_mouse_processor_ops vtable).
Gamepad support uses SDL's game controller API. The four dispatch functions handle device lifecycle and input:
| SDL event | Handler | Processor op |
|---|---|---|
SDL_CONTROLLERDEVICEADDED | sc_input_manager_process_gamepad_device | gp->ops->process_gamepad_added |
SDL_CONTROLLERDEVICEREMOVED | sc_input_manager_process_gamepad_device | gp->ops->process_gamepad_removed |
SDL_CONTROLLERAXISMOTION | sc_input_manager_process_gamepad_axis | gp->ops->process_gamepad_axis |
SDL_CONTROLLERBUTTONDOWN/UP | sc_input_manager_process_gamepad_button | gp->ops->process_gamepad_button |
When a device is added, SDL_GameControllerOpen is called and its SDL_JoystickInstanceID is used as gamepad_id in all subsequent events. When removed, SDL_GameControllerClose is called. Unknown axes and buttons are filtered out by sc_gamepad_axis_from_sdl and sc_gamepad_button_from_sdl (returning SC_GAMEPAD_AXIS_UNKNOWN / SC_GAMEPAD_BUTTON_UNKNOWN) app/src/input_events.h501-519
Sources: app/src/input_manager.c914-950 app/src/input_events.h328-355 app/src/trait/gamepad_processor.h
When clipboard_autosync is enabled (the default), pressing Ctrl+V in the SDL window triggers a clipboard synchronization step before the key event is forwarded to the device:
SDL_GetClipboardText() is called to get the host clipboard content.SC_CONTROL_MSG_TYPE_SET_CLIPBOARD message is queued through sc_controller_push_msg.async_paste, the message carries a monotonically-increasing sequence value (next_sequence). The processor must wait for an acknowledgement with this sequence before injecting the Ctrl+V keycode. The sequence counter is only incremented on success app/src/input_manager.c574-590async_paste is false, SC_SEQUENCE_INVALID is used, and the key event is forwarded immediately.legacy_paste mode bypasses this mechanism entirely: the clipboard text is injected as SC_CONTROL_MSG_TYPE_INJECT_TEXT without any clipboard sync app/src/input_manager.c567-570
Sources: app/src/input_manager.c565-612
When a mouse processor uses relative_mode, SDL's relative mouse mode must be engaged to hide the cursor and deliver raw deltas instead of absolute positions. This is managed by sc_mouse_capture app/src/mouse_capture.h which is stored directly in sc_screen app/src/screen.h36
sc_mouse_capture_handle_event app/src/mouse_capture.c19-83 is called by sc_screen_handle_event before sc_input_manager_handle_event app/src/screen.c864-870 It intercepts:
SDL_SetRelativeMouseMode.SDL_WINDOWEVENT_FOCUS_LOST: automatically releases capture.SDL_MOUSEBUTTONUP, which activates capture.When relative mode is active, sc_input_manager_get_position returns a zeroed sc_position (no absolute coordinates) app/src/input_manager.c618-624 and virtual finger simulation is disabled app/src/input_manager.c652-653
In no-video mode (--no-video), if a mouse processor uses relative mode, mouse capture is activated immediately on startup rather than waiting for the first frame app/src/screen.c468-471
Sources: app/src/mouse_capture.c app/src/mouse_capture.h app/src/screen.c864-871
SDL_DROPFILE events are handled by forwarding the file path to sc_file_pusher. The fp pointer in sc_input_manager app/src/input_manager.h20 is null when --no-control is used. The sc_file_pusher component handles APK installation and file push to the device via ADB (not part of the input processing layer itself).
Title: sc_input_manager data flow — from SDL event to device
Sources: app/src/input_manager.c app/src/input_manager.h app/src/screen.c864-871
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.