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.
This commit is contained in:
Vincent Bernat 2021-07-04 19:08:48 +02:00
parent 662db00e36
commit 43ab22dee5
5 changed files with 129 additions and 4 deletions

116
bin/i3-companion Executable file
View file

@ -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)

View file

@ -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

View file

@ -0,0 +1,7 @@
[Unit]
Description=i3 companion
PartOf=graphical-session.target
[Service]
ExecStart=%h/.config/i3/bin/i3-companion
Restart=on-failure

View file

@ -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

View file

@ -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