#!/usr/bin/env node const { execSync } = require('child_process') const path = require('path') const config = require(path.join(__dirname, '..', '..', 'configs', 'config.js')) const projectRoot = path.resolve(__dirname, '..', '..') /** 从 config 解析并返回 adb 可执行文件路径 */ function getAdbPath() { return config.adbPath?.path ? path.resolve(projectRoot, config.adbPath.path) : path.join(projectRoot, 'lib', 'scrcpy-adb', 'adb.exe') } /** 根据 deviceId 返回 adb -s 参数前缀,无设备时返回空串 */ function getDeviceFlag(deviceId) { return deviceId && deviceId.includes(':') ? `-s ${deviceId} ` : '' } /** 执行 adb shell 命令 */ function runShell(adbPath, deviceFlag, shellArgs, timeout = 1000) { const quoted = adbPath.includes(' ') ? `"${adbPath}"` : adbPath execSync(`${quoted} ${deviceFlag}shell ${shellArgs}`, { encoding: 'utf-8', timeout }) } /** 执行 adb shell 并返回标准输出 */ function runShellWithOutput(adbPath, deviceFlag, shellArgs, timeout = 1000) { const quoted = adbPath.includes(' ') ? `"${adbPath}"` : adbPath return execSync(`${quoted} ${deviceFlag}shell ${shellArgs}`, { encoding: 'utf-8', timeout }) } /** 处理 tap:点击坐标 */ function handleTap(adbPath, argv) { const coordX = argv[3] const coordY = argv[4] const deviceId = argv[5] || '' if (!coordX || !coordY) process.exit(1) const flag = getDeviceFlag(deviceId) runShell(adbPath, flag, `input tap ${coordX} ${coordY}`, 1000) } /** 根据方向与距离计算滑动的起止坐标(基于屏幕中心),返回 [startX, startY, endX, endY] */ function getSwipeCoordsByDirection(direction, distance, centerX, centerY) { const half = Math.floor(distance / 2) switch (direction) { case 'up-down': return [centerX, centerY - half, centerX, centerY + half] case 'down-up': return [centerX, centerY + half, centerX, centerY - half] case 'left-right': return [centerX - half, centerY, centerX + half, centerY] case 'right-left': return [centerX + half, centerY, centerX - half, centerY] default: process.exit(1) } } /** 处理 swipe:按方向与距离从屏幕中心滑动 */ function handleSwipe(adbPath, argv) { const direction = argv[3] const distance = parseInt(argv[4], 10) const duration = parseInt(argv[5], 10) const deviceId = argv[6] || '' if (!direction || !distance || !duration) process.exit(1) const flag = getDeviceFlag(deviceId) const sizeOut = runShellWithOutput(adbPath, flag, 'wm size', 1000) const sizeMatch = sizeOut.match(/(\d+)x(\d+)/) const centerX = Math.floor(parseInt(sizeMatch[1], 10) / 2) const centerY = Math.floor(parseInt(sizeMatch[2], 10) / 2) const [startX, startY, endX, endY] = getSwipeCoordsByDirection(direction, distance, centerX, centerY) runShell(adbPath, flag, `input swipe ${startX} ${startY} ${endX} ${endY} ${duration}`, 2000) } /** 处理 swipe-coords:按坐标滑动 */ function handleSwipeCoords(adbPath, argv) { const x1 = argv[3] const y1 = argv[4] const x2 = argv[5] const y2 = argv[6] const duration = argv[7] || 300 const deviceId = argv[8] || '' if (!x1 || !y1 || !x2 || !y2) process.exit(1) const flag = getDeviceFlag(deviceId) runShell(adbPath, flag, `input swipe ${x1} ${y1} ${x2} ${y2} ${duration}`, 2000) } /** 处理 keyevent:按键 */ function handleKeyevent(adbPath, argv) { const keyCode = argv[3] const deviceId = argv[4] || '' if (!keyCode) process.exit(1) const flag = getDeviceFlag(deviceId) runShell(adbPath, flag, `input keyevent ${keyCode}`, 1000) } /** 处理 text:输入文本 */ function handleText(adbPath, argv) { const text = argv[3] const deviceId = argv[4] || '' if (text === undefined) process.exit(1) const flag = getDeviceFlag(deviceId) const escaped = String(text).replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`') runShell(adbPath, flag, `input text "${escaped}"`, 5000) } const action = process.argv[2] if (!action) process.exit(1) const adbPath = getAdbPath() switch (action) { case 'tap': handleTap(adbPath, process.argv) break case 'swipe': handleSwipe(adbPath, process.argv) break case 'swipe-coords': handleSwipeCoords(adbPath, process.argv) break case 'keyevent': handleKeyevent(adbPath, process.argv) break case 'text': handleText(adbPath, process.argv) break default: process.exit(1) } process.exit(0)