# How scrcpy Injects Keyboard and Mouse Events into Android: A Deep Dive into the Control Pipeline

> Discover how scrcpy injects keyboard and mouse events into Android by detailing the control pipeline, client-server communication, and Java reflection techniques. Learn the inner workings of scrcpy's input injection.

- Repository: [Genymobile/scrcpy](https://github.com/Genymobile/scrcpy)
- Tags: deep-dive
- Published: 2026-02-25

---

**scrcpy injects keyboard and mouse events by transmitting control messages from the desktop client to an Android server that constructs KeyEvent and MotionEvent objects, then uses Java reflection to invoke the hidden `InputManager.injectInputEvent()` method.**

The Genymobile/scrcpy project achieves seamless desktop-to-Android input forwarding without requiring root access by running a privileged server component on the device. This article examines the three-layer pipeline that enables scrcpy to inject keyboard and mouse events into Android, referencing the actual source implementation.

## The Three-Layer Injection Architecture

scrcpy’s event injection operates through a distinct separation of concerns across the control channel, device abstraction, and system service layers.

### Control-Channel Handling (Controller.java)

The desktop client sends binary control messages over a socket to the Android server. The `Controller` class, located at [`server/src/main/java/com/genymobile/scrcpy/control/Controller.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/control/Controller.java), parses these messages and constructs the appropriate Android input objects.

Key methods in this layer include:

- `injectKeycode()` – Handles physical key presses (e.g., HOME, BACK)
- `injectChar()` – Processes Unicode character input
- `injectTouch()` – Translates mouse coordinates to touch events
- `injectScroll()` – Handles scroll wheel deltas

The `Controller` converts raw protocol messages into `KeyEvent` or `MotionEvent` instances, then forwards them to the device layer for injection.

### Device-Level Facade (Device.java)

The `Device` class at [`server/src/main/java/com/genymobile/scrcpy/device/Device.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/device/Device.java) acts as a façade that prepares events for the Android system. It handles display-specific routing and event metadata.

For keyboard input, `Device.injectKeyEvent()` creates a `KeyEvent` with the proper source flag (`InputDevice.SOURCE_KEYBOARD`) and calls the generic `Device.injectEvent()` method.

For mouse and touch input, `Device.injectEvent()` receives the `InputEvent` and performs critical preprocessing:

1. **Display binding**: If the target display is not the default, it attaches the display ID via `InputManager.setDisplayId()`
2. **Button state**: For mouse events, it sets the action button via `InputManager.setActionButton()`
3. **Source attribution**: Ensures the event carries the correct input source (mouse, touchpad, or keyboard)

### Android System Injection (InputManager Wrapper)

The final layer involves calling the hidden Android API. scrcpy cannot directly invoke `android.hardware.input.InputManager.injectInputEvent()` because it is marked `@hide`, so it uses reflection via the wrapper class at [`server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java).

The wrapper performs the following:

- **Lazy resolution**: Caches the `Method` object for `injectInputEvent(InputEvent, int)` on first use
- **Service retrieval**: Obtains the system service via `ServiceManager.getInputManager()` (from [`server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java))
- **Field injection**: Uses reflection to set private fields on the event object:
  - `setDisplayId()` for multi-display support
  - `setActionButton()` for mouse button tracking
- **Error handling**: Catches `SecurityException` for missing `INJECT_EVENTS` permission and logs appropriately

The injection modes—`INJECT_INPUT_EVENT_MODE_ASYNC`, `WAIT_FOR_RESULT`, and `WAIT_FOR_FINISH`—are defined as constants in the wrapper and passed through `Device` to control whether the call blocks until the event is processed.

## Event Types and Processing Details

### Keyboard Event Composition

For text input, scrcpy handles character composition through `KeyComposition` (located in the control package). When `injectChar()` receives a Unicode character, it uses `KeyCharacterMap.getEvents()` to decompose the character into a sequence of key codes. This ensures that complex characters requiring modifier keys (e.g., accented letters) generate the correct `KeyEvent` sequence.

