windows-input.mjs 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. import { execFile } from 'node:child_process'
  2. import { join } from 'node:path'
  3. export function windowsFrame(display, dimensions, physicalBounds) {
  4. return { display: display.id, ...dimensions, bounds: display.bounds, scaleFactor: display.scaleFactor, physicalBounds }
  5. }
  6. export function sameWindowsDisplay(frame, display, physicalBounds) {
  7. return frame?.display === display.id && frame.scaleFactor === display.scaleFactor
  8. && JSON.stringify(frame.bounds) === JSON.stringify(display.bounds)
  9. && JSON.stringify(frame.physicalBounds) === JSON.stringify(physicalBounds)
  10. }
  11. export class WindowsInput {
  12. constructor(root, packaged, execute = execFile) {
  13. this.helper = join(root, packaged ? 'computer-helper.exe' : 'computer-helper-dev.exe')
  14. this.execute = execute; this.controllers = new Set(); this.pending = new Set(); this.available = false
  15. }
  16. async call(value) {
  17. const controller = new AbortController()
  18. this.controllers.add(controller)
  19. let completed, closed
  20. const completion = new Promise(resolve => { completed = resolve })
  21. this.pending.add(completion)
  22. try {
  23. return await new Promise((resolve, reject) => {
  24. const child = this.execute(this.helper, [], { timeout: 10000, signal: controller.signal, windowsHide: true, maxBuffer: 65536 }, (error, stdout, stderr) => {
  25. if (error) { this.available = false; reject(new Error(String(stderr || '').trim() || 'Windows 电脑助手不可用,请检查桌面会话或重新安装客户端')); return }
  26. try { const result = JSON.parse(stdout); if (result.ok !== true) throw new Error(); resolve(result) }
  27. catch { this.available = false; reject(new Error('Windows 电脑助手响应无效')) }
  28. })
  29. closed = new Promise(resolve => child.once('close', resolve))
  30. child.stdin.on('error', () => {})
  31. child.stdin.end(JSON.stringify(value))
  32. })
  33. } finally {
  34. await closed
  35. this.controllers.delete(controller)
  36. // An aborted drag may have held the button when its process terminated.
  37. if (controller.signal.aborted && value.action?.kind === 'drag') await this.call({ operation: 'release-drag' }).catch(() => {})
  38. this.pending.delete(completion); completed()
  39. }
  40. }
  41. async probe() {
  42. try { await this.call({ operation: 'probe' }); this.available = true }
  43. catch { this.available = false }
  44. return this.available
  45. }
  46. cancel() { for (const controller of this.controllers) controller.abort(); this.available = false; return Promise.allSettled([...this.pending]) }
  47. }