#!/usr/bin/env python3 # Taken from https://gist.github.com/bgavran/462c9391a49a835546f9bf9c15ce56f9 # and slightly modified by me (https://github.com/SijmenSchoon). # # This script listens for i3 events and updates workspace names to show icons # for running programs. It contains icons for a few programs, but more can # easily be added by inserting them into XORG_ICONS below. # # Dependencies # * xorg-xprop - install through system package manager # * i3ipc - install with pip import i3ipc import subprocess as proc import re # Add icons here for common programs you use. The keys are the X window class # (WM_CLASS) names and the icons can be any text you want to display. However # most of these are character codes for font awesome: # http://fortawesome.github.io/Font-Awesome/icons/ FA_BEZIER_CURVE = "\uF55B" FA_BOOK = "\uF02D" FA_CALENDAR = "\uF133" FA_CHROME = "\uF268" FA_CODE = "\uF121" FA_CODE_BRANCH = "\uF126" FA_COMMENT_ALT = "\uF27A" FA_CUBE = "\uF1B2" FA_DISCORD = "\uF392" FA_DOWNLOAD = "\uF019" FA_ENVELOPE = "\uF0E0" FA_FILE_PDF_O = "\uF1C1" FA_FILE_TEXT_O = "\uF0F6" FA_FILES_O = "\uF0C5" FA_FIREFOX = "\uF269" FA_FOLDER = "\uF07C" FA_GAMEPAD = "\uF11B" FA_PAINT_BRUSH = "\uF1FC" FA_PICTURE_O = "\uF03E" FA_SPOTIFY = "\uF1BC" FA_STEAM = "\uF1B6" FA_TELEGRAM = "\uF3FE" FA_TERMINAL = "\uF120" FA_VECTOR_SQUARE = "\uF5CB" FA_VOLUME_UP = "\uF028" FA_YOUTUBE = "\uF167" XORG_ICONS = { "Blender": FA_CUBE, "Cadence": FA_VOLUME_UP, "Carla2": FA_VOLUME_UP, "chromium": FA_CHROME, "code": FA_CODE, "code-oss": FA_CODE, "discord": FA_DISCORD, "emacs": FA_CODE, "evince": FA_FILE_PDF_O, "feh": FA_PICTURE_O, "firefox": FA_FIREFOX, "gimp-2.10": FA_PAINT_BRUSH, "gnome-calendar": FA_CALENDAR, "Gnome-terminal": FA_TERMINAL, "google-chrome": FA_CHROME, "inkscape": FA_PAINT_BRUSH, "jetbrains-clion": FA_CODE, "jetbrains-goland": FA_CODE, "jetbrains-pycharm": FA_CODE, "jetbrains-rider": FA_CODE, "jetbrains-webstorm": FA_CODE, "libreoffice": FA_FILE_TEXT_O, "mupdf": FA_FILE_PDF_O, "nautilus": FA_FOLDER, "org.gnome.Nautilus": FA_FOLDER, "Prusa-slicer": FA_CUBE, "pulseeffects": FA_VOLUME_UP, "slic3r-gui": FA_CUBE, "slic3r-prusa3d": FA_CUBE, "spotify": FA_SPOTIFY, "Steam": FA_STEAM, "subl": FA_CODE, "subl3": FA_CODE, "Sublime_merge": FA_CODE_BRANCH, "telegram-desktop": FA_TELEGRAM, "Thunar": FA_FOLDER, "thunar": FA_FOLDER, "transmission-gtk": FA_DOWNLOAD, "urxvt": FA_TERMINAL, "youtube-music-desktop-app": FA_YOUTUBE, "xfce4-terminal": FA_TERMINAL, } WAYLAND_ICONS = { "Alacritty": FA_TERMINAL, "Blender": FA_CUBE, "chromium": FA_CHROME, "code": FA_CODE, "code-oss": FA_CODE, "emacs": FA_CODE, "evince": FA_FILE_PDF_O, "feh": FA_PICTURE_O, "firefox": FA_FIREFOX, "gimp-2.10": FA_PAINT_BRUSH, "gnome-calendar": FA_CALENDAR, "gnome-terminal-server": FA_TERMINAL, "google-chrome": FA_CHROME, "inkscape": FA_PAINT_BRUSH, "jetbrains-clion": FA_CODE, "jetbrains-pycharm": FA_CODE, "jetbrains-rider": FA_CODE, "jetbrains-webstorm": FA_CODE, "libreoffice": FA_FILE_TEXT_O, "mupdf": FA_FILE_PDF_O, "nautilus": FA_FOLDER, "org.gnome.Nautilus": FA_FOLDER, "polari": FA_COMMENT_ALT, "pulseeffects": FA_VOLUME_UP, "slic3r-gui": FA_CUBE, "slic3r-prusa3d": FA_CUBE, "spotify": FA_SPOTIFY, "Steam": FA_STEAM, "subl": FA_CODE, "subl3": FA_CODE, "sublime_merge": FA_CODE_BRANCH, "telegramdesktop": FA_TELEGRAM, "evolution": FA_ENVELOPE, "termite": FA_TERMINAL, "thunar": FA_FOLDER, "transmission-gtk": FA_DOWNLOAD, "urxvt": FA_TERMINAL, "xfce4-terminal": FA_TERMINAL, } TERMINAL_APPS = { "aerc": FA_ENVELOPE, "emacs": FA_CODE, "emacsclient": FA_CODE, "vim": FA_CODE, "weechat": "\uf075", } i3 = i3ipc.Connection() def xprop(win_id, property): """ Return an array of the values for the given property from xprop. Requires xorg-xprop to be installed. """ try: prop = proc.check_output( ["xprop", "-id", str(win_id), property], stderr=proc.DEVNULL ) prop = prop.decode("utf-8") return re.findall('"([^"]+)"', prop) except proc.CalledProcessError: print(f"Unable to get property for window '{win_id}'") return None def icon_for_window(window): if window.app_id == "Alacritty": app = window.name.split()[0] if app in TERMINAL_APPS: return TERMINAL_APPS[app] if window.app_id: # Wayland if window.app_id in WAYLAND_ICONS: return WAYLAND_ICONS[window.app_id] else: print(f"No icon available for app ID '{window.app_id}'") return "*" classes = xprop(window.window, "WM_CLASS") if classes: for cls in classes: if cls in XORG_ICONS: return XORG_ICONS[cls] if "Minecraft" in cls: return FA_GAMEPAD print("No icon available for window with classes: %s" % str(classes)) return "*" def rename(): """Rename all workspaces based on the present windows.""" for workspace in i3.get_tree().workspaces(): icons = " ".join(icon_for_window(w) for w in workspace.leaves()) new_name = f"{workspace.num}" if icons: new_name += f": {icons}" i3.command(f'rename workspace "{workspace.name}" to "{new_name}"') def on_change(i3, e): """Call rename() on relevant window events.""" print(f"{e.change}") if e.change in ["new", "close", "move", "title"]: rename() def main(): rename() i3.on("window", on_change) i3.main() if __name__ == "__main__": main()