123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
- //
- // SPDX-License-Identifier: GPL-2.0-or-later
- 'use strict';
- const Gdk = imports.gi.Gdk;
- const GObject = imports.gi.GObject;
- const Components = imports.service.components;
- const {InputDialog} = imports.service.ui.mousepad;
- const PluginBase = imports.service.plugin;
- var Metadata = {
- label: _('Mousepad'),
- description: _('Enables the paired device to act as a remote mouse and keyboard'),
- id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Mousepad',
- incomingCapabilities: [
- 'kdeconnect.mousepad.echo',
- 'kdeconnect.mousepad.request',
- 'kdeconnect.mousepad.keyboardstate',
- ],
- outgoingCapabilities: [
- 'kdeconnect.mousepad.echo',
- 'kdeconnect.mousepad.request',
- 'kdeconnect.mousepad.keyboardstate',
- ],
- actions: {
- keyboard: {
- label: _('Remote Input'),
- icon_name: 'input-keyboard-symbolic',
- parameter_type: null,
- incoming: [
- 'kdeconnect.mousepad.echo',
- 'kdeconnect.mousepad.keyboardstate',
- ],
- outgoing: ['kdeconnect.mousepad.request'],
- },
- },
- };
- /**
- * A map of "KDE Connect" keyvals to Gdk
- */
- const KeyMap = new Map([
- [1, Gdk.KEY_BackSpace],
- [2, Gdk.KEY_Tab],
- [3, Gdk.KEY_Linefeed],
- [4, Gdk.KEY_Left],
- [5, Gdk.KEY_Up],
- [6, Gdk.KEY_Right],
- [7, Gdk.KEY_Down],
- [8, Gdk.KEY_Page_Up],
- [9, Gdk.KEY_Page_Down],
- [10, Gdk.KEY_Home],
- [11, Gdk.KEY_End],
- [12, Gdk.KEY_Return],
- [13, Gdk.KEY_Delete],
- [14, Gdk.KEY_Escape],
- [15, Gdk.KEY_Sys_Req],
- [16, Gdk.KEY_Scroll_Lock],
- [17, 0],
- [18, 0],
- [19, 0],
- [20, 0],
- [21, Gdk.KEY_F1],
- [22, Gdk.KEY_F2],
- [23, Gdk.KEY_F3],
- [24, Gdk.KEY_F4],
- [25, Gdk.KEY_F5],
- [26, Gdk.KEY_F6],
- [27, Gdk.KEY_F7],
- [28, Gdk.KEY_F8],
- [29, Gdk.KEY_F9],
- [30, Gdk.KEY_F10],
- [31, Gdk.KEY_F11],
- [32, Gdk.KEY_F12],
- ]);
- const KeyMapCodes = new Map([
- [1, 14],
- [2, 15],
- [3, 101],
- [4, 105],
- [5, 103],
- [6, 106],
- [7, 108],
- [8, 104],
- [9, 109],
- [10, 102],
- [11, 107],
- [12, 28],
- [13, 111],
- [14, 1],
- [15, 99],
- [16, 70],
- [17, 0],
- [18, 0],
- [19, 0],
- [20, 0],
- [21, 59],
- [22, 60],
- [23, 61],
- [24, 62],
- [25, 63],
- [26, 64],
- [27, 65],
- [28, 66],
- [29, 67],
- [30, 68],
- [31, 87],
- [32, 88],
- ]);
- /**
- * Mousepad Plugin
- * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/mousepad
- *
- * TODO: support outgoing mouse events?
- */
- var Plugin = GObject.registerClass({
- GTypeName: 'GSConnectMousepadPlugin',
- Properties: {
- 'state': GObject.ParamSpec.boolean(
- 'state',
- 'State',
- 'Remote keyboard state',
- GObject.ParamFlags.READABLE,
- false
- ),
- },
- }, class Plugin extends PluginBase.Plugin {
- _init(device) {
- super._init(device, 'mousepad');
- if (!globalThis.HAVE_GNOME)
- this._input = Components.acquire('ydotool');
- else
- this._input = Components.acquire('input');
- this._shareControlChangedId = this.settings.connect(
- 'changed::share-control',
- this._sendState.bind(this)
- );
- }
- get state() {
- if (this._state === undefined)
- this._state = false;
- return this._state;
- }
- connected() {
- super.connected();
- this._sendState();
- }
- disconnected() {
- super.disconnected();
- this._state = false;
- this.notify('state');
- }
- handlePacket(packet) {
- switch (packet.type) {
- case 'kdeconnect.mousepad.request':
- this._handleInput(packet.body);
- break;
- case 'kdeconnect.mousepad.echo':
- this._handleEcho(packet.body);
- break;
- case 'kdeconnect.mousepad.keyboardstate':
- this._handleState(packet);
- break;
- }
- }
- /**
- * Handle a input event.
- *
- * @param {Object} input - The body of a `kdeconnect.mousepad.request`
- */
- _handleInput(input) {
- if (!this.settings.get_boolean('share-control'))
- return;
- let keysym;
- let modifiers = 0;
- const modifiers_codes = [];
- // These are ordered, as much as possible, to create the shortest code
- // path for high-frequency, low-latency events (eg. mouse movement)
- switch (true) {
- case input.hasOwnProperty('scroll'):
- this._input.scrollPointer(input.dx, input.dy);
- break;
- case (input.hasOwnProperty('dx') && input.hasOwnProperty('dy')):
- this._input.movePointer(input.dx, input.dy);
- break;
- case (input.hasOwnProperty('key') || input.hasOwnProperty('specialKey')):
- // NOTE: \u0000 sometimes sent in advance of a specialKey packet
- if (input.key && input.key === '\u0000')
- return;
- // Modifiers
- if (input.alt) {
- modifiers |= Gdk.ModifierType.MOD1_MASK;
- modifiers_codes.push(56);
- }
- if (input.ctrl) {
- modifiers |= Gdk.ModifierType.CONTROL_MASK;
- modifiers_codes.push(29);
- }
- if (input.shift) {
- modifiers |= Gdk.ModifierType.SHIFT_MASK;
- modifiers_codes.push(42);
- }
- if (input.super) {
- modifiers |= Gdk.ModifierType.SUPER_MASK;
- modifiers_codes.push(125);
- }
- // Regular key (printable ASCII or Unicode)
- if (input.key) {
- if (!globalThis.HAVE_GNOME)
- this._input.pressKeys(input.key, modifiers_codes);
- else
- this._input.pressKeys(input.key, modifiers);
- this._sendEcho(input);
- // Special key (eg. non-printable ASCII)
- } else if (input.specialKey && KeyMap.has(input.specialKey)) {
- if (!globalThis.HAVE_GNOME) {
- keysym = KeyMapCodes.get(input.specialKey);
- this._input.pressKeys(keysym, modifiers_codes);
- } else {
- keysym = KeyMap.get(input.specialKey);
- this._input.pressKeys(keysym, modifiers);
- }
- this._sendEcho(input);
- }
- break;
- case input.hasOwnProperty('singleclick'):
- this._input.clickPointer(Gdk.BUTTON_PRIMARY);
- break;
- case input.hasOwnProperty('doubleclick'):
- this._input.doubleclickPointer(Gdk.BUTTON_PRIMARY);
- break;
- case input.hasOwnProperty('middleclick'):
- this._input.clickPointer(Gdk.BUTTON_MIDDLE);
- break;
- case input.hasOwnProperty('rightclick'):
- this._input.clickPointer(Gdk.BUTTON_SECONDARY);
- break;
- case input.hasOwnProperty('singlehold'):
- this._input.pressPointer(Gdk.BUTTON_PRIMARY);
- break;
- case input.hasOwnProperty('singlerelease'):
- this._input.releasePointer(Gdk.BUTTON_PRIMARY);
- break;
- default:
- logError(new Error('Unknown input'));
- }
- }
- /**
- * Handle an echo/ACK of a event we sent, displaying it the dialog entry.
- *
- * @param {Object} input - The body of a `kdeconnect.mousepad.echo`
- */
- _handleEcho(input) {
- if (!this._dialog || !this._dialog.visible)
- return;
- // Skip modifiers
- if (input.alt || input.ctrl || input.super)
- return;
- if (input.key) {
- this._dialog._isAck = true;
- this._dialog.entry.buffer.text += input.key;
- this._dialog._isAck = false;
- } else if (KeyMap.get(input.specialKey) === Gdk.KEY_BackSpace) {
- this._dialog.entry.emit('backspace');
- }
- }
- /**
- * Handle a state change from the remote keyboard. This is an indication
- * that the remote keyboard is ready to accept input.
- *
- * @param {Object} packet - A `kdeconnect.mousepad.keyboardstate` packet
- */
- _handleState(packet) {
- this._state = !!packet.body.state;
- this.notify('state');
- }
- /**
- * Send an echo/ACK of @input, if requested
- *
- * @param {Object} input - The body of a 'kdeconnect.mousepad.request'
- */
- _sendEcho(input) {
- if (!input.sendAck)
- return;
- delete input.sendAck;
- input.isAck = true;
- this.device.sendPacket({
- type: 'kdeconnect.mousepad.echo',
- body: input,
- });
- }
- /**
- * Send the local keyboard state
- *
- * @param {boolean} state - Whether we're ready to accept input
- */
- _sendState() {
- this.device.sendPacket({
- type: 'kdeconnect.mousepad.keyboardstate',
- body: {
- state: this.settings.get_boolean('share-control'),
- },
- });
- }
- /**
- * Open the Keyboard Input dialog
- */
- keyboard() {
- if (this._dialog === undefined) {
- this._dialog = new InputDialog({
- device: this.device,
- plugin: this,
- });
- }
- this._dialog.present();
- }
- destroy() {
- if (this._input !== undefined) {
- if (!globalThis.HAVE_GNOME)
- this._input = Components.release('ydotool');
- else
- this._input = Components.release('input');
- }
- if (this._dialog !== undefined)
- this._dialog.destroy();
- this.settings.disconnect(this._shareControlChangedId);
- super.destroy();
- }
- });
|