atspi.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
  2. //
  3. // SPDX-License-Identifier: GPL-2.0-or-later
  4. 'use strict';
  5. imports.gi.versions.Atspi = '2.0';
  6. const Atspi = imports.gi.Atspi;
  7. const Gdk = imports.gi.Gdk;
  8. /**
  9. * Printable ASCII range
  10. */
  11. const _ASCII = /[\x20-\x7E]/;
  12. /**
  13. * Modifier Keycode Defaults
  14. */
  15. const XKeycode = {
  16. Alt_L: 0x40,
  17. Control_L: 0x25,
  18. Shift_L: 0x32,
  19. Super_L: 0x85,
  20. };
  21. /**
  22. * A thin wrapper around Atspi for X11 sessions without Pipewire support.
  23. */
  24. var Controller = class {
  25. constructor() {
  26. // Atspi.init() return 2 on fail, but still marks itself as inited. We
  27. // uninit before throwing an error otherwise any future call to init()
  28. // will appear successful and other calls will cause GSConnect to exit.
  29. // See: https://gitlab.gnome.org/GNOME/at-spi2-core/blob/master/atspi/atspi-misc.c
  30. if (Atspi.init() === 2) {
  31. this.destroy();
  32. throw new Error('Failed to start AT-SPI');
  33. }
  34. try {
  35. this._display = Gdk.Display.get_default();
  36. this._seat = this._display.get_default_seat();
  37. this._pointer = this._seat.get_pointer();
  38. } catch (e) {
  39. this.destroy();
  40. throw e;
  41. }
  42. // Try to read modifier keycodes from Gdk
  43. try {
  44. const keymap = Gdk.Keymap.get_for_display(this._display);
  45. let modifier;
  46. modifier = keymap.get_entries_for_keyval(Gdk.KEY_Alt_L)[1][0];
  47. XKeycode.Alt_L = modifier.keycode;
  48. modifier = keymap.get_entries_for_keyval(Gdk.KEY_Control_L)[1][0];
  49. XKeycode.Control_L = modifier.keycode;
  50. modifier = keymap.get_entries_for_keyval(Gdk.KEY_Shift_L)[1][0];
  51. XKeycode.Shift_L = modifier.keycode;
  52. modifier = keymap.get_entries_for_keyval(Gdk.KEY_Super_L)[1][0];
  53. XKeycode.Super_L = modifier.keycode;
  54. } catch (e) {
  55. debug('using default modifier keycodes');
  56. }
  57. }
  58. /*
  59. * Pointer events
  60. */
  61. clickPointer(button) {
  62. try {
  63. const [, x, y] = this._pointer.get_position();
  64. const monitor = this._display.get_monitor_at_point(x, y);
  65. const scale = monitor.get_scale_factor();
  66. Atspi.generate_mouse_event(scale * x, scale * y, `b${button}c`);
  67. } catch (e) {
  68. logError(e);
  69. }
  70. }
  71. doubleclickPointer(button) {
  72. try {
  73. const [, x, y] = this._pointer.get_position();
  74. const monitor = this._display.get_monitor_at_point(x, y);
  75. const scale = monitor.get_scale_factor();
  76. Atspi.generate_mouse_event(scale * x, scale * y, `b${button}d`);
  77. } catch (e) {
  78. logError(e);
  79. }
  80. }
  81. movePointer(dx, dy) {
  82. try {
  83. const [, x, y] = this._pointer.get_position();
  84. const monitor = this._display.get_monitor_at_point(x, y);
  85. const scale = monitor.get_scale_factor();
  86. Atspi.generate_mouse_event(scale * dx, scale * dy, 'rel');
  87. } catch (e) {
  88. logError(e);
  89. }
  90. }
  91. pressPointer(button) {
  92. try {
  93. const [, x, y] = this._pointer.get_position();
  94. const monitor = this._display.get_monitor_at_point(x, y);
  95. const scale = monitor.get_scale_factor();
  96. Atspi.generate_mouse_event(scale * x, scale * y, `b${button}p`);
  97. } catch (e) {
  98. logError(e);
  99. }
  100. }
  101. releasePointer(button) {
  102. try {
  103. const [, x, y] = this._pointer.get_position();
  104. const monitor = this._display.get_monitor_at_point(x, y);
  105. const scale = monitor.get_scale_factor();
  106. Atspi.generate_mouse_event(scale * x, scale * y, `b${button}r`);
  107. } catch (e) {
  108. logError(e);
  109. }
  110. }
  111. scrollPointer(dx, dy) {
  112. if (dy > 0)
  113. this.clickPointer(4);
  114. else if (dy < 0)
  115. this.clickPointer(5);
  116. }
  117. /*
  118. * Phony virtual keyboard helpers
  119. */
  120. _modeLock(keycode) {
  121. Atspi.generate_keyboard_event(
  122. keycode,
  123. null,
  124. Atspi.KeySynthType.PRESS
  125. );
  126. }
  127. _modeUnlock(keycode) {
  128. Atspi.generate_keyboard_event(
  129. keycode,
  130. null,
  131. Atspi.KeySynthType.RELEASE
  132. );
  133. }
  134. /*
  135. * Simulate a printable-ASCII character.
  136. *
  137. */
  138. _pressASCII(key, modifiers) {
  139. try {
  140. // Press Modifiers
  141. if (modifiers & Gdk.ModifierType.MOD1_MASK)
  142. this._modeLock(XKeycode.Alt_L);
  143. if (modifiers & Gdk.ModifierType.CONTROL_MASK)
  144. this._modeLock(XKeycode.Control_L);
  145. if (modifiers & Gdk.ModifierType.SHIFT_MASK)
  146. this._modeLock(XKeycode.Shift_L);
  147. if (modifiers & Gdk.ModifierType.SUPER_MASK)
  148. this._modeLock(XKeycode.Super_L);
  149. Atspi.generate_keyboard_event(
  150. 0,
  151. key,
  152. Atspi.KeySynthType.STRING
  153. );
  154. // Release Modifiers
  155. if (modifiers & Gdk.ModifierType.MOD1_MASK)
  156. this._modeUnlock(XKeycode.Alt_L);
  157. if (modifiers & Gdk.ModifierType.CONTROL_MASK)
  158. this._modeUnlock(XKeycode.Control_L);
  159. if (modifiers & Gdk.ModifierType.SHIFT_MASK)
  160. this._modeUnlock(XKeycode.Shift_L);
  161. if (modifiers & Gdk.ModifierType.SUPER_MASK)
  162. this._modeUnlock(XKeycode.Super_L);
  163. } catch (e) {
  164. logError(e);
  165. }
  166. }
  167. _pressKeysym(keysym, modifiers) {
  168. try {
  169. // Press Modifiers
  170. if (modifiers & Gdk.ModifierType.MOD1_MASK)
  171. this._modeLock(XKeycode.Alt_L);
  172. if (modifiers & Gdk.ModifierType.CONTROL_MASK)
  173. this._modeLock(XKeycode.Control_L);
  174. if (modifiers & Gdk.ModifierType.SHIFT_MASK)
  175. this._modeLock(XKeycode.Shift_L);
  176. if (modifiers & Gdk.ModifierType.SUPER_MASK)
  177. this._modeLock(XKeycode.Super_L);
  178. Atspi.generate_keyboard_event(
  179. keysym,
  180. null,
  181. Atspi.KeySynthType.PRESSRELEASE | Atspi.KeySynthType.SYM
  182. );
  183. // Release Modifiers
  184. if (modifiers & Gdk.ModifierType.MOD1_MASK)
  185. this._modeUnlock(XKeycode.Alt_L);
  186. if (modifiers & Gdk.ModifierType.CONTROL_MASK)
  187. this._modeUnlock(XKeycode.Control_L);
  188. if (modifiers & Gdk.ModifierType.SHIFT_MASK)
  189. this._modeUnlock(XKeycode.Shift_L);
  190. if (modifiers & Gdk.ModifierType.SUPER_MASK)
  191. this._modeUnlock(XKeycode.Super_L);
  192. } catch (e) {
  193. logError(e);
  194. }
  195. }
  196. /**
  197. * Simulate the composition of a unicode character with:
  198. * Control+Shift+u, [hex], Return
  199. *
  200. * @param {number} key - An XKeycode
  201. * @param {number} modifiers - A modifier mask
  202. */
  203. _pressUnicode(key, modifiers) {
  204. try {
  205. if (modifiers > 0)
  206. log('GSConnect: ignoring modifiers for unicode keyboard event');
  207. // TODO: Using Control and Shift keysym is not working (it triggers
  208. // key release). Probably using LOCKMODIFIERS will not work either
  209. // as unlocking the modifier will not trigger a release
  210. // Activate compose sequence
  211. this._modeLock(XKeycode.Control_L);
  212. this._modeLock(XKeycode.Shift_L);
  213. this.pressreleaseKeysym(Gdk.KEY_U);
  214. this._modeUnlock(XKeycode.Control_L);
  215. this._modeUnlock(XKeycode.Shift_L);
  216. // Enter the unicode sequence
  217. const ucode = key.charCodeAt(0).toString(16);
  218. let keysym;
  219. for (let h = 0, len = ucode.length; h < len; h++) {
  220. keysym = Gdk.unicode_to_keyval(ucode.charAt(h).codePointAt(0));
  221. this.pressreleaseKeysym(keysym);
  222. }
  223. // Finish the compose sequence
  224. this.pressreleaseKeysym(Gdk.KEY_Return);
  225. } catch (e) {
  226. logError(e);
  227. }
  228. }
  229. /*
  230. * Keyboard Events
  231. */
  232. pressKeysym(keysym) {
  233. Atspi.generate_keyboard_event(
  234. keysym,
  235. null,
  236. Atspi.KeySynthType.PRESS | Atspi.KeySynthType.SYM
  237. );
  238. }
  239. releaseKeysym(keysym) {
  240. Atspi.generate_keyboard_event(
  241. keysym,
  242. null,
  243. Atspi.KeySynthType.RELEASE | Atspi.KeySynthType.SYM
  244. );
  245. }
  246. pressreleaseKeysym(keysym) {
  247. Atspi.generate_keyboard_event(
  248. keysym,
  249. null,
  250. Atspi.KeySynthType.PRESSRELEASE | Atspi.KeySynthType.SYM
  251. );
  252. }
  253. pressKey(input, modifiers) {
  254. // We were passed a keysym
  255. if (typeof input === 'number')
  256. this._pressKeysym(input, modifiers);
  257. // Regular ASCII
  258. else if (_ASCII.test(input))
  259. this._pressASCII(input, modifiers);
  260. // Unicode
  261. else
  262. this._pressUnicode(input, modifiers);
  263. }
  264. destroy() {
  265. try {
  266. Atspi.exit();
  267. } catch (e) {
  268. // Silence errors
  269. }
  270. }
  271. };