From 43ab22dee5b5cb1ab80ba5d895d4c834a424d724 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Sun, 4 Jul 2021 19:08:48 +0200 Subject: [PATCH] i3-companion: add a companion for i3 This is a Python script implementing what's missing from i3. Currently, it will only rename workspace to add icons with their content. --- bin/i3-companion | 116 ++++++++++++++++++++++++++ dotfiles/polybar.conf | 8 +- dotfiles/systemd/i3-companion.service | 7 ++ dotfiles/systemd/i3.service | 1 + dotfiles/systemd/xsession.target | 1 + 5 files changed, 129 insertions(+), 4 deletions(-) create mode 100755 bin/i3-companion create mode 100644 dotfiles/systemd/i3-companion.service diff --git a/bin/i3-companion b/bin/i3-companion new file mode 100755 index 0000000..aeb01c8 --- /dev/null +++ b/bin/i3-companion @@ -0,0 +1,116 @@ +#!/usr/bin/python3 + +"""Personal i3 companion + +This script will listen to i3 events and react accordingly. Currently, +it will rename workspaces using icons according to their content. + +""" + +import argparse +import logging +import logging.handlers +import os +import sys +import i3ipc +import re + +logger = logging.getLogger(os.path.splitext(os.path.basename(sys.argv[0]))[0]) + + +class CustomFormatter(argparse.RawDescriptionHelpFormatter, + argparse.ArgumentDefaultsHelpFormatter): + pass + + +def parse_args(args=sys.argv[1:]): + """Parse arguments.""" + parser = argparse.ArgumentParser( + description=sys.modules[__name__].__doc__, + formatter_class=CustomFormatter) + + g = parser.add_mutually_exclusive_group() + g.add_argument("--debug", "-d", action="store_true", + default=False, + help="enable debugging") + g.add_argument("--silent", "-s", action="store_true", + default=False, + help="don't log") + + return parser.parse_args(args) + + +def setup_logging(options): + """Configure logging.""" + root = logging.getLogger("") + root.setLevel(logging.WARNING) + logger.setLevel(options.debug and logging.DEBUG or logging.INFO) + if not options.silent: + ch = logging.StreamHandler() + ch.setFormatter(logging.Formatter( + "%(levelname)s[%(name)s] %(message)s")) + root.addHandler(ch) + + +# See https://fontawesome.com/v5.15/icons +application_icons = { + "chromium": "", + "emacs": "", + "firefox": "", + "gimp": "", + "google-chrome": "", + "spotify": "", + "vbeterm": "", + "NOMATCH": "" +} + + +def application_icon(window): + """Get application icon for a window.""" + for attr in ('name', 'window_title', + 'window_instance', 'window_class'): + name = getattr(window, attr, None) + if name is None: + continue + for k, v in application_icons.items(): + if re.match(k, name, re.IGNORECASE): + logger.debug(f"in {attr}, found '{name}', matching {k}") + return v + return application_icons["NOMATCH"] + + +def workspace_rename(i3, event): + """Rename workspaces using icons to match what's inside it.""" + workspaces = i3.get_tree().workspaces() + commands = [] + for workspace in workspaces: + icons = set() + for window in workspace.leaves(): + icon = application_icon(window) + if icon is not None: + icons.add(icon) + new_name = f"{workspace.num}:{'|'.join(icons)}" + if workspace.name != new_name: + logger.debug(f"rename workspace {workspace.num}") + command = f'rename workspace "{workspace.name}" to "{new_name}"' + commands.append(command) + i3.command(';'.join(commands)) + + +if __name__ == "__main__": + options = parse_args() + setup_logging(options) + + try: + i3 = i3ipc.Connection() + + # Rename workspace depending on what is inside + for event in ('window::move', 'window::new', + 'window::title', 'window::close'): + i3.on(event, workspace_rename) + + i3.main() + except Exception as e: + logger.exception("%s", e) + sys.exit(1) + sys.exit(0) diff --git a/dotfiles/polybar.conf b/dotfiles/polybar.conf index 885bb19..577f8f6 100644 --- a/dotfiles/polybar.conf +++ b/dotfiles/polybar.conf @@ -43,15 +43,15 @@ enable-scroll = false label-mode-background = ${colors.highlight} label-mode-padding = 1 -label-focused = %index% +label-focused = %name% label-focused-background = ${colors.highlight} label-focused-padding = 1 -label-unfocused = %index% +label-unfocused = %name% label-unfocused-padding = 1 -label-visible = %index% +label-visible = %name% label-visible-background = ${self.label-focused-background} label-visible-padding = ${self.label-focused-padding} -label-urgent = %index% +label-urgent = %name% label-urgent-background = #a00000 label-urgent-padding = 1 diff --git a/dotfiles/systemd/i3-companion.service b/dotfiles/systemd/i3-companion.service new file mode 100644 index 0000000..0f1579d --- /dev/null +++ b/dotfiles/systemd/i3-companion.service @@ -0,0 +1,7 @@ +[Unit] +Description=i3 companion +PartOf=graphical-session.target + +[Service] +ExecStart=%h/.config/i3/bin/i3-companion +Restart=on-failure diff --git a/dotfiles/systemd/i3.service b/dotfiles/systemd/i3.service index f4303aa..da7870c 100644 --- a/dotfiles/systemd/i3.service +++ b/dotfiles/systemd/i3.service @@ -8,6 +8,7 @@ After=tmux.service After=ssh-agent.service Before=wallpaper.service Before=polybar.service +Before=i3-companion.service [Service] ExecStart=/usr/bin/i3 diff --git a/dotfiles/systemd/xsession.target b/dotfiles/systemd/xsession.target index c578f8f..6d27004 100644 --- a/dotfiles/systemd/xsession.target +++ b/dotfiles/systemd/xsession.target @@ -4,6 +4,7 @@ BindsTo=graphical-session.target Wants=autorandr.service Wants=dunst.socket Wants=i3.service +Wants=i3-companion.service Wants=inputplug.service Wants=misc-x.service Wants=picom.service