Skip to content

Commit

Permalink
keyd-application-mapper: Add Gnome 45 support (#639, #649)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rvaiya committed Feb 18, 2024
1 parent 459cb1f commit 88b9b81
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 90 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
65 changes: 65 additions & 0 deletions data/gnome-extension-45/extension.js
Original file line number Diff line number Diff line change
@@ -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');
}
}

7 changes: 7 additions & 0 deletions data/gnome-extension-45/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "keyd",
"description": "Used by keyd to obtain active window information.",
"uuid": "keyd",
"shell-version": [ "45" ]
}

65 changes: 65 additions & 0 deletions data/gnome-extension/extension.js
Original file line number Diff line number Diff line change
@@ -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');
}
}
}
7 changes: 7 additions & 0 deletions data/gnome-extension/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "keyd",
"description": "Used by keyd to obtain active window information.",
"uuid": "keyd",
"shell-version": [ "42", "43", "44" ]
}

125 changes: 35 additions & 90 deletions scripts/keyd-application-mapper
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import select
import socket
import struct
import os
import errno
import shutil
import re
import sys
Expand Down Expand Up @@ -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):
Expand All @@ -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)

Expand Down

0 comments on commit 88b9b81

Please sign in to comment.