|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | + |
| 3 | +import subprocess |
| 4 | +from collections import namedtuple |
| 5 | +from albert import * |
| 6 | + |
| 7 | +md_iid = "3.0" |
| 8 | +md_version = "0.6.0" |
| 9 | +md_name = "X Window Switcher" |
| 10 | +md_description = "Switch X11 Windows" |
| 11 | +md_license = "MIT" |
| 12 | +md_url = "https://github.com/albertlauncher/python/tree/main/x_window_switcher" |
| 13 | +md_bin_dependencies = "wmctrl" |
| 14 | +md_authors = ["Ed Perez", "Manuel S.", "dshoreman", "nopsqi"] |
| 15 | + |
| 16 | +Window = namedtuple("Window", ["wid", "desktop", "wm_class", "host", "wm_name"]) |
| 17 | + |
| 18 | + |
| 19 | +class Plugin(PluginInstance, TriggerQueryHandler): |
| 20 | + def __init__(self): |
| 21 | + PluginInstance.__init__(self) |
| 22 | + TriggerQueryHandler.__init__(self) |
| 23 | + |
| 24 | + # Check for X session and wmctrl availability |
| 25 | + try: |
| 26 | + subprocess.check_call(["wmctrl", "-m"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
| 27 | + except FileNotFoundError: |
| 28 | + raise Exception("wmctrl not found. Please install wmctrl.") |
| 29 | + except subprocess.CalledProcessError: |
| 30 | + raise Exception("Unable to communicate with X11 window manager. This plugin requires a running X session.") |
| 31 | + |
| 32 | + def defaultTrigger(self): |
| 33 | + return 'w ' |
| 34 | + |
| 35 | + def handleTriggerQuery(self, query): |
| 36 | + try: |
| 37 | + for line in subprocess.check_output(['wmctrl', '-l', '-x']).splitlines(): |
| 38 | + win = Window(*parseWindow(line)) |
| 39 | + |
| 40 | + if win.desktop == "-1": |
| 41 | + continue |
| 42 | + |
| 43 | + win_instance, win_class = win.wm_class.replace(' ', '-').split('.', 1) |
| 44 | + |
| 45 | + m = Matcher(query.string) |
| 46 | + if not query.string or m.match(win_instance + ' ' + win_class + ' ' + win.wm_name): |
| 47 | + query.add(StandardItem( |
| 48 | + id="%s%s" % (md_name, win.wm_class), |
| 49 | + iconUrls=["xdg:%s" % win_instance], |
| 50 | + text="%s - Desktop %s" % (win_class.replace('-', ' '), win.desktop), |
| 51 | + subtext=win.wm_name, |
| 52 | + actions=[Action("switch", |
| 53 | + "Switch Window", |
| 54 | + lambda w=win: runDetachedProcess(["wmctrl", '-i', '-a', w.wid])), |
| 55 | + Action("move", |
| 56 | + "Move window to this desktop", |
| 57 | + lambda w=win: runDetachedProcess(["wmctrl", '-i', '-R', w.wid])), |
| 58 | + Action("close", |
| 59 | + "Close the window gracefully.", |
| 60 | + lambda w=win: runDetachedProcess(["wmctrl", '-c', w.wid]))] |
| 61 | + )) |
| 62 | + except subprocess.CalledProcessError as e: |
| 63 | + warning(f"Error executing wmctrl: {str(e)}") |
| 64 | + |
| 65 | + |
| 66 | +def parseWindow(line): |
| 67 | + win_id, desktop, rest = line.decode().split(None, 2) |
| 68 | + win_class, rest = rest.split(' ', 1) |
| 69 | + host, title = rest.strip().split(None, 1) |
| 70 | + |
| 71 | + return [win_id, desktop, win_class, host, title] |
0 commit comments