#!/usr/bin/env node const { execSync, spawnSync } = require('child_process') const path = require('path') const fs = require('fs') const configPath = process.env.STATIC_ROOT ? path.join(path.dirname(process.env.STATIC_ROOT), 'config.js') : path.join(__dirname, '..', '..', 'config.js') const projectRoot = path.dirname(path.resolve(configPath)) const config = fs.existsSync(configPath) ? require(configPath) : {} /** 从 config 解析并返回 adb 可执行文件路径 */ function getAdbPath() { const p = config.adbPath?.path if (p) return path.isAbsolute(p) ? p : path.resolve(projectRoot, p) return path.join(projectRoot, 'lib', 'scrcpy-adb', process.platform === 'win32' ? 'adb.exe' : 'adb') } /** 根据 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:点击坐标(超时放宽以便 WiFi 连接时能完成) */ 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}`, 8000) } /** 根据方向与距离计算滑动的起止坐标(基于屏幕中心),返回 [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) } /** 简单 input text(供 type:input 等调用;adb method:input 的完整逻辑在 ef-compiler/actions/adb/input.js) */ function handleText(adbPath, argv) { const text = argv[3] const deviceId = argv[4] || '' if (text === undefined) process.exit(1) const str = String(text) const shellArgs = deviceId && deviceId.includes(':') ? ['-s', deviceId, 'shell', 'input', 'text', str] : ['shell', 'input', 'text', str] const r = spawnSync(adbPath, shellArgs, { encoding: 'utf-8', timeout: 5000 }) if (r.status !== 0) { const err = new Error(r.stderr || r.stdout || 'input text failed') err.status = r.status throw err } } 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)