const { spawn } = require('child_process') const { execSync } = require('child_process') const path = require('path') const fs = require('fs') const config = require(path.join(__dirname, '..', '..', 'configs', 'config.js')) let projectRoot = path.resolve(__dirname, '..', '..') if (projectRoot.includes('app.asar') && !projectRoot.includes('app.asar.unpacked')) { projectRoot = projectRoot.replace('app.asar', 'app.asar.unpacked') } const adbPath = path.resolve(projectRoot, config.adbPath.path) const scrcpyDir = path.dirname(adbPath) const scrcpyPath = path.join(scrcpyDir, 'scrcpy.exe') const pidFile = path.join(projectRoot, 'static', 'scrcpy-pid.json') const action = process.argv[2] const pidArg = process.argv[3] /** 终止指定 PID 的进程 */ function killPidIfRunning(pid) { spawn('taskkill.exe', ['/F', '/PID', pid.toString()], { stdio: 'ignore', detached: true }).unref() } /** 从 tasklist 获取 scrcpy.exe 的 PID(用 bat start 启动后需轮询得到 pid) */ function getScrcpyPid() { const output = execSync('tasklist /FI "IMAGENAME eq scrcpy.exe" /FO CSV', { encoding: 'utf-8' }) const lines = output.split('\n').filter(line => line.includes('scrcpy.exe')) if (lines.length === 0) return null const pidMatch = lines[0].match(/"(\d+)"/) return pidMatch ? parseInt(pidMatch[1]) : null } /** 判断是否为 IP(或 IP:port),用于无线设备选择器 */ function isDeviceIp(val) { return val && !/^\d+$/.test(val) && /[\d.]/.test(val) } /** 根据传入的 IP 得到 adb/scrcpy 设备选择器,无线默认 5555 */ function toDeviceSelector(ipOrSelector) { return ipOrSelector.includes(':') ? ipOrSelector : `${ipOrSelector}:5555` } /** 启动 scrcpy:先 adb connect(若有 IP),再用 scrcpy-noconsole.bat(start "" scrcpy.exe %*)无控制台启动 */ function startScrcpy() { if (fs.existsSync(pidFile)) { const { pid } = JSON.parse(fs.readFileSync(pidFile, 'utf-8')) killPidIfRunning(pid) } let deviceSelector = '' if (isDeviceIp(pidArg)) { deviceSelector = toDeviceSelector(pidArg) execSync(`"${adbPath}" connect ${deviceSelector}`, { encoding: 'utf-8', cwd: scrcpyDir }) } const serial = deviceSelector || (pidArg && !/^\d+$/.test(pidArg) ? pidArg : '') const args = serial ? ['--pause-on-exit=if-error', '-s', serial] : ['-e', '--pause-on-exit=if-error'] const argsStr = args.join(' ') const vbsPath = path.join(scrcpyDir, 'scrcpy-noconsole.vbs') execSync(`wscript "${vbsPath.replace(/"/g, '""')}" ${argsStr}`, { cwd: scrcpyDir }) const sleep = (ms) => { const t = Date.now(); while (Date.now() - t < ms) {} } sleep(1500) const runningPid = getScrcpyPid() if (!runningPid) { console.log(JSON.stringify({ success: false, error: 'scrcpy did not appear in tasklist' })) process.exit(1) } fs.writeFileSync(pidFile, JSON.stringify({ pid: runningPid }), 'utf-8') console.log(JSON.stringify({ success: true, pid: runningPid })) process.exit(0) } /** 停止 scrcpy:有 pid 则 taskkill 并删文件,返回 action: 'stop';无 pid 文件返回 action: 'none',供前端区分是否继续执行 start */ function stopScrcpy() { if (!fs.existsSync(pidFile)) { console.log(JSON.stringify({ success: true, action: 'none' })) process.exit(0) return } const pid = JSON.parse(fs.readFileSync(pidFile, 'utf-8')).pid killPidIfRunning(pid) fs.unlinkSync(pidFile) console.log(JSON.stringify({ success: true, action: 'stop' })) process.exit(0) } switch (action) { case 'start': startScrcpy() break case 'stop': stopScrcpy() break default: console.log(JSON.stringify({ success: false, error: 'Usage: node screenshot.js [start|stop] [pid|deviceIp]' })) process.exit(1) }