> ## 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.

# Event Controller

> Server-side input event controller for handling touch and key events on Android devices

## 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.

```java theme={null}
public EventController(Device device, DroidConnection connection)
```

<ParamField path="device" type="Device" required>
  Device instance for injecting events and accessing screen state
</ParamField>

<ParamField path="connection" type="DroidConnection" required>
  Connection instance for receiving control events from the client
</ParamField>

**Example:**

```java theme={null}
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.

```java theme={null}
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:**

```java theme={null}
try {
    controller.control();
} catch (IOException e) {
    Log.e("EventController", "Connection lost: " + e.getMessage());
}
```

**Event Loop Implementation:**

```java theme={null}
// 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.

```java theme={null}
private boolean injectTouch(int action, long pointerId, Point point, int button)
```

<ParamField path="action" type="int" required>
  MotionEvent action constant (e.g., `ACTION_DOWN`, `ACTION_MOVE`, `ACTION_UP`)
</ParamField>

<ParamField path="pointerId" type="long" required>
  Unique identifier for the touch pointer (0-9 for multi-touch)
</ParamField>

<ParamField path="point" type="Point" required>
  Touch coordinates on the screen
</ParamField>

<ParamField path="button" type="int" required>
  Button state flags
</ParamField>

<ResponseField name="return" type="boolean">
  Returns `true` if the event was successfully injected
</ResponseField>

**Supported Actions:**

<ResponseField name="ACTION_DOWN" type="int">
  First finger touches the screen
</ResponseField>

<ResponseField name="ACTION_UP" type="int">
  Last finger leaves the screen
</ResponseField>

<ResponseField name="ACTION_MOVE" type="int">
  Touch point moves
</ResponseField>

<ResponseField name="ACTION_POINTER_DOWN" type="int">
  Additional finger touches (for multi-touch)
</ResponseField>

<ResponseField name="ACTION_POINTER_UP" type="int">
  Additional finger leaves (for multi-touch)
</ResponseField>

**Multi-Touch Implementation:**

```java theme={null}
// 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.

```java theme={null}
private boolean injectKeycode(int keyCode)
```

<ParamField path="keyCode" type="int" required>
  Android KeyEvent keycode constant
</ParamField>

<ResponseField name="return" type="boolean">
  Returns `true` if both events were successfully injected
</ResponseField>

**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:**

```java theme={null}
// 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.

```java theme={null}
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState)
```

<ParamField path="action" type="int" required>
  `KeyEvent.ACTION_DOWN` or `KeyEvent.ACTION_UP`
</ParamField>

<ParamField path="keyCode" type="int" required>
  Android KeyEvent keycode
</ParamField>

<ParamField path="repeat" type="int" required>
  Repeat count for the key event
</ParamField>

<ParamField path="metaState" type="int" required>
  Meta key state (Shift, Alt, Ctrl, etc.)
</ParamField>

**Implementation:**

```java theme={null}
// 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);
}
```

## Event Data Format

Events are received as integer arrays from the client.

### Touch Event Format

**Array Structure (5 integers = 20 bytes):**

```java theme={null}
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:**

```java theme={null}
// 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};
```

### Key Event Format

**Array Structure (1 integer = 4 bytes):**

```java theme={null}
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:**

```java theme={null}
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

<ResponseField name="28" type="int">
  Proximity sensor near - Sets proximity flag to `true`
</ResponseField>

<ResponseField name="29" type="int">
  Proximity sensor far - Sets proximity flag to `false`
</ResponseField>

## Multi-Touch Support

The EventController supports up to 10 simultaneous touch points.

### PointersState

Manages the state of all active touch pointers.

```java theme={null}
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:**

```java theme={null}
// 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

```java theme={null}
// 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:

```java theme={null}
// 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.

```java theme={null}
private boolean turnScreenOn()
```

**Implementation:**

```java theme={null}
// 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.

```java theme={null}
private boolean injectEvent(InputEvent event)
```

<ParamField path="event" type="InputEvent" required>
  The MotionEvent or KeyEvent to inject
</ParamField>

<ResponseField name="return" type="boolean">
  Returns `true` if injection was successful
</ResponseField>

**Implementation:**

```java theme={null}
// From source (EventController.java:261-263)
private boolean injectEvent(InputEvent event) {
    return device.injectInputEvent(
        event, 
        InputManager.INJECT_INPUT_EVENT_MODE_ASYNC
    );
}
```

<Note>
  Events are injected asynchronously (`INJECT_INPUT_EVENT_MODE_ASYNC`) for better performance and to avoid blocking the control loop.
</Note>

## Coordinate Transformation

Touch coordinates are transformed from client screen space to device physical coordinates:

```java theme={null}
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:

```java theme={null}
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();
        }
    }
}
```

## Performance Considerations

<Warning>
  Event injection requires system-level permissions. The server must run with appropriate privileges to inject input events.
</Warning>

**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

* [Scrcpy Service](/api/scrcpy-service) - Client-side service
* [Video Decoder](/api/video-decoder) - Video stream decoding
* [Audio Decoder](/api/audio-decoder) - Audio stream decoding
