From 88b9b81b8d6255858b68e408ff5500631c51824d Mon Sep 17 00:00:00 2001 From: Raheman Vaiya Date: Sat, 17 Feb 2024 07:15:57 +0000 Subject: [PATCH] keyd-application-mapper: Add Gnome 45 support (#639, #649) Add support for Gnome 45, which breaks backward extension compatibility. We now ship and maintain two extensions :/. The main change in Gnome seems to be the switch from a custom import implementation to 'standard' ES6 style imports. The init method also seems to have inexplicably been swapped out in favour of extending a magic class. This patch takes the opportunity to clean up some residual cruft and move the extension code out of the mapper script. Specifically: - Ship distinct extensions for Gnome 42-44 and Gnome 45 in /usr/local/share/keyd - Move the named pipe to XDG_RUNTIME_DIR - Initialize the pipe inside the extension instead of the script to avoid potential race conditions during initialization. --- Makefile | 1 + data/gnome-extension-45/extension.js | 65 ++++++++++++++ data/gnome-extension-45/metadata.json | 7 ++ data/gnome-extension/extension.js | 65 ++++++++++++++ data/gnome-extension/metadata.json | 7 ++ scripts/keyd-application-mapper | 125 ++++++++------------------ 6 files changed, 180 insertions(+), 90 deletions(-) create mode 100644 data/gnome-extension-45/extension.js create mode 100644 data/gnome-extension-45/metadata.json create mode 100644 data/gnome-extension/extension.js create mode 100644 data/gnome-extension/metadata.json diff --git a/Makefile b/Makefile index 5d625518..dd521e1e 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ install: install -m644 docs/*.md $(DESTDIR)$(PREFIX)/share/doc/keyd/ install -m644 examples/* $(DESTDIR)$(PREFIX)/share/doc/keyd/examples/ install -m644 layouts/* $(DESTDIR)$(PREFIX)/share/keyd/layouts + cp -r data/gnome-* $(DESTDIR)$(PREFIX)/share/keyd install -m644 data/*.1.gz $(DESTDIR)$(PREFIX)/share/man/man1/ install -m644 data/keyd.compose $(DESTDIR)$(PREFIX)/share/keyd/ diff --git a/data/gnome-extension-45/extension.js b/data/gnome-extension-45/extension.js new file mode 100644 index 00000000..0d1968b7 --- /dev/null +++ b/data/gnome-extension-45/extension.js @@ -0,0 +1,65 @@ +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import GLib from 'gi://GLib'; +import Shell from 'gi://Shell'; +import Gio from 'gi://Gio'; +import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; + +let file = Gio.File.new_for_path(makePipe()); +let pipe = file.append_to_async(0, 0, null, on_pipe_open); + +function makePipe() { + let runtime_dir = GLib.getenv('XDG_RUNTIME_DIR'); + if (!runtime_dir) + runtime_dir = '/run/user/'+new TextDecoder().decode( + GLib.spawn_command_line_sync('id -u')[1] + ).trim(); + + let path = runtime_dir + '/keyd.fifo'; + GLib.spawn_command_line_sync('mkfifo ' + path); + + return path; +} + +function send(msg) { + if (!pipe) + return; + + try { + pipe.write(msg, null); + } catch { + log('pipe closed, reopening...'); + pipe = null; + file.append_to_async(0, 0, null, on_pipe_open); + } +} + +function on_pipe_open(file, res) { + log('pipe opened'); + pipe = file.append_to_finish(res); +} + +export default class KeydExtension extends Extension { + enable() { + Shell.WindowTracker.get_default().connect('notify::focus-app', () => { + const win = global.display.focus_window; + const cls = win ? win.get_wm_class() : 'root'; + const title = win ? win.get_title() : ''; + + send(`${cls} ${title}\n`); + }); + + Main.layoutManager.connectObject( + 'system-modal-opened', () => { + send(`system-modal ${global.stage.get_title()}\n`); + }, + this + ); + + GLib.spawn_command_line_async('keyd-application-mapper -d'); + } + + disable() { + GLib.spawn_command_line_async('pkill -f keyd-application-mapper'); + } +} + diff --git a/data/gnome-extension-45/metadata.json b/data/gnome-extension-45/metadata.json new file mode 100644 index 00000000..48591bea --- /dev/null +++ b/data/gnome-extension-45/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "keyd", + "description": "Used by keyd to obtain active window information.", + "uuid": "keyd", + "shell-version": [ "45" ] +} + diff --git a/data/gnome-extension/extension.js b/data/gnome-extension/extension.js new file mode 100644 index 00000000..a11f4b67 --- /dev/null +++ b/data/gnome-extension/extension.js @@ -0,0 +1,65 @@ +const Shell = imports.gi.Shell; +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio; +const Main = imports.ui.main; + +let file = Gio.File.new_for_path(makePipe()); +let pipe = file.append_to_async(0, 0, null, on_pipe_open); + +function makePipe() { + let runtime_dir = GLib.getenv('XDG_RUNTIME_DIR'); + if (!runtime_dir) + runtime_dir = '/run/user/'+new TextDecoder().decode( + GLib.spawn_command_line_sync('id -u')[1] + ).trim(); + + let path = runtime_dir + '/keyd.fifo'; + GLib.spawn_command_line_sync('mkfifo ' + path); + + return path; +} + +function send(msg) { + if (!pipe) + return; + + try { + pipe.write(msg, null); + } catch { + log('pipe closed, reopening...'); + pipe = null; + file.append_to_async(0, 0, null, on_pipe_open); + } +} + +function on_pipe_open(file, res) { + log('pipe opened'); + pipe = file.append_to_finish(res); +} + +function init() { + return { + enable: function() { + Shell.WindowTracker.get_default().connect('notify::focus-app', () => { + const win = global.display.focus_window; + const cls = win ? win.get_wm_class() : 'root'; + const title = win ? win.get_title() : ''; + + send(`${cls} ${title}\n`); + }); + + Main.layoutManager.connectObject( + 'system-modal-opened', () => { + send(`system-modal ${global.stage.get_title()}\n`); + }, + this + ); + + GLib.spawn_command_line_async('keyd-application-mapper -d'); + }, + + disable: function() { + GLib.spawn_command_line_async('pkill -f keyd-application-mapper'); + } + } +} diff --git a/data/gnome-extension/metadata.json b/data/gnome-extension/metadata.json new file mode 100644 index 00000000..dbeff8cf --- /dev/null +++ b/data/gnome-extension/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "keyd", + "description": "Used by keyd to obtain active window information.", + "uuid": "keyd", + "shell-version": [ "42", "43", "44" ] +} + diff --git a/scripts/keyd-application-mapper b/scripts/keyd-application-mapper index 55dd63be..d3d5215a 100755 --- a/scripts/keyd-application-mapper +++ b/scripts/keyd-application-mapper @@ -6,6 +6,7 @@ import select import socket import struct import os +import errno import shutil import re import sys @@ -294,97 +295,37 @@ class GnomeMonitor(): self.on_window_change = on_window_change - self.version = '1.4' - self.extension_dir = os.getenv('HOME') + '/.local/share/gnome-shell/extensions/keyd' - self.fifo_path = self.extension_dir + '/keyd.fifo' - - def _install(self): - shutil.rmtree(self.extension_dir, ignore_errors=True) - os.makedirs(self.extension_dir, exist_ok=True) - - extension = ''' - const Shell = imports.gi.Shell; - const GLib = imports.gi.GLib; - const Main = imports.ui.main; - - // We have to keep an explicit reference around to prevent garbage collection :/. - let file = imports.gi.Gio.File.new_for_path('%s'); - let pipe = file.append_to_async(0, 0, null, on_pipe_open); - - function send(msg) { - if (!pipe) - return; - - try { - pipe.write(msg, null); - } catch { - log('pipe closed, reopening...'); - pipe = null; - file.append_to_async(0, 0, null, on_pipe_open); - } - } - - function on_pipe_open(file, res) { - log('pipe opened'); - pipe = file.append_to_finish(res); - } - - function init() { - Shell.WindowTracker.get_default().connect('notify::focus-app', () => { - const win = global.display.focus_window; - const cls = win ? win.get_wm_class() : 'root'; - const title = win ? win.get_title() : ''; - - send(`${cls}\\t${title}\\n`); - }); - - Main.layoutManager.connectObject( - 'system-modal-opened', () => { - send(`system-modal\\t${global.stage.get_title()}\\n`); - }, - this - ); - - return { - enable: ()=>{ GLib.spawn_command_line_async('keyd-application-mapper -d'); }, - disable: ()=>{ GLib.spawn_command_line_async('pkill -f keyd-application-mapper'); } - }; - - } - ''' % (self.fifo_path) - - metadata = ''' - { - "name": "keyd", - "description": "Used by keyd to obtain active window information.", - "uuid": "keyd", - "shell-version": [ "41", "42", "43" ] - } - ''' - - open(self.extension_dir + '/version', 'w').write(self.version) - open(self.extension_dir + '/metadata.json', 'w').write(metadata) - open(self.extension_dir + '/extension.js', 'w').write(extension) - os.mkfifo(self.fifo_path) - - def _is_installed(self): - try: - return open(self.extension_dir + '/version', 'r').read() == self.version - except: - return False + self.fifo_path = (os.getenv('XDG_RUNTIME_DIR') or '/run/user/'+str(os.getuid())) + '/keyd.fifo' def init(self): - if not self._is_installed(): - print('keyd extension not found, installing...') - self._install() - run_or_die('gsettings set org.gnome.shell disable-user-extensions false'); + if not os.path.exists(self.fifo_path): + print("""Gnome extension doesn't appear to be running: - print('Success! Please restart Gnome and run this script one more time.') - exit(0) +You will need to install the keyd gnome extension in order for the +application mapper to work correctly. + +This can usually be achieved by running: + +rm -r ~/.local/share/gnome-shell/extensions/keyd # Remove any older versions of the extension +mkdir -p ~/.local/share/gnome-shell/extensions + +Followed by: - if 'DISABLED' in run('gnome-extensions show keyd'): - run_or_die('gnome-extensions enable keyd', 'Failed to enable keyd extension.') - print(f'Successfully enabled keyd extension :). Output will be stored in {LOGFILE}') +Gnome 42-44: + ln -s /usr/local/share/keyd/gnome-extension ~/.local/share/gnome-shell/extensions/keyd + +Gnome 45: + ln -s /usr/local/share/keyd/gnome-extension-45 ~/.local/share/gnome-shell/extensions/keyd + +Finally restart Gnome and run: + + gnome-extensions enable keyd + gnome-extensions show keyd (verify the extension is enabled) + +NOTE: + You may need to adjust the above paths (e.g /usr/share/keyd/gnome-extension) + depending on your distro. +""") exit(0) def run(self): @@ -397,9 +338,13 @@ class GnomeMonitor(): self.on_window_change(last_cls, last_title) continue - (cls, title) = line.strip('\n').split('\t') - last_cls = cls - last_title = title + try: + (cls, title) = line.strip('\n').split('\t') + last_cls = cls + last_title = title + except: + cls = '' + title = '' self.on_window_change(cls, title)