Skip to main content
Version: 1.1.0

Touch event

Inject a touch event into the device.

Both mouse and touch events are supported. Multiple fingers are also supported.

Options

const ScrcpyPointerId = {
Mouse: -1n,
Finger: -2n,
VirtualMouse: -3n,
VirtualFinger: -4n,
} as const;

interface ScrcpyInjectTouchControlMessage {
action: AndroidMotionEventAction;
pointerId: bigint;
pointerX: number;
pointerY: number;
screenWidth: number;
screenHeight: number;
pressure: number;
actionButton: number;
buttons: number;
}
  • action: The action of the event.
  • pointerId: The ID of the pointer. It can be one of the predefined values in ScrcpyPointerId, or any non-negative bigints for each individual finger.
  • pointerX: The X coordinate of the event.
  • pointerY: The Y coordinate of the event.
  • actionButton: The button that changed. It's a value of AndroidMotionEventButton enum.
  • buttons: The state of all the buttons. It's a bit-or combination of AndroidMotionEventButton values.

screenWidth and screenHeight

The size of latest video frame.

The server will validate these values, and ignore the command if they don't match. This is to prevent invalid pointerX and pointerY values from being sent to the device, for example when device orientation is changed.

When using AdbScrcpyClient, it will parse and save the size (if video stream is enabled):

import type { AdbScrcpyClient } from "@yume-chan/adb-scrcpy";

declare const client: AdbScrcpyClient;

console.log(client.screenWidth, client.screenHeight);

When using one of the built-in video decoders, the sizeChanged event can be used to get the latest screenWidth and screenHeight values.

import type { ScrcpyVideoDecoder } from "@yume-chan/scrcpy-decoder-tinyh264";

let screenWidth = 0;
let screenHeight = 0;

declare const decoder: ScrcpyVideoDecoder;

decoder.sizeChanged(({ width, height }) => {
screenWidth = width;
screenHeight = height;
});

Usage

import {
AndroidMotionEventAction,
AndroidMotionEventButton,
ScrcpyPointerId,
} from "@yume-chan/scrcpy";

// Using `ScrcpyControlMessageSerializer`
const message: Uint8Array = serializer.injectTouch({
action: AndroidMotionEventAction.Down,
pointerId: ScrcpyPointerId.Finger,
pointerX: 100,
pointerY: 200,
screenWidth: 1920,
screenHeight: 1080,
pressure: 0.5,
actionButton: AndroidMotionEventButton.Primary,
buttons: AndroidMotionEventButton.Primary,
});

// Using `ScrcpyControlMessageWriter`
await writer.injectTouch({
action: AndroidMotionEventAction.Down,
pointerId: ScrcpyPointerId.Finger,
pointerX: 100,
pointerY: 200,
screenWidth: 1920,
screenHeight: 1080,
pressure: 0.5,
actionButton: AndroidMotionEventButton.Primary,
buttons: AndroidMotionEventButton.Primary,
});

// Using `AdbScrcpyClient`
await client.controller!.injectTouch({
action: AndroidMotionEventAction.Down,
pointerId: ScrcpyPointerId.Finger,
pointerX: 100,
pointerY: 200,
screenWidth: 1920,
screenHeight: 1080,
pressure: 0.5,
actionButton: AndroidMotionEventButton.Primary,
buttons: AndroidMotionEventButton.Primary,
});

DOM Events Example

The pointerdown, pointermove and pointerup events can be mapped to Scrcpy touch events.

import type { ScrcpyControlMessageSerializer } from "@yume-chan/scrcpy";
import { AndroidMotionEventAction, clamp } from "@yume-chan/scrcpy";

declare const serializer: ScrcpyControlMessageSerializer; // ScrcpyControlMessageWriter` and `AdbScrcpyClient` can also be used
declare let screenWidth: number;
declare let screenHeight: number;

const PointerEventButtonToAndroidButton = [
AndroidMotionEventButton.Primary,
AndroidMotionEventButton.Tertiary,
AndroidMotionEventButton.Secondary,
AndroidMotionEventButton.Back,
AndroidMotionEventButton.Forward,
];

function handlePointerEvent(event: PointerEvent) {
e.preventDefault();
e.stopPropagation();

const target = e.currentTarget as HTMLElement;
target.setPointerCapture(e.pointerId);

const { type, clientX, clientY, button, buttons } = e;

let action: AndroidMotionEventAction;
switch (type) {
case "pointerdown":
action = AndroidMotionEventAction.Down;
break;
case "pointermove":
if (buttons === 0) {
action = AndroidMotionEventAction.HoverMove;
} else {
action = AndroidMotionEventAction.Move;
}
break;
case "pointerup":
action = AndroidMotionEventAction.Up;
break;
default:
throw new Error(`Unsupported event type: ${type}`);
}

const rect = target.getBoundingClientRect();
const percentageX = clamp((clientX - rect.x) / rect.width, 0, 1);
const percentageY = clamp((clientY - rect.y) / rect.height, 0, 1);

const pointerX = percentageX * screenWidth;
const pointerY = percentageY * screenHeight;

return serializer.injectTouch({
action,
pointerId: BigInt(e.pointerId),
pointerX,
pointerY,
screenWidth,
screenHeight,
pressure: buttons === 0 ? 0 : 1,
actionButton: PointerEventButtonToAndroidButton[button],
buttons,
});
}