Нема описа
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. // OS input injection layer.
  2. //
  3. // Cross-platform mouse/keyboard control via @nut-tree-fork/nut-js (optional
  4. // native dependency). If nut-js isn't installed (e.g. CI, or a sandbox without
  5. // a display), this module degrades to a logging no-op so the rest of the agent
  6. // still runs and can be tested. On Windows, nut-js drives the Win32 SendInput
  7. // API under the hood — the same mechanism TeamViewer/AnyDesk use.
  8. let nut = null;
  9. try {
  10. // eslint-disable-next-line import/no-extraneous-dependencies
  11. nut = require('@nut-tree-fork/nut-js');
  12. nut.mouse.config.autoDelayMs = 0;
  13. nut.keyboard.config.autoDelayMs = 0;
  14. } catch {
  15. nut = null;
  16. }
  17. const available = !!nut;
  18. // Map browser KeyboardEvent.key values to nut-js Key enum names.
  19. function mapKey(key, code) {
  20. if (!nut) return null;
  21. const K = nut.Key;
  22. const direct = {
  23. 'Enter': K.Enter, 'Backspace': K.Backspace, 'Tab': K.Tab, 'Escape': K.Escape,
  24. ' ': K.Space, 'ArrowLeft': K.Left, 'ArrowRight': K.Right, 'ArrowUp': K.Up, 'ArrowDown': K.Down,
  25. 'Home': K.Home, 'End': K.End, 'PageUp': K.PageUp, 'PageDown': K.PageDown, 'Delete': K.Delete,
  26. 'Control': K.LeftControl, 'Shift': K.LeftShift, 'Alt': K.LeftAlt, 'Meta': K.LeftSuper,
  27. 'CapsLock': K.CapsLock,
  28. };
  29. if (direct[key] !== undefined) return [direct[key]];
  30. if (/^F\d{1,2}$/.test(key) && K[key] !== undefined) return [K[key]];
  31. if (key && key.length === 1) {
  32. const upper = key.toUpperCase();
  33. if (/[A-Z]/.test(upper) && K[upper] !== undefined) return [K[upper]];
  34. if (/[0-9]/.test(key) && K['Num' + key] !== undefined) return [K['Num' + key]];
  35. // Fall back to typing the literal character (handles symbols/shifted chars)
  36. return { type: key };
  37. }
  38. return null;
  39. }
  40. async function moveTo(xNorm, yNorm) {
  41. if (!nut) return;
  42. const { width, height } = await nut.screen.getResolution();
  43. await nut.mouse.setPosition(new nut.Point(Math.round(xNorm * width), Math.round(yNorm * height)));
  44. }
  45. function buttonEnum(b) {
  46. if (!nut) return null;
  47. return b === 2 ? nut.Button.RIGHT : b === 1 ? nut.Button.MIDDLE : nut.Button.LEFT;
  48. }
  49. const pressed = new Set();
  50. // Inject a single normalized input event coming from the viewer.
  51. async function inject(evt) {
  52. if (!nut) {
  53. if (evt.kind !== 'mousemove') console.log('[input:noop]', JSON.stringify(evt));
  54. return;
  55. }
  56. try {
  57. switch (evt.kind) {
  58. case 'mousemove':
  59. await moveTo(evt.x, evt.y); break;
  60. case 'mousedown':
  61. await moveTo(evt.x, evt.y); await nut.mouse.pressButton(buttonEnum(evt.button)); break;
  62. case 'mouseup':
  63. await nut.mouse.releaseButton(buttonEnum(evt.button)); break;
  64. case 'dblclick':
  65. await moveTo(evt.x, evt.y); await nut.mouse.doubleClick(nut.Button.LEFT); break;
  66. case 'scroll':
  67. if (evt.dy) await (evt.dy > 0 ? nut.mouse.scrollDown(Math.abs(evt.dy)) : nut.mouse.scrollUp(Math.abs(evt.dy)));
  68. if (evt.dx) await (evt.dx > 0 ? nut.mouse.scrollRight(Math.abs(evt.dx)) : nut.mouse.scrollLeft(Math.abs(evt.dx)));
  69. break;
  70. case 'keydown': {
  71. const m = mapKey(evt.key, evt.code);
  72. if (!m) break;
  73. if (m.type) { await nut.keyboard.type(m.type); break; }
  74. await nut.keyboard.pressKey(...m); m.forEach((k) => pressed.add(k));
  75. break;
  76. }
  77. case 'keyup': {
  78. const m = mapKey(evt.key, evt.code);
  79. if (!m || m.type) break;
  80. await nut.keyboard.releaseKey(...m); m.forEach((k) => pressed.delete(k));
  81. break;
  82. }
  83. }
  84. } catch (e) {
  85. console.error('[input] inject error:', e.message);
  86. }
  87. }
  88. // Safety: release any stuck modifier keys when a session ends.
  89. async function releaseAll() {
  90. if (!nut) { pressed.clear(); return; }
  91. for (const k of pressed) { try { await nut.keyboard.releaseKey(k); } catch {} }
  92. pressed.clear();
  93. }
  94. module.exports = { inject, releaseAll, available, mapKey };