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 inScrcpyPointerId
, 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 ofAndroidMotionEventButton
enum.buttons
: The state of all the buttons. It's a bit-or combination ofAndroidMotionEventButton
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):
- JavaScript
- TypeScript
console.log(client.screenWidth, client.screenHeight);
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.
- JavaScript
- TypeScript
let screenWidth = 0;
let screenHeight = 0;
decoder.sizeChanged(({ width, height }) => {
screenWidth = width;
screenHeight = height;
});
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
- JavaScript
- TypeScript
import {
AndroidMotionEventAction,
AndroidMotionEventButton,
ScrcpyPointerId,
} from "@yume-chan/scrcpy";
// Using `ScrcpyControlMessageSerializer`
const message = 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,
});
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.
- JavaScript
- TypeScript
import { AndroidMotionEventAction, clamp } from "@yume-chan/scrcpy";
const PointerEventButtonToAndroidButton = [
AndroidMotionEventButton.Primary,
AndroidMotionEventButton.Tertiary,
AndroidMotionEventButton.Secondary,
AndroidMotionEventButton.Back,
AndroidMotionEventButton.Forward,
];
function handlePointerEvent(event) {
e.preventDefault();
e.stopPropagation();
const target = e.currentTarget;
target.setPointerCapture(e.pointerId);
const { type, clientX, clientY, button, buttons } = e;
let action;
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,
});
}
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,
});
}