screenshot.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. const { spawn } = require('child_process')
  2. const { execSync } = require('child_process')
  3. const path = require('path')
  4. const fs = require('fs')
  5. const configPath = process.env.STATIC_ROOT
  6. ? path.join(path.dirname(process.env.STATIC_ROOT), 'config.js')
  7. : path.join(__dirname, '..', '..', 'config.js')
  8. const projectRoot = path.dirname(path.resolve(configPath))
  9. const config = fs.existsSync(configPath) ? require(configPath) : {}
  10. const adbPath = config.adbPath?.path
  11. ? (path.isAbsolute(config.adbPath.path) ? config.adbPath.path : path.resolve(projectRoot, config.adbPath.path))
  12. : path.join(projectRoot, 'lib', 'scrcpy-adb', process.platform === 'win32' ? 'adb.exe' : 'adb')
  13. const scrcpyDir = path.dirname(adbPath)
  14. const scrcpyPath = path.join(scrcpyDir, 'scrcpy.exe')
  15. const staticRoot = process.env.STATIC_ROOT ? path.resolve(process.env.STATIC_ROOT) : path.join(projectRoot, 'static')
  16. const pidFile = path.join(staticRoot, 'scrcpy-pid.json')
  17. const action = process.argv[2]
  18. const ipArg = process.argv[3]
  19. const portArg = process.argv[4]
  20. // 支持两种传参:1) ip, port 分开 2) ip:port 合并(无默认 port,只用传入值)
  21. let ip, port
  22. if (portArg !== undefined && portArg !== '') {
  23. ip = ipArg
  24. port = portArg
  25. } else if (ipArg && String(ipArg).includes(':')) {
  26. const parts = String(ipArg).split(':')
  27. ip = parts[0] || ''
  28. port = parts[1] || ''
  29. } else {
  30. ip = ipArg || ''
  31. port = portArg || ''
  32. }
  33. /** 终止指定 PID 的进程 */
  34. function killPidIfRunning(pid) {
  35. spawn('taskkill.exe', ['/F', '/PID', pid.toString()], {
  36. stdio: 'ignore',
  37. detached: true
  38. }).unref()
  39. }
  40. /** 从 tasklist 获取 scrcpy.exe 的 PID(用 bat start 启动后需轮询得到 pid) */
  41. function getScrcpyPid() {
  42. const output = execSync('tasklist /FI "IMAGENAME eq scrcpy.exe" /FO CSV', { encoding: 'utf-8' })
  43. const lines = output.split('\n').filter(line => line.includes('scrcpy.exe'))
  44. if (lines.length === 0) return null
  45. const pidMatch = lines[0].match(/"(\d+)"/)
  46. return pidMatch ? parseInt(pidMatch[1]) : null
  47. }
  48. /** 判断是否为 IP(或 IP:port),用于无线设备选择器 */
  49. function isDeviceIp(val) {
  50. return val && !/^\d+$/.test(val) && /[\d.]/.test(val)
  51. }
  52. /** 启动 scrcpy:先 adb connect(若有 IP),再用 scrcpy-noconsole.bat(start "" scrcpy.exe %*)无控制台启动 */
  53. function startScrcpy() {
  54. if (fs.existsSync(pidFile)) {
  55. const { pid } = JSON.parse(fs.readFileSync(pidFile, 'utf-8'))
  56. killPidIfRunning(pid)
  57. }
  58. let deviceSelector = ''
  59. if (ip && port && isDeviceIp(ip)) {
  60. deviceSelector = `${ip}:${port}`
  61. execSync(`"${adbPath}" connect ${deviceSelector}`, { encoding: 'utf-8', cwd: scrcpyDir })
  62. }
  63. const serial = deviceSelector
  64. const args = serial
  65. ? ['--pause-on-exit=if-error', '-s', serial]
  66. : ['-e', '--pause-on-exit=if-error']
  67. const argsStr = args.join(' ')
  68. const vbsPath = path.join(scrcpyDir, 'scrcpy-noconsole.vbs')
  69. execSync(`wscript "${vbsPath.replace(/"/g, '""')}" ${argsStr}`, { cwd: scrcpyDir })
  70. const sleep = (ms) => { const t = Date.now(); while (Date.now() - t < ms) {} }
  71. sleep(1500)
  72. const runningPid = getScrcpyPid()
  73. if (!runningPid) {
  74. console.log(JSON.stringify({ success: false, error: 'scrcpy did not appear in tasklist' }))
  75. process.exit(1)
  76. }
  77. fs.writeFileSync(pidFile, JSON.stringify({ pid: runningPid }), 'utf-8')
  78. console.log(JSON.stringify({ success: true, pid: runningPid }))
  79. process.exit(0)
  80. }
  81. /** 停止 scrcpy:有 pid 则 taskkill 并删文件,返回 action: 'stop';无 pid 文件返回 action: 'none',供前端区分是否继续执行 start */
  82. function stopScrcpy() {
  83. if (!fs.existsSync(pidFile)) {
  84. console.log(JSON.stringify({ success: true, action: 'none' }))
  85. process.exit(0)
  86. return
  87. }
  88. const pid = JSON.parse(fs.readFileSync(pidFile, 'utf-8')).pid
  89. killPidIfRunning(pid)
  90. fs.unlinkSync(pidFile)
  91. console.log(JSON.stringify({ success: true, action: 'stop' }))
  92. process.exit(0)
  93. }
  94. switch (action) {
  95. case 'start':
  96. startScrcpy()
  97. break
  98. case 'stop':
  99. stopScrcpy()
  100. break
  101. default:
  102. console.log(JSON.stringify({
  103. success: false,
  104. error: 'Usage: node screenshot.js [start|stop] [ip] [port]'
  105. }))
  106. process.exit(1)
  107. }