Documentation Index
Fetch the complete documentation index at: https://mintlify.com/zwc456baby/ScrcpyForAndroid/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The EventController class runs on the scrcpy server side and processes input events received from the client, injecting them into the Android system. It supports multi-touch gestures, keyboard input, and screen power management.
Package: org.server.scrcpy
Location: Server-side (runs on the Android device being controlled)
Initialization
Constructor
Creates a new EventController instance.
public EventController(Device device, DroidConnection connection)
Device instance for injecting events and accessing screen state
Connection instance for receiving control events from the client
Example:
Device device = new Device();
DroidConnection connection = new DroidConnection(socket);
EventController controller = new EventController(device, connection);
Main Control Loop
control()
Main event processing loop that receives and handles input events.
public void control() throws IOException
Behavior:
- Turns the screen on when starting
- Continuously receives events from the client
- Routes events to appropriate handlers
- Blocks until an IOException occurs
Example:
try {
controller.control();
} catch (IOException e) {
Log.e("EventController", "Connection lost: " + e.getMessage());
}
Event Loop Implementation:
// From source (EventController.java:66-121)
public void control() throws IOException {
// Turn screen on at start
turnScreenOn();
while (true) {
int[] buffer = connection.NewreceiveControlEvent();
if (buffer != null) {
long now = SystemClock.uptimeMillis();
// Check if this is a keycode event (no coordinates)
if (buffer[2] == 0 && buffer[3] == 0) {
if (buffer[0] == 28) {
proximity = true; // Proximity sensor near
} else if (buffer[0] == 29) {
proximity = false; // Proximity sensor far
} else {
injectKeycode(buffer[0]);
}
} else {
// Touch event with coordinates
Point point = new Point(buffer[2], buffer[3]);
Point newpoint = device.NewgetPhysicalPoint(point);
injectTouch(buffer[0], buffer[4], newpoint, buffer[1]);
}
}
}
}
Touch Event Handling
injectTouch()
Processes and injects touch events with multi-touch support.
private boolean injectTouch(int action, long pointerId, Point point, int button)
MotionEvent action constant (e.g., ACTION_DOWN, ACTION_MOVE, ACTION_UP)
Unique identifier for the touch pointer (0-9 for multi-touch)
Touch coordinates on the screen
Returns true if the event was successfully injected
Supported Actions:
First finger touches the screen
Last finger leaves the screen
Additional finger touches (for multi-touch)
Additional finger leaves (for multi-touch)
Multi-Touch Implementation:
// From source (EventController.java:158-183)
private boolean injectTouch(int action, long pointerId, Point point, int button) {
long now = SystemClock.uptimeMillis();
// Get or create pointer index
int pointerIndex = pointersState.getPointerIndex(pointerId);
if (pointerIndex == -1) {
Ln.w("Too many pointers for touch event");
return false;
}
// Update pointer state
Pointer pointer = pointersState.get(pointerIndex);
pointer.setPoint(point);
pointer.setPressure(1.0f);
// Configure as touchscreen event
pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_FINGER;
int source = InputDevice.SOURCE_TOUCHSCREEN;
// Handle pointer up events
boolean pointerUp = action == MotionEvent.ACTION_UP;
int actionType = action & MotionEvent.ACTION_MASK;
if (actionType == MotionEvent.ACTION_POINTER_UP) {
pointerUp = true;
}
pointer.setUp(pointerUp);
// Update pointer count and coordinates
int pointerCount = pointersState.update(pointerProperties, pointerCoords);
// Adjust action for multi-touch
if (pointerCount == 1) {
if (action == MotionEvent.ACTION_DOWN) {
lastMouseDown = now;
}
} else {
// Convert to ACTION_POINTER_* for secondary fingers
if (action == MotionEvent.ACTION_UP ||
actionType == MotionEvent.ACTION_POINTER_UP) {
action = MotionEvent.ACTION_POINTER_UP |
(pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
} else if (action == MotionEvent.ACTION_DOWN ||
actionType == MotionEvent.ACTION_POINTER_DOWN) {
action = MotionEvent.ACTION_POINTER_DOWN |
(pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
}
// Create and inject motion event
MotionEvent event = MotionEvent.obtain(
lastMouseDown, now, action, pointerCount,
pointerProperties, pointerCoords,
0, button, 1f, 1f, 0, 0, source, 0
);
return injectEvent(event);
}
Key Event Handling
injectKeycode()
Injects a keycode as both DOWN and UP events.
private boolean injectKeycode(int keyCode)
Android KeyEvent keycode constant
Returns true if both events were successfully injected
Common Keycodes:
KeyEvent.KEYCODE_POWER - Power button
KeyEvent.KEYCODE_BACK - Back button
KeyEvent.KEYCODE_HOME - Home button
KeyEvent.KEYCODE_VOLUME_UP - Volume up
KeyEvent.KEYCODE_VOLUME_DOWN - Volume down
Implementation:
// From source (EventController.java:256-258)
private boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0)
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
}
injectKeyEvent()
Injects a single key event with full control.
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState)
KeyEvent.ACTION_DOWN or KeyEvent.ACTION_UP
Repeat count for the key event
Meta key state (Shift, Alt, Ctrl, etc.)
Implementation:
// From source (EventController.java:249-253)
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(
now, now, action, keyCode, repeat, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD
);
return injectEvent(event);
}
Events are received as integer arrays from the client.
Array Structure (5 integers = 20 bytes):
buffer[0] = action; // MotionEvent action constant
buffer[1] = buttonState; // Button state flags
buffer[2] = x; // X coordinate
buffer[3] = y; // Y coordinate
buffer[4] = pointerId; // Pointer ID for multi-touch (0-9)
Example:
// Single finger down at (500, 800)
int[] touchDown = {MotionEvent.ACTION_DOWN, 0, 500, 800, 0};
// Second finger down at (600, 900)
int[] secondFinger = {MotionEvent.ACTION_POINTER_DOWN, 0, 600, 900, 1};
// Move first finger to (510, 810)
int[] move = {MotionEvent.ACTION_MOVE, 0, 510, 810, 0};
Array Structure (1 integer = 4 bytes):
buffer[0] = keyCode; // Android keycode
buffer[1] = 0; // Unused
buffer[2] = 0; // Marks as key event (not touch)
buffer[3] = 0; // Marks as key event (not touch)
Detection Logic:
if (buffer[2] == 0 && buffer[3] == 0) {
// This is a key event
injectKeycode(buffer[0]);
} else {
// This is a touch event
injectTouch(buffer[0], buffer[4],
new Point(buffer[2], buffer[3]),
buffer[1]);
}
Special Keycodes
Proximity sensor near - Sets proximity flag to true
Proximity sensor far - Sets proximity flag to false
Multi-Touch Support
The EventController supports up to 10 simultaneous touch points.
Manages the state of all active touch pointers.
private final PointersState pointersState = new PointersState();
private final MotionEvent.PointerProperties[] pointerProperties =
new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
private final MotionEvent.PointerCoords[] pointerCoords =
new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
Pointer Initialization:
// From source (EventController.java:52-64)
private void initPointers() {
for (int i = 0; i < PointersState.MAX_POINTERS; ++i) {
MotionEvent.PointerProperties props =
new MotionEvent.PointerProperties();
props.toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent.PointerCoords coords =
new MotionEvent.PointerCoords();
coords.orientation = 0;
coords.size = 0;
pointerProperties[i] = props;
pointerCoords[i] = coords;
}
}
Pointer Tracking Example
// Two-finger pinch gesture
// 1. First finger down
int[] finger1Down = {MotionEvent.ACTION_DOWN, 0, 400, 600, 0};
controller.processEvent(finger1Down);
// 2. Second finger down
int[] finger2Down = {MotionEvent.ACTION_POINTER_DOWN, 0, 600, 800, 1};
controller.processEvent(finger2Down);
// 3. Move both fingers (pinch in)
int[] finger1Move = {MotionEvent.ACTION_MOVE, 0, 450, 650, 0};
int[] finger2Move = {MotionEvent.ACTION_MOVE, 0, 550, 750, 1};
controller.processEvent(finger1Move);
controller.processEvent(finger2Move);
// 4. First finger up
int[] finger1Up = {MotionEvent.ACTION_POINTER_UP, 0, 450, 650, 0};
controller.processEvent(finger1Up);
// 5. Second finger up
int[] finger2Up = {MotionEvent.ACTION_UP, 0, 550, 750, 1};
controller.processEvent(finger2Up);
Screen Power Management
The EventController includes special handling for screen wake-up.
Double-Tap Wake
When the screen is off or proximity sensor is active, tapping twice quickly wakes the screen:
// From source (EventController.java:85-97)
int action = buffer[0];
if (action == MotionEvent.ACTION_UP &&
(!device.isScreenOn() || proximity)) {
if (hit) {
// Second tap within 250ms
if (now - then < 250) {
then = 0;
hit = false;
injectKeycode(KeyEvent.KEYCODE_POWER); // Wake screen
} else {
then = now;
}
} else {
// First tap
hit = true;
then = now;
}
}
turnScreenOn()
Ensures the screen is on when the controller starts.
private boolean turnScreenOn()
Implementation:
// From source (EventController.java:265-267)
private boolean turnScreenOn() {
return device.isScreenOn() || injectKeycode(KeyEvent.KEYCODE_POWER);
}
Event Injection
injectEvent()
Low-level method to inject events into the Android system.
private boolean injectEvent(InputEvent event)
The MotionEvent or KeyEvent to inject
Returns true if injection was successful
Implementation:
// From source (EventController.java:261-263)
private boolean injectEvent(InputEvent event) {
return device.injectInputEvent(
event,
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC
);
}
Events are injected asynchronously (INJECT_INPUT_EVENT_MODE_ASYNC) for better performance and to avoid blocking the control loop.
Touch coordinates are transformed from client screen space to device physical coordinates:
Point point = new Point(buffer[2], buffer[3]); // Client coordinates
Point newpoint = device.NewgetPhysicalPoint(point); // Physical coordinates
injectTouch(action, pointerId, newpoint, buttonState);
This handles:
- Screen rotation
- Resolution differences
- Aspect ratio adjustments
Usage Example
Complete server-side event handling setup:
public class ScrcpyServer {
public void startServer(Socket socket) throws IOException {
// Initialize device and connection
Device device = new Device();
DroidConnection connection = new DroidConnection(socket);
// Create event controller
EventController controller = new EventController(device, connection);
// Start control loop (blocks until error)
try {
controller.control();
} catch (IOException e) {
Log.e("Server", "Control loop ended: " + e.getMessage());
} finally {
socket.close();
}
}
}
Event injection requires system-level permissions. The server must run with appropriate privileges to inject input events.
Key Points:
- Events are processed sequentially in the order received
- Asynchronous injection prevents blocking
- Multi-touch state is tracked automatically
- Coordinate transformation is applied per-event
- Screen state affects touch event handling
See Also