'use strict'; imports.gi.versions.Atspi = '2.0'; const Atspi = imports.gi.Atspi; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; /** * Printable ASCII range */ const _ASCII = /[\x20-\x7E]/; /** * Modifier Keycode Defaults */ const XKeycode = { Alt_L: 0x40, Control_L: 0x25, Shift_L: 0x32, Super_L: 0x85 }; var Controller = class { constructor() { // Atspi.init() return 2 on fail, but still marks itself as inited. We // uninit before throwing an error otherwise any future call to init() // will appear successful and other calls will cause GSConnect to exit. // See: https://gitlab.gnome.org/GNOME/at-spi2-core/blob/master/atspi/atspi-misc.c if (Atspi.init() === 2) { this.destroy(); throw new Error('Failed to start AT-SPI'); } try { this._display = Gdk.Display.get_default(); this._seat = this._display.get_default_seat(); this._pointer = this._seat.get_pointer(); } catch (e) { this.destroy(); throw e; } // Try to read modifier keycodes from Gdk try { let keymap = Gdk.Keymap.get_for_display(this._display); let modifier; modifier = keymap.get_entries_for_keyval(Gdk.KEY_Alt_L)[1][0]; XKeycode.Alt_L = modifier.keycode; modifier = keymap.get_entries_for_keyval(Gdk.KEY_Control_L)[1][0]; XKeycode.Control_L = modifier.keycode; modifier = keymap.get_entries_for_keyval(Gdk.KEY_Shift_L)[1][0]; XKeycode.Shift_L = modifier.keycode; modifier = keymap.get_entries_for_keyval(Gdk.KEY_Super_L)[1][0]; XKeycode.Super_L = modifier.keycode; } catch (e) { debug('using default modifier keycodes'); } } /** * Pointer events */ clickPointer(button) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}c`); } catch (e) { logError(e); } } doubleclickPointer(button) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}d`); } catch (e) { logError(e); } } movePointer(dx, dy) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * dx, scale * dy, 'rel'); } catch (e) { logError(e); } } pressPointer(button) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}p`); } catch (e) { logError(e); } } releasePointer(button) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}r`); } catch (e) { logError(e); } } scrollPointer(dx, dy) { if (dy > 0) { this.clickPointer(4); } else if (dy < 0) { this.clickPointer(5); } } /** * Phony virtual keyboard helpers */ _modeLock(keycode) { Atspi.generate_keyboard_event( keycode, null, Atspi.KeySynthType.PRESS ); } _modeUnlock(keycode) { Atspi.generate_keyboard_event( keycode, null, Atspi.KeySynthType.RELEASE ); } /** * Simulate a printable-ASCII character. * */ _pressASCII(key, modifiers) { try { // Press Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this._modeLock(XKeycode.Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this._modeLock(XKeycode.Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this._modeLock(XKeycode.Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this._modeLock(XKeycode.Super_L); Atspi.generate_keyboard_event( 0, key, Atspi.KeySynthType.STRING ); // Release Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this._modeUnlock(XKeycode.Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this._modeUnlock(XKeycode.Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this._modeUnlock(XKeycode.Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this._modeUnlock(XKeycode.Super_L); } catch (e) { logError(e); } } _pressKeysym(keysym, modifiers) { try { // Press Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this._modeLock(XKeycode.Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this._modeLock(XKeycode.Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this._modeLock(XKeycode.Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this._modeLock(XKeycode.Super_L); Atspi.generate_keyboard_event( keysym, null, Atspi.KeySynthType.PRESSRELEASE | Atspi.KeySynthType.SYM ); // Release Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this._modeUnlock(XKeycode.Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this._modeUnlock(XKeycode.Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this._modeUnlock(XKeycode.Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this._modeUnlock(XKeycode.Super_L); } catch (e) { logError(e); } } /** * Simulate the composition of a unicode character with: * Control+Shift+u, [hex], Return * * @param {object} input - 'body' of a 'kdeconnect.mousepad.request' packet */ _pressUnicode(key, modifiers) { try { if (modifiers > 0) { log('GSConnect: ignoring modifiers for unicode keyboard event'); } // TODO: Using Control and Shift keysym is not working (it triggers // key release). Probably using LOCKMODIFIERS will not work either // as unlocking the modifier will not trigger a release // Activate compose sequence this._modeLock(XKeycode.Control_L); this._modeLock(XKeycode.Shift_L); this.pressreleaseKeysym(Gdk.KEY_U); this._modeUnlock(XKeycode.Control_L); this._modeUnlock(XKeycode.Shift_L); // Enter the unicode sequence let ucode = key.charCodeAt(0).toString(16); let keysym; for (let h = 0, len = ucode.length; h < len; h++) { keysym = Gdk.unicode_to_keyval(ucode.charAt(h).codePointAt(0)); this.pressreleaseKeysym(keysym); } // Finish the compose sequence this.pressreleaseKeysym(Gdk.KEY_Return); } catch (e) { logError(e); } } /** * Keyboard Events */ pressKeysym(keysym) { Atspi.generate_keyboard_event( keysym, null, Atspi.KeySynthType.PRESS | Atspi.KeySynthType.SYM ); } releaseKeysym(keysym) { Atspi.generate_keyboard_event( keysym, null, Atspi.KeySynthType.RELEASE | Atspi.KeySynthType.SYM ); } pressreleaseKeysym(keysym) { Atspi.generate_keyboard_event( keysym, null, Atspi.KeySynthType.PRESSRELEASE | Atspi.KeySynthType.SYM ); } pressKey(input, modifiers) { // We were passed a keysym if (typeof input === 'number') { this._pressKeysym(input, modifiers); // Regular ASCII } else if (_ASCII.test(input)) { this._pressASCII(input, modifiers); // Unicode } else { this._pressUnicode(input, modifiers); } } destroy() { try { Atspi.exit(); } catch (e) { // Silence errors } } };