### Mouse and Touch Translation

Mouse events undergo coordinate transformation from desktop screen space to Android display coordinates. The `Controller` maintains pointer IDs to support multi-touch scenarios. When injecting:

- **MotionEvent** objects are created with `MotionEvent.obtain()` using the appropriate action (`ACTION_DOWN`, `ACTION_MOVE`, `ACTION_UP`)
- **Button state** is preserved via `setActionButton()` to distinguish primary, secondary, and tertiary clicks
- **Scroll events** are converted from desktop scroll deltas to Android `MotionEvent` axis values

## Practical Implementation Examples

### Injecting a Keycode (Home Button)

To programmatically inject a Home key press using scrcpy’s server classes:

```java
// Inject HOME key on the primary display
boolean success = Device.injectKeyEvent(
    KeyEvent.ACTION_DOWN,
    KeyEvent.KEYCODE_HOME,
    0,                          // repeat count
    0,                          // meta state
    Device.DISPLAY_ID_NONE,     // default display
    Device.INJECT_MODE_ASYNC    // non-blocking
);
success &= Device.injectKeyEvent(
    KeyEvent.ACTION_UP,
    KeyEvent.KEYCODE_HOME,
    0, 0, 
    Device.DISPLAY_ID_NONE, 
    Device.INJECT_MODE_ASYNC
);

```

This uses the `Device.injectKeyEvent()` pathway that creates a properly sourced `KeyEvent` and routes it through the reflective `InputManager` wrapper.

### Simulating a Mouse Click

To simulate a primary mouse click at specific coordinates on a secondary display:

```java
long now = SystemClock.uptimeMillis();
float x = 400f, y = 300f;
int displayId = 1;
int button = MotionEvent.BUTTON_PRIMARY;

// ACTION_DOWN
MotionEvent down = MotionEvent.obtain(
    now, now, MotionEvent.ACTION_DOWN, x, y, 1.0f);
down.setSource(InputDevice.SOURCE_MOUSE);
InputManager.setActionButton(down, button);
Device.injectEvent(down, displayId, Device.INJECT_MODE_ASYNC);

// ACTION_UP
MotionEvent up = MotionEvent.obtain(
    now, now, MotionEvent.ACTION_UP, x, y, 0);
up.setSource(InputDevice.SOURCE_MOUSE);
InputManager.setActionButton(up, button);
Device.injectEvent(up, displayId, Device.INJECT_MODE_ASYNC);

```

This demonstrates the display-specific routing and button state preservation required for accurate mouse injection.

### Sending Text Input

To inject a text string character-by-character:

```java
String text = "scrcpy";
for (char c : text.toCharArray()) {
    // Decompose character to key events using KeyCharacterMap
    KeyEvent[] events = KeyCharacterMap.VIRTUAL_KEYBOARD.getEvents(new char[]{c});
    if (events != null) {
        for (KeyEvent event : events) {
            Device.injectKeyEvent(
                event.getAction(),
                event.getKeyCode(),
                event.getRepeatCount(),
                event.getMetaState(),
                Device.DISPLAY_ID_NONE,
                Device.INJECT_MODE_ASYNC
            );
        }
    }
}

```

This utilizes the `KeyCharacterMap` system to handle complex character composition, ensuring proper handling of modifiers and dead keys.

## Key Source Files and Architecture

The injection pipeline spans several critical files in the scrcpy server:

