From 6903af7a438ad42a84e9c3039ecaf848ff012ab1 Mon Sep 17 00:00:00 2001 From: Matt Singleton Date: Wed, 20 Mar 2024 20:39:44 -0500 Subject: script to name workspaces based on xdg metadata --- sway-de/install.sh | 4 +- sway-de/xdg-names/.config/xdg-names.ini | 24 ++++ sway-de/xdg-names/.local/bin/xdg-names.py | 157 +++++++++++++++++++++ .../.local/share/systemd/user/xdg-names.service | 14 ++ 4 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 sway-de/xdg-names/.config/xdg-names.ini create mode 100755 sway-de/xdg-names/.local/bin/xdg-names.py create mode 100644 sway-de/xdg-names/.local/share/systemd/user/xdg-names.service diff --git a/sway-de/install.sh b/sway-de/install.sh index 6403618..ccef98f 100755 --- a/sway-de/install.sh +++ b/sway-de/install.sh @@ -70,10 +70,10 @@ fi $STOWCMD \ --dir "$(pwd)" \ --target "$HOME" \ - foot mako rofi sway swaylock udiskie waybar + foot mako rofi sway swaylock udiskie waybar xdg-names # enable new systemd units and start them if sway is running -for unit in udiskie.service; do +for unit in udiskie.service xdg-names.service; do if systemctl --user is-active sway-session.target; then systemctl --user enable --now "$(basename "$unit")" else diff --git a/sway-de/xdg-names/.config/xdg-names.ini b/sway-de/xdg-names/.config/xdg-names.ini new file mode 100644 index 0000000..529df80 --- /dev/null +++ b/sway-de/xdg-names/.config/xdg-names.ini @@ -0,0 +1,24 @@ +[fallback] +icon=_ +[desktop] +1= +2= +[app] +xwayland=X +foot= +shotwell= +[xdg_main] +Audio= +Video= +Development= +Education= +Game= +Graphics= +Network= +Office= +Science= +Settings= +System= +Utility= +[xdg_additional] +WebBrowser= diff --git a/sway-de/xdg-names/.local/bin/xdg-names.py b/sway-de/xdg-names/.local/bin/xdg-names.py new file mode 100755 index 0000000..fb4767a --- /dev/null +++ b/sway-de/xdg-names/.local/bin/xdg-names.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 + +""" +Rename i3 and Sway workspaces based on the focused app's XDG categories. + + Copyright (C) 2024 Matt Singleton + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Adapted from this script: + https://github.com/nwg-piotr/swayinfo/blob/master/wsdnames.py + +Which was in turn based on this example: + https://github.com/altdesktop/i3ipc-python/blob/master/README.rst + +Dependencies: python-i3ipc>=2.0.1 (i3ipc-python), python-xlib + +Pay attention to the fact that your workspaces need to be numbered, not named, +for the script to work. + +Use: + bindsym $mod+1 workspace number 1 + +instead of: + bindsym $mod+1 workspace 1 + +in your ~/.config/sway/config or ~/.config/i3/config file. +""" + +import argparse +import configparser +import logging +import os +import os.path +import traceback + +from i3ipc import Connection, Event + + +def get_xdg_categories(app_id): + path = os.path.join("/usr/share/applications", f"{app_id}.desktop") + try: + config = configparser.ConfigParser() + config.read(path) + return config["Desktop Entry"]["Categories"].split(";") + except Exception: + return [] + + +def glyph(ws_num, app_id=None): + """ + Return a single glyph based on the app_id, if given, + then a default based on the workspace number, + and finally an empty glyph. + """ + global CONFIG_MTIME + latest_mtime = os.stat(CONFIG_PATH).st_mtime + if CONFIG_MTIME < latest_mtime: + CONFIG_MTIME = latest_mtime + CONFIG.read(CONFIG_PATH) + if app_id is not None: + if app_id in CONFIG["app"]: + return CONFIG["app"][app_id] + categories = get_xdg_categories(app_id) + for c in categories: + if c in CONFIG["xdg_additional"]: + return CONFIG["xdg_additional"][c] + for c in categories: + if c in CONFIG["xdg_main"]: + return CONFIG["xdg_main"][c] + logger.debug(f"no xdg icon found for: {app_id}") + ws_num = str(ws_num) + if ws_num in CONFIG["desktop"]: + return CONFIG["desktop"][ws_num] + return CONFIG["fallback"]["icon"] + + +def get_ws_name(ws_num, app_id=None): + ws_glyph = glyph(ws_num, app_id) + return f"{ws_num}:{ws_glyph}" + + +def assign_generic_name(i3, e): + """ + Name the workspace after the focused window name + """ + try: + if e.change == 'rename': + return + if e.change in ['new', 'move']: + # if a change results in a window spawning in an unfocused + # state (i.e. on another workspace), we need to look + # it up by id + con = i3.get_tree().find_by_id(e.container.id) + else: + con = i3.get_tree().find_focused() + if con.type == 'workspace': + # empty workspace + ws_num = con.workspace().num + name = get_ws_name(ws_num) + i3.command(f'rename workspace to "{name}"') + else: + old_name = con.workspace().name + # assume windows without an app_id are xwayland + app_id = "xwayland" if con.app_id is None else con.app_id + name = get_ws_name(con.workspace().num, app_id) + i3.command(f'rename workspace "{old_name}" to "{name}"') + except Exception as e: + logger.debug("".join(traceback.format_exception(e))) + + +parser = argparse.ArgumentParser() +parser.add_argument("--debug", action="store_true") +args = parser.parse_args() + +log_format = "%(levelname)s %(name)s: %(message)s" +if args.debug is True: + logging.basicConfig(level=logging.DEBUG, format=log_format) +else: + logging.basicConfig(level=logging.INFO, format=log_format) +logger = logging.getLogger(__name__) + +CONFIG_MTIME = 0 +CONFIG_PATH = os.path.join( + os.environ.get( + "XDG_CONFIG_HOME", + os.path.join(os.environ["HOME"], ".config"), + ), + "xdg-names.ini", +) +CONFIG = configparser.ConfigParser() + +# Create the Connection object that can be used to send commands and +# subscribe to events. +i3 = Connection() + +# Subscribe to events +i3.on(Event.WORKSPACE_FOCUS, assign_generic_name) +i3.on(Event.WINDOW_FOCUS, assign_generic_name) +i3.on(Event.WINDOW_TITLE, assign_generic_name) +i3.on(Event.WINDOW_CLOSE, assign_generic_name) +i3.on(Event.WINDOW_NEW, assign_generic_name) +i3.on(Event.WINDOW_MOVE, assign_generic_name) +i3.on(Event.BINDING, assign_generic_name) + +i3.main() diff --git a/sway-de/xdg-names/.local/share/systemd/user/xdg-names.service b/sway-de/xdg-names/.local/share/systemd/user/xdg-names.service new file mode 100644 index 0000000..799431b --- /dev/null +++ b/sway-de/xdg-names/.local/share/systemd/user/xdg-names.service @@ -0,0 +1,14 @@ +[Unit] +Description=Rename Sway workspaces based on XDG metadata +PartOf=sway-session.target + +[Service] +Type=simple +ExecStart=%h/.local/bin/xdg-names.py --debug +ExecStop=/bin/kill -2 $MAINPID +Restart=on-failure +RestartSec=1 +TimeoutStopSec=10 + +[Install] +WantedBy=sway-session.target -- cgit v1.2.3