From ac27378217e0e304f0b1affc4d93e6c139a53a13 Mon Sep 17 00:00:00 2001 From: Raheman Vaiya Date: Mon, 17 Jan 2022 14:29:44 -0500 Subject: [PATCH] Fix title matching + add wildcards --- README.md | 2 +- keyd.1.gz | Bin 6320 -> 6534 bytes man.md | 52 ++++++++++----- scripts/keyd-application-mapper | 112 +++++++++++++++++++++++--------- 4 files changed, 117 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 1e63377..47388a0 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ E.G - Run: - keyd-application-mapper + `keyd-application-mapper` You will probably want to put `keyd-application-mapper -d` somewhere in your display server initialization logic (e.g ~/.xinitrc) unless you are running Gnome. diff --git a/keyd.1.gz b/keyd.1.gz index 1f0ffe1860cb8236446b2a8b2f0eecd72220015c..c8de52ad8eb59278f3403e084e7e39feb85d883b 100644 GIT binary patch delta 6500 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 delta 6285 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] +The config file has the following form: + + [] -Where each expression is a valid argument to `-e` (see *Expressions*). +Where `` has one of the following forms: + + [] # Match by window class + [|] # Match by class and title -For example: +and each `<expression>` is a valid argument to `-e` (see *Expressions*). + +`<class exp>` and `<title exp>` are strings which describe window class and title names +to be matched and may optionally contain unix style wildcards (*). For example, +'`[gnome|*find*]`' will match any window with a class of 'gnome' and a title +which contains 'find'. + +applied over the global rules defined in `/etc/keyd/*.conf`. + +E.G [kitty] @@ -316,9 +330,9 @@ For example: alt.[ = C-S-tab alt.t = C-S-t - [alacritty] + [st-*] - alt.1 = macro(Inside space alacritty!) + alt.1 = macro(Inside space st) [chromium] @@ -327,22 +341,24 @@ For example: alt.[ = C-S-tab alt.t = C-t -Will remap `A-1` to the the string 'Inside alacritty' when a window with class -`alacritty` is active. You can think of each section as an application specific mask -applied over the default configuration. +Will remap `A-1` to the the string 'Inside st' when a window with a class +that begins with 'st-' (e.g st-256color) is active. -Application classes can be obtained by inspecting the log output while the +Window class and title names can be obtained by inspecting the log output while the daemon is running (e.g `tail -f ~/.config/keyd/app.log`). -If the script is run under Gnome, the extension will manage the lifecycle of -the application remapper after it has been run for the first time. Otherwise -you will need to put `keyd-application-mapper -d` somewhere in your display -server initialization logic (e.g ~/.xinitrc or ~/.xsession) - At the moment X, Sway and Gnome are supported. -Note: In order for this to work, keyd must be running and the user must have access to -*/var/run/keyd.socket* (i.e be a member of the *keyd* group). +#### Installation + +Installation is a simple matter of running the daemon `keyd-application-mapper -d` +somewhere in your display server initialization logic (e.g ~/.xinitrc or +~/.xsession). The exception to this is if you are running Gnome, in which case +running `keyd-application-mapper` for the first time will install an extension +which manages the script lifecycle. + +In order for this to work, keyd must be running and the user must have access +to */var/run/keyd.socket* (i.e be a member of the *keyd* group). ### A note on security diff --git a/scripts/keyd-application-mapper b/scripts/keyd-application-mapper index bd99df5..63133fc 100755 --- a/scripts/keyd-application-mapper +++ b/scripts/keyd-application-mapper @@ -7,6 +7,7 @@ import shutil import re import sys import fcntl +from fnmatch import fnmatch # Good enough for now :/. @@ -31,6 +32,9 @@ def assert_env(var): if not os.getenv(var): raise Exception(f'Missing environment variable {var}') +def run(cmd): + return subprocess.check_output(['/bin/sh', '-c', cmd]).decode('utf8') + def run_or_die(cmd, msg=''): rc = subprocess.run(['/bin/sh', '-c', cmd], stdout=subprocess.DEVNULL, @@ -40,16 +44,23 @@ def run_or_die(cmd, msg=''): die(msg) def parse_config(path): - map = {} + config = [] for line in open(path): line = line.strip() if line.startswith('[') and line.endswith(']'): - window_identifier = line[1:-1] + a = line[1:-1].split('|') + + if len(a) < 2: + cls = a[0] + title = '*' + else: + cls = a[0] + title = a[1] bindings = [] - map[window_identifier] = bindings + config.append((cls, title, bindings)) elif line == '': continue elif line.startswith('#'): @@ -57,7 +68,7 @@ def parse_config(path): else: bindings.append(line) - return map + return config class SwayMonitor(): def __init__(self, on_window_change): @@ -71,6 +82,8 @@ class SwayMonitor(): def run(self): import json import subprocess + last_cls = '' + last_title = '' swayproc = subprocess.Popen( ['swaymsg', @@ -83,14 +96,27 @@ class SwayMonitor(): for ev in swayproc.stdout: data = json.loads(ev) + title = '' + cls = '' + try: if data['container']['focused'] == True: props = data['container']['window_properties'] - self.on_window_change(props['class'], props['title']) + + cls = props['class'] + title = props['title'] except: + title = '' cls = data['container']['app_id'] - self.on_window_change(cls, "") - pass + + if title == '' and cls == '': + continue + + if last_cls != cls or last_title != title: + last_cls = cls + last_title = title + + self.on_window_change(cls, title) class XMonitor(): @@ -109,21 +135,29 @@ class XMonitor(): def run(self): last_active_class = "" - last_active_name = "" + last_active_title = "" + + WM_NAME = self.dpy.intern_atom('WM_NAME') + while True: self.dpy.next_event() try: - wm_class = self.dpy.get_input_focus().focus.get_wm_class() + win = self.dpy.get_input_focus().focus - if wm_class == None: + classes = win.get_wm_class() + title = win.get_full_property(WM_NAME, 0).value.decode('utf8') + + if classes == None: cls = 'root' else: - cls = wm_class[1] + cls = classes[1] - if cls != last_active_class: + if cls != last_active_class or title != last_active_title: last_active_class = cls - self.on_window_change(cls) + last_active_title = title + + self.on_window_change(cls, title) except: pass @@ -134,7 +168,7 @@ class GnomeMonitor(): self.on_window_change = on_window_change - self.version = '1' + self.version = '1.1' self.extension_dir = os.getenv('HOME') + '/.local/share/gnome-shell/extensions/keyd' self.fifo_path = self.extension_dir + '/keyd.fifo' @@ -172,7 +206,9 @@ class GnomeMonitor(): Shell.WindowTracker.get_default().connect('notify::focus-app', () => { const win = global.display.focus_window; const cls = win ? win.get_wm_class() : 'root'; - send(`${cls}\n`); + const title = win ? win.get_title() : ''; + + send(`${cls}\\t${title}\\n`); }); return { @@ -207,16 +243,21 @@ class GnomeMonitor(): if not self._is_installed(): print('keyd extension not found, installing...') self._install() - print('Success! Please restart Gnome.') + run_or_die('gsettings set org.gnome.shell disable-user-extensions false'); + + print('Success! Please restart Gnome and rerun this script one more time.') exit(0) - run_or_die('gsettings set org.gnome.shell disable-user-extensions false'); - run_or_die('gnome-extensions enable keyd', 'Failed to enable keyd extension.') + 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}') + exit(0) def run(self): for line in open(self.fifo_path): - [cls, name] = line.strip().split('\t') - self.on_window_change(cls, name) + (cls, title) = line.strip('\n').split('\t') + + self.on_window_change(cls, title) def get_monitor(on_window_change): monitors = [ @@ -269,31 +310,42 @@ args = opt.parse_args() if not os.path.exists(CONFIG_PATH): die('could not find app.conf, make sure it is in ~/.config/keyd/app.conf') -map = parse_config(CONFIG_PATH) +config = parse_config(CONFIG_PATH) ping_keyd() lock() -def normalize_identifier(s): - return re.sub('[^A-Za-z0-9]', '-', s).strip('-').lower() +def lookup_bindings(cls, title): + for cexp, texp, bindings in config: + if fnmatch(cls, cexp) and fnmatch(title, texp): + return bindings + + return [] + +def normalize_class(s): + return re.sub('[^A-Za-z0-9]+', '-', s).strip('-').lower() + +def normalize_title(s): + return re.sub('[^A-Za-z0-9@:/?.]+', '-', s).strip('-').lower() last_mtime = os.path.getmtime(CONFIG_PATH) -def on_window_change(cls, name): +def on_window_change(cls, title): global last_mtime - global map + global config + + cls = normalize_class(cls) + title = normalize_title(title) - cls = normalize_identifier(cls) - name = normalize_identifier(name) mtime = os.path.getmtime(CONFIG_PATH) if mtime != last_mtime: print(CONFIG_PATH + ': Updated, reloading config...') - map = parse_config(CONFIG_PATH) + config = parse_config(CONFIG_PATH) last_mtime = mtime if not args.quiet: - print(f'Active window class: {cls}') + print(f'Active window: {cls}|{title}') - bindings = map.get(cls, []) + bindings = lookup_bindings(cls, title) subprocess.run(['keyd', '-e', 'reset', *bindings])