| Component | File Path | Primary Responsibility |
|-----------|-----------|------------------------|
| **Control Message Parser** | [`server/src/main/java/com/genymobile/scrcpy/control/Controller.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/control/Controller.java) | Parses socket messages and constructs `KeyEvent`/`MotionEvent` objects via `injectKeycode()`, `injectChar()`, `injectTouch()`, and `injectScroll()` |
| **Device Abstraction** | [`server/src/main/java/com/genymobile/scrcpy/device/Device.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/device/Device.java) | Provides `injectKeyEvent()` and `injectEvent()` to attach display IDs and route to the system service |
| **System Service Wrapper** | [`server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java) | Reflectively invokes `injectInputEvent()`, handles multi-display fields, and manages injection modes |
| **Service Locator** | [`server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java) | Provides singleton access to the system `InputManager` instance |
| **Text Composition** | [`server/src/main/java/com/genymobile/scrcpy/control/KeyComposition.java`](https://github.com/Genymobile/scrcpy/blob/main/server/src/main/java/com/genymobile/scrcpy/control/KeyComposition.java) | Converts Unicode characters to key sequences using `KeyCharacterMap` |

## Summary

- scrcpy injects keyboard and mouse events through a **three-layer pipeline**: control message parsing ([`Controller.java`](https://github.com/Genymobile/scrcpy/blob/main/Controller.java)), device abstraction ([`Device.java`](https://github.com/Genymobile/scrcpy/blob/main/Device.java)), and reflective system service access ([`InputManager.java`](https://github.com/Genymobile/scrcpy/blob/main/InputManager.java)).
- The **desktop client** sends binary control messages over a socket, which the Android server converts into native `KeyEvent` and `MotionEvent` objects.
- **Reflection** is required to access the hidden `InputManager.injectInputEvent()` API, as the `INJECT_EVENTS` permission is not granted to regular apps but is available to shell-level processes started via ADB.
- **Multi-display support** is achieved by attaching display IDs to events before injection, allowing mouse and keyboard input to target specific virtual displays.
- **Text input** uses `KeyCharacterMap` to decompose Unicode characters into proper key sequences, handling complex input methods and modifier keys.

## Frequently Asked Questions

### How does scrcpy inject events without root access?

scrcpy does not require root because it runs a server process on the Android device with shell-level privileges via ADB. When you start scrcpy, it pushes the `scrcpy-server` binary to `/data/local/tmp` and executes it through `adb shell`. This shell context grants the `INJECT_EVENTS` permission, which allows the server to call the hidden `InputManager.injectInputEvent()` method through reflection, something regular third-party apps cannot do.

### What is the difference between INJECT_MODE_ASYNC and other injection modes?

scrcpy supports three injection modes defined in [`InputManager.java`](https://github.com/Genymobile/scrcpy/blob/main/InputManager.java): `INJECT_INPUT_EVENT_MODE_ASYNC`, `WAIT_FOR_RESULT`, and `WAIT_FOR_FINISH`. `INJECT_MODE_ASYNC` (the default) returns immediately after queuing the event without waiting for the system to process it. `WAIT_FOR_RESULT` blocks until the input dispatcher accepts the event, while `WAIT_FOR_FINISH` waits until the event has been fully processed by the target application. scrcpy typically uses asynchronous mode to minimize latency between desktop input and Android response.

### Can scrcpy inject events to specific Android displays?

Yes, scrcpy supports multi-display injection by attaching a display ID to events before they reach the system `InputManager`. In [`Device.java`](https://github.com/Genymobile/scrcpy/blob/main/Device.java), the `injectEvent()` method checks if the target display differs from the default. If so, it uses reflection to call `InputManager.setDisplayId()` on the event object. This allows the input to appear on virtual displays or secondary screens rather than the primary touchscreen, essential for scenarios like controlling a device with multiple monitors or DeX mode.

### Why does scrcpy use reflection to access InputManager?

scrcpy uses reflection because `InputManager.injectInputEvent()` is a hidden API marked with `@hide` in the Android SDK. This method is not exposed to regular applications and requires the `INJECT_EVENTS` system permission, which is only granted to system apps or shell processes. Since scrcpy runs as a shell process via ADB, it has the necessary permission, but it must still access the method through Java reflection to bypass the SDK visibility restrictions. The wrapper class in [`InputManager.java`](https://github.com/Genymobile/scrcpy/blob/main/InputManager.java) caches the reflected method for performance and handles field injection for display IDs and action buttons.