justbuchanan / i3scripts

My scripts for enhancing i3wm
Other
163 stars 46 forks source link

autoname_workspaces runtime error #26

Closed fn-ix closed 5 years ago

fn-ix commented 5 years ago

Hi!

When trying to execute the script, I get the following error:

$ python .i3/autoname_workspaces.py
Traceback (most recent call last):
  File ".i3/autoname_workspaces.py", line 223, in <module>
    ensure_window_icons_lowercase()
  File ".i3/autoname_workspaces.py", line 139, in ensure_window_icons_lowercase
    for cls in WINDOW_ICONS:
RuntimeError: dictionary changed size during iteration

I've been using this script for about a month now without any hitches, and since I don't know any Python, I have no idea as to the problem. I'd appreciate any help! :)

justbuchanan commented 5 years ago

I'm not sure offhand since I haven't run into this issue yet, although I can see how it might happen.

Which version/commit of these scripts are you using? And if you've modified autoname_workspaces.py, could you upload your modified copy?

fn-ix commented 5 years ago

Thanks for the quick reply! Sure, here you go. I started using it about a month ago, so it should be the most recent version. The only modifications that I've made are to the icon list.

Modified copy ``` #!/usr/bin/env python3 # # github.com/justbuchanan/i3scripts # # 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 editing the WINDOW_ICONS list below. # # It also re-numbers workspaces in ascending order with one skipped number # between monitors (leaving a gap for a new workspace to be created). By # default, i3 workspace numbers are sticky, so they quickly get out of order. # # Dependencies # * xorg-xprop - install through system package manager # * i3ipc - install with pip # * fontawesome - install with pip # # Installation: # * Download this repo and place it in ~/.config/i3/ (or anywhere you want) # * Add "exec_always ~/.config/i3/i3scripts/autoname_workspaces.py &" to your i3 config # * Restart i3: $ i3-msg restart # # Configuration: # The default i3 config's keybindings reference workspaces by name, which is an # issue when using this script because the "names" are constantly changing to # include window icons. Instead, you'll need to change the keybindings to # reference workspaces by number. Change lines like: # bindsym $mod+1 workspace 1 # To: # bindsym $mod+1 workspace number 1 import argparse import i3ipc import logging import signal import sys import fontawesome as fa from util import * # Add icons here for common programs you use. The keys are the X window class # (WM_CLASS) names (lower-cased) and the icons can be any text you want to # display. # # Most of these are character codes for font awesome: # http://fortawesome.github.io/Font-Awesome/icons/ # # If you're not sure what the WM_CLASS is for your application, you can use # xprop (https://linux.die.net/man/1/xprop). Run `xprop | grep WM_CLASS` # then click on the application you want to inspect. WINDOW_ICONS = { 'alacritty': fa.icons['terminal'], 'atom': fa.icons['code'], 'banshee': fa.icons['play'], 'blender': fa.icons['cube'], 'chromium': fa.icons['chrome'], 'code-oss': fa.icons['code'], 'cura': fa.icons['cube'], 'darktable': fa.icons['image'], 'discord': fa.icons['comment'], 'eclipse': fa.icons['code'], 'emacs': fa.icons['code'], 'eog': fa.icons['image'], 'epdfview': fa.icons['file-pdf'], 'evince': fa.icons['file-pdf'], 'evolution': fa.icons['envelope'], 'feedreader': fa.icons['rss-square'], 'feh': fa.icons['image'], 'file-roller': fa.icons['compress'], 'firefox': fa.icons['firefox'], 'firefox-esr': fa.icons['firefox'], 'gcolor2': fa.icons['eye-dropper'], 'geary': fa.icons['envelope'], 'gimp': fa.icons['paint-brush'], 'gnome-font-viewer': fa.icons['font'], 'gnome-control-center': fa.icons['toggle-on'], 'gnome-terminal-server': fa.icons['terminal'], 'google-chrome': fa.icons['chrome'], 'gpick': fa.icons['eye-dropper'], 'gucharmap': fa.icons['font'], 'imv': fa.icons['image'], 'java': fa.icons['code'], 'jetbrains-studio': fa.icons['code'], 'keepassxc': fa.icons['key'], 'keybase': fa.icons['key'], 'kicad': fa.icons['microchip'], 'kitty': fa.icons['terminal'], 'libreoffice': fa.icons['file-alt'], 'libreoffice-writer': fa.icons['file-alt'], 'lollypop': fa.icons['music'], 'lua5.1': fa.icons['moon'], '/usr/lib/marktext/resources/app.asar': fa.icons['file-alt'], '/usr/lib/marktext/marktext': fa.icons['file-alt'], 'marktext': fa.icons['file-alt'], 'mousepad': fa.icons['file-alt'], 'mpv': fa.icons['tv'], 'mupdf': fa.icons['file-pdf'], 'mysql-workbench-bin': fa.icons['database'], 'nautilus': fa.icons['copy'], 'nemo': fa.icons['copy'], 'openscad': fa.icons['cube'], 'pavucontrol': fa.icons['volume-up'], 'pamac-manager': fa.icons['shield-alt'], 'pcmanfm': fa.icons['folder'], 'postman': fa.icons['space-shuttle'], 'rhythmbox': fa.icons['play'], 'slack': fa.icons['slack'], 'slic3r.pl': fa.icons['cube'], 'smplayer': fa.icons['video'], '/opt/spotify/spotify': fa.icons['spotify'], 'spotify': fa.icons['spotify'], 'Spotify': fa.icons['spotify'], 'steam': fa.icons['steam'], 'subl': fa.icons['file-alt'], 'subl3': fa.icons['file-alt'], 'sublime_text': fa.icons['file-alt'], 'thunar': fa.icons['copy'], 'thunderbird': fa.icons['envelope'], 'totem': fa.icons['play'], 'transmission-gtk': fa.icons['download'], 'urxvt': fa.icons['terminal'], 'viewnior': fa.icons['image'], 'xfce4-terminal': fa.icons['terminal'], 'xournal': fa.icons['file-alt'], 'yelp': fa.icons['code'], 'zenity': fa.icons['window-maximize'], } # This icon is used for any application not in the list above DEFAULT_ICON = fa.icons['dot-circle'] # Global setting that determines whether workspaces will be automatically # re-numbered in ascending order with a "gap" left on each monitor. This is # overridden via command-line flag. RENUMBER_WORKSPACES = True def ensure_window_icons_lowercase(): for cls in WINDOW_ICONS: if cls != cls.lower(): WINDOW_ICONS[cls.lower()] = WINDOW_ICONS[cls] del WINDOW_ICONS[cls] def icon_for_window(window): # Try all window classes and use the first one we have an icon for classes = xprop(window.window, 'WM_CLASS') if classes != None and len(classes) > 0: for cls in classes: cls = cls.lower() # case-insensitive matching if cls in WINDOW_ICONS: return WINDOW_ICONS[cls] logging.info( 'No icon available for window with classes: %s' % str(classes)) return DEFAULT_ICON # renames all workspaces based on the windows present # also renumbers them in ascending order, with one gap left between monitors # for example: workspace numbering on two monitors: [1, 2, 3], [5, 6] def rename_workspaces(i3): ws_infos = i3.get_workspaces() prev_output = None n = 1 for ws_index, workspace in enumerate(i3.get_tree().workspaces()): ws_info = ws_infos[ws_index] name_parts = parse_workspace_name(workspace.name) new_icons = ' '.join([icon_for_window(w) for w in workspace.leaves()]) # As we enumerate, leave one gap in workspace numbers between each monitor. # This leaves a space to insert a new one later. if ws_info.output != prev_output and prev_output != None: n += 1 prev_output = ws_info.output # optionally renumber workspace new_num = n if RENUMBER_WORKSPACES else name_parts.num n += 1 new_name = construct_workspace_name( NameParts( num=new_num, shortname=name_parts.shortname, icons=new_icons)) if workspace.name == new_name: continue i3.command( 'rename workspace "%s" to "%s"' % (workspace.name, new_name)) # Rename workspaces to just numbers and shortnames, removing the icons. def on_exit(i3): for workspace in i3.get_tree().workspaces(): name_parts = parse_workspace_name(workspace.name) new_name = construct_workspace_name( NameParts( num=name_parts.num, shortname=name_parts.shortname, icons=None)) if workspace.name == new_name: continue i3.command( 'rename workspace "%s" to "%s"' % (workspace.name, new_name)) i3.main_quit() sys.exit(0) if __name__ == '__main__': parser = argparse.ArgumentParser( description= "Rename workspaces dynamically to show icons for running programs.") parser.add_argument( '--norenumber_workspaces', action='store_true', default=False, help= "Disable automatic workspace re-numbering. By default, workspaces are automatically re-numbered in ascending order." ) args = parser.parse_args() RENUMBER_WORKSPACES = not args.norenumber_workspaces logging.basicConfig(level=logging.INFO) ensure_window_icons_lowercase() i3 = i3ipc.Connection() # Exit gracefully when ctrl+c is pressed for sig in [signal.SIGINT, signal.SIGTERM]: signal.signal(sig, lambda signal, frame: on_exit(i3)) rename_workspaces(i3) # Call rename_workspaces() for relevant window events def event_handler(i3, e): if e.change in ['new', 'close', 'move']: rename_workspaces(i3) i3.on('window', event_handler) i3.on('workspace::move', event_handler) i3.main() ```
justbuchanan commented 5 years ago

The issue was that there were two entries for spotify: one "spotify" and one "Spotify". I improved the lower-casing part of the script so that it can handle this without issue.

Try out the latest version when you get a chance and let me know if it still has issues.

fn-ix commented 5 years ago

Thanks! Yeah, I kept having problems with Spotify, as it doesn't seem to define its WM_CLASS properly.