Skip to content

Commit 1116725

Browse files
committed
add a test click script
1 parent fa3e2a0 commit 1116725

File tree

2 files changed

+243
-3
lines changed

2 files changed

+243
-3
lines changed

src/adb/thirdparty/scrcpy/Scrcpy.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,15 @@ export default class Scrcpy extends EventEmitter {
772772
* see parseInjectTouchEvent()
773773
*/
774774
// usb.data_len == 28
775-
async injectTouchEvent(action: MotionEvent, pointerId: bigint, position: Point, screenSize: Point, pressure?: number): Promise<boolean> {
775+
async injectTouchEvent(
776+
action: MotionEvent,
777+
pointerId: bigint,
778+
position: Point,
779+
screenSize: Point,
780+
pressure: number = MotionEventMap.ACTION_UP,
781+
actionButton: number = MotionEventMap.BUTTON_PRIMARY,
782+
buttons: number = MotionEventMap.BUTTON_PRIMARY
783+
): Promise<boolean> {
776784
let size = 28;
777785
if (this.major >= 2) {
778786
size += 4;
@@ -797,10 +805,15 @@ export default class Scrcpy extends EventEmitter {
797805
chunk.writeUint16BE(screenSize.x | 0); // int screenWidth = dis.readUnsignedShort();
798806
chunk.writeUint16BE(screenSize.y | 0); // int screenHeight = dis.readUnsignedShort();
799807
chunk.writeUint16BE(pressure); // Binary.u16FixedPointToFloat(dis.readShort());
800-
chunk.writeUint32BE(MotionEventMap.BUTTON_PRIMARY); // int actionButton = dis.readInt();
808+
801809
if (this.major >= 2) {
802-
chunk.writeUint32BE(MotionEventMap.BUTTON_PRIMARY); // int buttons = dis.readInt();
810+
chunk.writeUint32BE(actionButton); // New: specific button for this action
803811
}
812+
chunk.writeUint32BE(buttons); // Old: just buttons state
813+
// chunk.writeUint32BE(MotionEventMap.BUTTON_PRIMARY); // int actionButton = dis.readInt();
814+
// if (this.major >= 2) {
815+
// chunk.writeUint32BE(MotionEventMap.BUTTON_PRIMARY); // int buttons = dis.readInt();
816+
// }
804817
assert(this.controlSocket);
805818
try {
806819
await this.controlSocket.write(chunk.buffer);

tasks/clickTest.ts

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import adb, { DeviceClient, Utils, MotionEventMap, Scrcpy } from "../src/index.js";
2+
import Parser from "../src/adb/parser.js";
3+
4+
const NB_CLICK = 1000000;
5+
6+
const Y_OFFSET = 0.02;//25;
7+
8+
async function* getCenterTaps(width: number, height: number, count: number, asPercent = false) {
9+
const centerX = asPercent ? width / 2 : Math.floor(width / 2);
10+
const centerY = asPercent ? height / 2 - height * Y_OFFSET : Math.floor(height / 2 - height * Y_OFFSET); // Move up by 20%
11+
const radius = Math.min(width, height) * 0.15; // 40% diameter = 20% radius
12+
13+
console.log(`Screen: ${width}x${height}, Center: ${centerX},${centerY}, Circle radius: ${radius}`);
14+
const STEPS = 16;
15+
for (let i = 0; i < count; i++) {
16+
const angle = (2 * Math.PI * (i % STEPS)) / STEPS;
17+
const x = asPercent ? centerX + radius * Math.cos(angle) : Math.floor(centerX + radius * Math.cos(angle));
18+
const y = asPercent ? centerY + radius * Math.sin(angle) : Math.floor(centerY + radius * Math.sin(angle));
19+
yield { x, y };
20+
// await Utils.delay(100);
21+
}
22+
}
23+
24+
25+
async function* getGridPercent(count: number) {
26+
const XGap = 0.02;
27+
const YGap = 0.35;
28+
const deltaX = 0.07;
29+
const deltaY = 0.07;
30+
let x = XGap;
31+
let y = YGap;
32+
for (let i = 0; i < count; i++) {
33+
x += deltaX;
34+
if (x > 1 - XGap) {
35+
x = XGap;
36+
y += deltaY;
37+
if (y > 1 - YGap) {
38+
y = YGap;
39+
}
40+
}
41+
yield { x, y };
42+
// await Utils.delay(100);
43+
}
44+
}
45+
46+
47+
48+
async function* getCenterTapsForDevice(deviceClient: DeviceClient, count: number, asPercent = false) {
49+
if (asPercent) {
50+
// Use normalized coordinates (0-1) without retrieving actual screen size
51+
for await (const pos of getCenterTaps(1, 1, count, true)) {
52+
yield pos;
53+
}
54+
} else {
55+
const screenInfo = await deviceClient.execOut("wm size", "utf8");
56+
const match = screenInfo.match(/Physical size: (\d+)x(\d+)/);
57+
if (!match) {
58+
throw new Error("Could not parse screen dimensions");
59+
}
60+
const width = parseInt(match[1]);
61+
const height = parseInt(match[2]);
62+
for await (const pos of getCenterTaps(width, height, count, false)) {
63+
yield pos;
64+
}
65+
}
66+
}
67+
68+
// async function* getFixedCenterForDevice(deviceClient: DeviceClient, count: number) {
69+
// const screenInfo = await deviceClient.execOut("wm size", "utf8");
70+
// const match = screenInfo.match(/Physical size: (\d+)x(\d+)/);
71+
// if (!match) {
72+
// throw new Error("Could not parse screen dimensions");
73+
// }
74+
// const width = parseInt(match[1]);
75+
// const height = parseInt(match[2]);
76+
// const centerX = Math.floor(width / 2);
77+
// const centerY = Math.floor(height / 2 - height * Y_OFFSET); // Move up by 20%
78+
// // const centerY = Math.floor(height / 2);
79+
//
80+
// console.log(`Screen: ${width}x${height}, Fixed center: ${centerX},${centerY}`);
81+
//
82+
// for (let i = 0; i < count; i++) {
83+
// yield { x: centerX, y: centerY };
84+
// }
85+
// }
86+
87+
const testClickExec = async (deviceClient: DeviceClient) => {
88+
try {
89+
for await (const pos of getCenterTapsForDevice(deviceClient, NB_CLICK)) {
90+
await deviceClient.execOut(`input tap ${pos.x} ${pos.y}`, "utf8");
91+
}
92+
} catch (e) {
93+
console.error("Impossible to start", e);
94+
}
95+
};
96+
97+
const testClickShell = async (deviceClient: DeviceClient, coordinateGenerator: AsyncGenerator<{ x: number, y: number }>) => {
98+
try {
99+
while (true) {
100+
const result1 = await coordinateGenerator.next();
101+
if (result1.done) break;
102+
103+
const result2 = await coordinateGenerator.next();
104+
if (result2.done) break;
105+
106+
const pos1 = result1.value;
107+
const pos2 = result2.value;
108+
109+
// const duplex = await deviceClient.shell(`input multitap 2 ${pos1.x} ${pos1.y} ${pos2.x} ${pos2.y}`);
110+
const duplex = await deviceClient.shell(`input tap ${pos1.x} ${pos1.y}`);
111+
await new Parser(duplex).readAll();
112+
}
113+
console.log("All shell taps completed");
114+
} catch (e) {
115+
console.error("Shell command failed", e);
116+
}
117+
};
118+
119+
const testClickScrcpy = async (deviceClient: DeviceClient, coordinateGenerator: AsyncGenerator<{ x: number, y: number }>) => {
120+
let scrcpy: Scrcpy | undefined;
121+
try {
122+
while (!scrcpy) {
123+
try {
124+
scrcpy = deviceClient.scrcpy({});
125+
await Utils.delay(1);
126+
await scrcpy.start();
127+
await scrcpy.width;
128+
} catch (e) {
129+
console.error('Scrcpy start failed, retrying...', e);
130+
await Utils.delay(1000);
131+
scrcpy = undefined;
132+
}
133+
}
134+
// const scrcpy
135+
// = deviceClient.scrcpy({});
136+
// await Utils.delay(1);
137+
// await scrcpy.start();
138+
await Utils.delay(1);
139+
const width = await scrcpy.width;
140+
const height = await scrcpy.height;
141+
const screenSize = { x: width, y: height };
142+
console.log('Scrcpy started');
143+
144+
// Get screen dimensions
145+
// const screenInfo = await deviceClient.execOut("wm size", "utf8");
146+
// const match = screenInfo.match(/Physical size: (\d+)x(\d+)/);
147+
// if (!match) {
148+
// throw new Error("Could not parse screen dimensions");
149+
// }
150+
// const screenSize = { x: parseInt(match[1]), y: parseInt(match[1]) };
151+
152+
for await (const tap of coordinateGenerator) {
153+
// Convert percentage coordinates (0-1) to absolute pixels if needed
154+
const position = {
155+
x: tap.x <= 1 ? Math.floor(tap.x * width) : tap.x,
156+
y: tap.y <= 1 ? Math.floor(tap.y * height) : tap.y
157+
};
158+
// Send ACTION_DOWN followed by ACTION_UP for each tap
159+
// MotionEvent.ACTION_DOWN
160+
await scrcpy.injectTouchEvent(MotionEventMap.ACTION_DOWN, 0n, position, screenSize, undefined, 0, MotionEventMap.BUTTON_PRIMARY);
161+
await Utils.delay(8);
162+
//await scrcpy.injectTouchEvent(MotionEventMap.ACTION_UP, 0n, position, screenSize, undefined, 0, 0);
163+
//await Utils.delay(1);
164+
}
165+
console.log('All scrcpy taps completed');
166+
} catch (e) {
167+
console.error('Scrcpy command failed', e);
168+
} finally {
169+
}
170+
if (scrcpy)
171+
scrcpy.stop();
172+
};
173+
174+
175+
const main = async () => {
176+
// process.env.DEBUG = '*';
177+
const adbClient = adb.createClient();
178+
const devices = await adbClient.listDevices();
179+
if (!devices.length) {
180+
console.error("Need at least one connected android device");
181+
return;
182+
}
183+
const deviceClient = devices[0].getClient();
184+
185+
// {
186+
// let t1 = Date.now();
187+
// await testClickExec(deviceClient);
188+
// t1 = Date.now() - t1;
189+
// console.log(`execOut = ${t1 / NB_CLICK}`); // 50.146 ms
190+
// }
191+
//
192+
if (false) {
193+
const promises: Promise<unknown>[] = [];
194+
let t2 = Date.now();
195+
promises.push(testClickShell(deviceClient, getCenterTapsForDevice(deviceClient, NB_CLICK)));
196+
//promises.push(testClickShell(deviceClient, getFixedCenterForDevice(deviceClient, NB_CLICK));
197+
await Promise.all(promises);
198+
t2 = Date.now() - t2;
199+
console.log(`shell = ${t2 / NB_CLICK}`); // 44.815 ms
200+
}
201+
202+
if (true) {
203+
let t3 = Date.now();
204+
// await testClickScrcpy(deviceClient, getCenterTapsForDevice(deviceClient, NB_CLICK, true));
205+
await testClickScrcpy(deviceClient, getGridPercent(NB_CLICK));
206+
t3 = Date.now() - t3;
207+
console.log(`scrcpy = ${t3 / NB_CLICK}`);
208+
}
209+
210+
console.log(`Done`);
211+
};
212+
213+
process.on("unhandledRejection", (reason, promise) => {
214+
debugger;
215+
console.error("Unhandled Rejection at:", promise, "reason:", reason);
216+
});
217+
218+
process.on(
219+
"exit",
220+
(code) => console.log("Processus is closing, exit code:", code),
221+
);
222+
process.on("SIGINT", () => console.log("SIGINT reçu"));
223+
process.on("SIGTERM", () => console.log("SIGTERM reçu"));
224+
225+
main().catch((e) => console.error("ERROR", e)).finally(() => {
226+
console.log("Processus terminé");
227+
});

0 commit comments

Comments
 (0)