import json import os import random import socket import subprocess from typing import Optional from libqtile.backend.base import Window from libqtile.backend.wayland.layer import LayerStatic from libqtile.backend.wayland.xwindow import XWindow as WaylandXWindow, XStatic as WaylandXStatic from libqtile.backend.x11.window import XWindow as XorgXWindow # Initialize logging from libqtile.log_utils import logger logger.warning("Importing qtile theme requirements...") from libqtile.config import Key, Screen, Group, Drag, Click, Match from libqtile.command import lazy from libqtile import layout, bar, widget, qtile from qtile_extras import widget as extra_widget logger.warning("Importing theme util functions...") # Import theme util functions from xcffib.xproto import WindowError logger.warning("Importing kuro utils...") import kuro.utils.widgets from kuro.utils import general as utils logger.warning("Importing variables and other utils...") # Import variables from kuro.base import BaseTheme from kuro.utils.general import display_wm_class, test_popups from kuro.utils import layouts as kuro_layouts logger.warning("Importing configuration...") try: from kuro.config import Config except ImportError: try: from kuro.baseconfig import BaseConfig as Config except ImportError: Config = None raise ImportError("Could not load theme Config or BaseConfig!") logger.warning("Imports done") class Kuro(BaseTheme): # Shorthand for modifier key mod = Config.get("modifier", "mod4") # Screen count num_screens = 0 # Top bars topbars = [] # Static windows static_windows = [] # Current wallpaper path current_wallpaper = None # Window manager name wmname = "qtile" startup_completed = False autostart_app_rules = {} # Floating layout override floating_layout = kuro_layouts.KuroFloating( border_width=0, border_focus="#000000", border_normal="#000000", float_rules=[ # Run the utility of `xprop` to see the wm class and name of an X client. *layout.Floating.default_float_rules, Match(wm_class='confirmreset'), # gitk Match(wm_class='makebranch'), # gitk Match(wm_class='maketag'), # gitk Match(wm_class='ssh-askpass'), # ssh-askpass Match(title='branchdialog'), # gitk Match(title='pinentry'), # GPG key password entry Match(title='origin.exe', wm_class='Wine'), # Wine Origin game launcher ] ) def initialize(self): # Update color scheme logger.warning("Initializing colorscheme...") self.initialize_colorscheme() logger.warning("Initializing superclass...") super(Kuro, self).initialize() logger.warning("Updating keys for groups and layouts...") self.update_keys() def init_keys(self): logger.warning("Initializing keys") return [ # Switch between windows in current stack pane Key([self.mod], "k", lazy.layout.down()), Key([self.mod], "j", lazy.layout.up()), # Move windows up or down in current stack Key([self.mod, "control"], "k", lazy.layout.shuffle_down()), Key([self.mod, "control"], "j", lazy.layout.shuffle_up()), # Switch window focus to other pane(s) of stack Key([self.mod], "space", lazy.layout.next()), # Swap panes of split stack Key([self.mod, "shift"], "space", lazy.layout.rotate()), # Fullscreen toggle Key([self.mod], 'f', lazy.window.toggle_fullscreen()), # Floating toggle Key([self.mod, "shift"], 'f', lazy.window.toggle_floating()), # Toggle between split and unsplit sides of stack. # Split = all windows displayed # Unsplit = 1 window displayed, like Max layout, but still with # multiple stack panes Key([self.mod, "shift"], "Return", lazy.layout.toggle_split()), # Super-Enter to start terminal Key([self.mod], "Return", lazy.spawn(Config.get('app_terminal', "xterm"))), # Super-R to start dmenu_run Key([self.mod], "r", lazy.spawn(Config.get('app_launcher', "dmenu_run"))), # Super-B to start webbrowser Key([self.mod], "b", lazy.spawn(Config.get('web_browser', "xterm links"))), # Super-T to start file manager Key([self.mod], "t", lazy.spawn(Config.get('file_manager', "thunar"))), # Super-Shift-R to start spawncmd Key([self.mod, "shift"], "r", lazy.spawncmd()), # Lock shortcut Key([self.mod], "l", lazy.spawn(Config.get('lock_command', "i3lock"))), # Display modes Key([self.mod], "Prior", lazy.spawn(Config.get('cmd_monitor_mode_3s144', 'true'))), Key([self.mod], "Next", lazy.spawn(Config.get('cmd_monitor_mode_3s60', 'true'))), Key([self.mod], "Home", lazy.spawn(Config.get('cmd_monitor_mode_day', 'true'))), Key([self.mod], "End", lazy.spawn(Config.get('cmd_monitor_mode_night', 'true'))), Key([self.mod], "Insert", lazy.spawn(Config.get('cmd_monitor_mode_alt', 'true'))), # Backlight keys Key([], "XF86MonBrightnessUp", lazy.spawn(Config.get('cmd_brightness_up', 'xbacklight -inc 10'))), Key([], "XF86MonBrightnessDown", lazy.spawn(Config.get('cmd_brightness_down', 'xbacklight -dec 10'))), # Media keys Key([], "XF86AudioPlay", lazy.spawn(Config.get('cmd_media_play', 'true'))), Key([], "XF86AudioNext", lazy.spawn(Config.get('cmd_media_next', 'true'))), Key([], "XF86AudioMute", lazy.spawn(Config.get('cmd_media_mute', 'true'))), Key([], "XF86AudioRaiseVolume", lazy.spawn(Config.get('cmd_media_volume_up', 'true'))), Key([], "XF86AudioLowerVolume", lazy.spawn(Config.get('cmd_media_volume_down', 'true'))), # Sleep key Key([], "XF86Sleep", lazy.spawn(Config.get('cmd_sleep', 'true'))), # Screenshot key Key([], "Print", lazy.spawn(Config.get('cmd_screenshot', 'xfce4-screenshooter'))), # Alt screenshot Key([self.mod], "Print", lazy.spawn(Config.get('cmd_alt_screenshot', 'xfce4-screenshooter'))), # Toggle between different layouts as defined below Key([self.mod], "Tab", lazy.next_layout()), # Kill the current window Key([self.mod], "w", lazy.window.kill()), # Restart/reload QTile (Restart only available in X11 backend) Key([self.mod, "control"], "r", lazy.restart() if qtile.core.name == "x11" else lazy.reload_config()), # Shutdown QTile Key([self.mod, "control"], "q", lazy.shutdown()), # Update wallpaper Key([self.mod, "control"], "w", lazy.function(self.set_random_wallpaper), lazy.function(self.update_colorscheme)), # Reload screen configuration Key([self.mod, "control"], "s", lazy.function(Config.get('cmd_reconfigure_screens', 'true'))), # Reload colorscheme Key([self.mod, "control"], "t", lazy.function(self.update_colorscheme)), # Toggle static windows Key([self.mod], "p", lazy.function(self.toggle_window_static)), ## # Debug keyboard shortcuts ## Key([self.mod, "control"], "c", lazy.function(display_wm_class)), # Show extensive window info Key([self.mod, "shift", "control"], "i", lazy.function(self.show_window_info)), # Spawn a popup, and despawn it after 3 seconds Key([self.mod, "control"], "p", lazy.function(test_popups)), ] def init_groups(self): logger.warning("Initializing groups") # http://fontawesome.io/cheatsheet groups = [ Group(""), Group(""), Group(""), Group(""), Group(""), Group(""), Group(""), Group(""), Group(""), Group("", layout='floating', layouts=[ layout.Floating( border_focus="#990000", border_normal="#440000" ) ]) ] return groups def init_layouts(self): logger.warning("Initializing layouts") return [ kuro_layouts.KuroWmii( theme=self, border_focus=Config.get('colour_border_focus', "#ffffff"), border_focus_stack=Config.get('colour_border_normal', "#777777"), border_normal=Config.get('colour_border_normal', "#777777"), border_normal_stack=Config.get('colour_border_normal', "#777777"), border_width=Config.get('width_border', "1"), grow_amount=Config.get('grow_amount', "5"), margin=Config.get('margin_layout', "0"), ), layout.Max(), layout.Zoomy( columnwidth=Config.get('width_zoomy_column', 150), margin=Config.get('margin_layout', "0"), ) ] def init_widget_defaults(self): logger.warning("Initializing widget_defaults") return { "font": Config.get('font_topbar', "Sans"), "fontsize": Config.get('fontsize_topbar', 16), "padding": 3, } def init_screens(self): logger.warning("Initializing screens") self.reinit_screens() return self.screens def init_mouse(self): logger.warning("Initializing mouse") # Drag floating layouts. mouse = [ Drag([self.mod], "Button1", lazy.window.set_position_floating(), start=lazy.window.get_position()), Drag([self.mod], "Button3", lazy.window.set_size_floating(), start=lazy.window.get_size()), Click([self.mod], "Button2", lazy.window.bring_to_front()) ] return mouse def initialize_colorscheme(self): colors = None if os.path.isfile("/home/kevin/.cache/wal/colors.json"): with open("/home/kevin/.cache/wal/colors.json", 'r') as f: try: colors = json.load(f)['colors'] except KeyError: colors = None if colors: # Update Config Config.foreground = colors['color15'] Config.background = colors['color0'] Config.highlight = colors['color3'] Config.inactive_light = colors['color4'] Config.inactive_dark = colors['color5'] Config.bar_background = colors['color1'] def reinit_screens(self): # Re-initalize bars self.topbars.clear() if qtile.core.name == "x11": self.num_screens = max(1, utils.get_screen_count()) else: self.num_screens = max(1, len(qtile.core.get_screen_info())) logger.warning(f"Detected {self.num_screens} screens.") screens = [] for x in range(self.num_screens): logger.warning("Initializing bars for screen {}".format(x)) topbar = self.build_bar_for_screen(x) self.topbars.append(topbar) screens.append(Screen(top=topbar)) self.screens.clear() for s in screens: self.screens.append(s) def update_keys(self): logger.warning("Updating keys") for i, g in enumerate(self.groups): if i == 9: i = -1 elif i > 9: continue # mod1 + number = switch to group self.keys.append( Key([self.mod], str(i + 1), lazy.group[g.name].toscreen()) ) # mod1 + shift + number = switch to & move focused window to group self.keys.append( Key([self.mod, "shift"], str(i + 1), lazy.window.togroup(g.name)) ) # Keys for the Wmii layout self.keys.extend([ Key([self.mod, "shift"], "j", lazy.layout.shuffle_down()), Key([self.mod, "shift"], "k", lazy.layout.shuffle_up()), Key([self.mod, "shift"], "h", lazy.layout.shuffle_left()), Key([self.mod, "shift"], "l", lazy.layout.shuffle_right()), Key([self.mod, "shift", "control"], "j", lazy.layout.grow_down()), Key([self.mod, "shift", "control"], "k", lazy.layout.grow_up()), Key([self.mod, "shift", "control"], "h", lazy.layout.grow_left()), Key([self.mod, "shift", "control"], "l", lazy.layout.grow_right()), Key([self.mod], "s", lazy.layout.toggle_split()), Key([self.mod], "n", lazy.layout.normalize()), ]) def build_bar_for_screen(self, screen_num): widgets = [ # Workspaces widget.GroupBox( active=Config.get('colour_groupbox_icon_active', '#ffffff'), borderwidth=Config.get('width_groupbox_border', 1), disable_drag=Config.get('bool_groupbox_disable_drag', False), font=Config.get('font_groupbox', 'Arial'), fontsize=Config.get('fontsize_groupbox', 15), highlight_color=Config.get("colour_groupbox_border_normal", '#444444'), inactive=Config.get('colour_groupbox_icon_inactive', '#444444'), rounded=Config.get('bool_groupbox_rounded_borders', True), this_current_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'), this_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'), margin=Config.get('margin_groupbox', 0) ), # Spawn prompt (only shown if activated) widget.Prompt(**self.widget_defaults), # Open window list widget.TaskList( border=Config.get('tasklist_border', '#ffffff'), borderwidth=Config.get('tasklist_borderwidth', 1), font=Config.get('tasklist_font', 'Arial'), fontsize=Config.get('tasklist_fontsize', 15), highlight_method=Config.get('tasklist_highlight_method', 'border'), max_title_width=Config.get('tasklist_max_title_width', 200), rounded=Config.get('tasklist_rounded', True), urgent_alert_method=Config.get('tasklist_urgent_alert_method', 'border'), urgent_border=Config.get('tasklist_urgent_border', '#ff0000'), margin=Config.get('margin_groupbox', 0), icon_size=Config.get('tasklist_iconsize', 17), theme_mode=Config.get('tasklist_thememode', 'preferred'), theme_path=Config.get('tasklist_themepath', '/usr/share/icons/Papirus-Dark'), txt_floating=" ", txt_maximized=" ", txt_minimized=" ", ) ] # Media widget(s) widgets.extend([ # An MPRIS widget that shows the media play status as an icon. widget.Mpris2( font=Config.get('font_groupbox', 'Arial'), fontsize=Config.get('fontsize_groupbox', 15), format="", scroll=False, playing_text="", paused_text="", stopped_text="", no_metadata_text="", name=f"media_icon{screen_num}", mouse_callbacks={ "Button1": lazy.widget[f"media_icon{screen_num}"].play_pause(), "Button3": lazy.widget[f"media_icon{screen_num}"].next(), "Button4": lambda: None, "Button5": lambda: None, } ), # An MPRIS widget that shows the currently playing song information in a nice format. widget.Mpris2( font=Config.get('font_topbar', 'Arial'), fontsize=Config.get('fontsize_topbar', 15), format="{xesam:title} - {xesam:artist} - {xesam:album}", scroll=True, width=300, # Maximum width before widget starts scrolling playing_text="{track}", paused_text="{track}", stopped_text="", no_metadata_text="No metadata available", name=f"media_text{screen_num}", mouse_callbacks={ "Button1": lazy.widget[f"media_icon{screen_num}"].play_pause(), "Button3": lazy.widget[f"media_icon{screen_num}"].next(), "Button4": lambda: None, "Button5": lambda: None, } ), # An MPRIS widget masquerading as a text widget, that only shows "|" when media is playing or paused. widget.Mpris2( fontsize=14, format="", scroll=False, playing_text="|", paused_text="|", stopped_text="", no_metadata_text="", mouse_callbacks={ "Button1": lambda: None, "Button4": lambda: None, "Button5": lambda: None, } ) ]) # Sensor widgets sensor_widgets = [] # Temperature sensor if Config.get('show_temperature', False): sensor_widgets.append(kuro.utils.widgets.ThermalSensorWidget( font=Config.get('font_topbar', 'Arial'), fontsize=Config.get('fontsize_topbar', 16), foreground=Config.get('thermal_colour', '#ffffff'), foreground_alert=Config.get('thermal_colour_alert', '#ff0000'), tag_sensor=Config.get('thermal_sensor', 'temp1'), chip=Config.get('thermal_chip', None), threshold=Config.get('thermal_threshold', 70), update_interval=5, fontsize_left=18, fontsize_right=11 )) # CPU/Memory/Disk sensors sensor_widgets.extend([ kuro.utils.widgets.CPUInfoWidget(fontsize_left=16, fontsize_right=11), kuro.utils.widgets.MemoryInfoWidget(fontsize_left=18, fontsize_right=11), kuro.utils.widgets.DiskIOInfoWidget(fontsize_left=16, fontsize_right=11), ]) widgets.extend([ widget.WidgetBox( font=Config.get('font_groupbox', 'Arial'), fontsize=Config.get('fontsize_groupbox', 15), text_open="[>]", text_closed="[]", widgets=sensor_widgets ), widget.TextBox(fontsize=14, text="|") ]) # Battery level if Config.get('show_battery_widget', False): widgets.extend([ kuro.utils.widgets.BatteryInfoWidget(fontsize_left=16, fontsize_right=11), ]) # Volume widget(s) widgets.append( kuro.utils.widgets.VolumeInfoWidget( pulse_sink=Config.get('volume_pulse_sink', None), fontsize_left=18, fontsize_right=11, font_left=Config.get('font_groupbox', None), ) ) # Violet has multiple volume widgets if socket.gethostname() in ["Violet"]: widgets.append( kuro.utils.widgets.VolumeInfoWidget( pulse_sink=Config.get('volume_pulse_sink2', None), fontsize_left=18, fontsize_right=11, font_left=Config.get('font_groupbox', None), ) ) # Network information widgets.extend([ widget.TextBox(fontsize=14, text="|"), kuro.utils.widgets.NetworkInfoWidget( fontsize_left=16, fontsize_right=14, wireless_interface=Config.get('wifi_interface', None), wired_interface=Config.get('wired_interface', None) ), ]) # GPU widget if Config.get('show_gpu_widget', False): widgets.extend([ kuro.utils.widgets.GPUStatusWidget( theme_path=Config.get( 'gpu_theme_path', '/usr/lib/python3.11/site-packages/libqtile/resources/battery-icons'), padding=0, ) ]) widgets.append(widget.TextBox(fontsize=14, text="|")) # Determine systray to use (X or Wayland) # X system tray can only be on one screen, so only put it on the first if qtile.core.name == "x11" and screen_num == 0: widgets.append(widget.Systray(**self.widget_defaults)) elif qtile.core.name != "x11": widgets.append(extra_widget.StatusNotifier(**self.widget_defaults)) # Layout switcher, clock and Screen ID widgets.extend([ widget.CurrentLayoutIcon(custom_icon_paths=Config.get('custom_layout_icon_paths', [])), widget.Clock(format="%a %d %b ", **self.widget_defaults), widget.Clock( format="%H:%M", font=Config.get('font_topbar', "Sans"), fontsize=16, padding=0, ), widget.Clock( format=":%S ", font=Config.get('font_topbar', "Sans"), fontsize=11, padding=0, ), widget.CurrentScreen( active_color="66FF66", inactive_color="FFFFFF", active_text=f"#{screen_num}", inactive_text=f"#{screen_num}", **self.widget_defaults ) ]) # Build the bar return bar.Bar( background=f"{Config.get('bar_background', '#000000')}{Config.get('bar_rgba_opacity', 'AA')}", opacity=Config.get('bar_opacity', 1.0), widgets=widgets, size=Config.get('height_groupbox', 30) ) # QTile base callbacks def callback_startup_once(self, *args, **kwargs): logger.warning("Callback Startup Once") if not hasattr(qtile, 'theme_instance'): # Save theme instance in qtile qtile.theme_instance = self self.set_random_wallpaper() def callback_startup(self): logger.warning("Callback Startup") if not hasattr(qtile, 'theme_instance'): # Save theme instance in qtile qtile.theme_instance = self logger.warning("Restoring wallpaper...") if self.current_wallpaper: p = utils.execute_once(["wal", "-n", "-i", "{}".format(self.current_wallpaper)]) p.wait() else: wallpaper = None if os.path.isfile("/home/kevin/.cache/wal/colors.json"): with open("/home/kevin/.cache/wal/colors.json", 'r') as f: try: wallpaper = json.load(f)['wallpaper'] except KeyError: wallpaper = None if wallpaper: self.set_wallpaper(wallpaper) else: logger.warning("No wallpaper to restore.") # Update color scheme self.update_colorscheme() self.startup_completed = True def callback_startup_complete(self, *args, **kwargs): logger.warning("Callback Startup Complete") if not hasattr(qtile, 'theme_instance'): # Save theme instance in qtile qtile.theme_instance = self # Update color scheme self.update_colorscheme() # After first startup is complete, autostart configured apps logger.warning("Autostarting apps...") for app in Config.get("apps_autostart", []): logger.warning(f"Starting '{app}'...") utils.execute_once(app) for app in Config.get("apps_autostart_group", []): if all(x in app.keys() for x in ["group", "command"]): logger.warning(f"Starting '{app['command']}' in group {app['group']}...") utils.start_in_group_once(theme=self, qtile=qtile, **app) else: logger.warning(f"Invalid app in 'apps_autostart_group', " f"must have 'group' and 'command' keys: {app}...") logger.warning("Autostart complete") def callback_client_managed(self, *args, **kwargs): client: Optional[Window] = args[0] if len(args) > 0 else None w_pid = None try: w_pid = client.get_pid() except AttributeError: # Some windows might not have this .get_pid method. Try other ways if isinstance(client, WaylandXWindow) or isinstance(client, WaylandXStatic): w_pid = client.surface.pid elif isinstance(client, XorgXWindow): w_pid = client.get_net_wm_pid() elif isinstance(client, LayerStatic): pass # Wayland background layer 'window' else: logger.error(f"Unknown window type {client.__class__.__name__}") if w_pid is not None and w_pid in self.autostart_app_rules.keys(): rule_id = self.autostart_app_rules[w_pid] logger.warning(f"Removing rule {rule_id} for PID {w_pid}, client {client.name}") lazy.remove_rule(rule_id) def callback_client_killed(self, *args, **kwargs): client = args[0] logger.warning("Client {} Killed".format(client)) # If this window was static, remove it from the static window list if hasattr(client, "is_static_window") and client.is_static_window: logger.warning("Removing static window {}".format(client.name)) del client.is_static_window self.static_windows.remove(client) def callback_screens_reconfigured(self, *args, **kwargs): logger.warning(f"Re-configuring screens!") self.reinit_screens() self.set_wallpaper(self.current_wallpaper) self.update_colorscheme() def callback_setgroup(self, *args, **kwargs): for window in self.static_windows: # Only move if the window is not currently on any screen. if window.group.screen is None: try: window.togroup() except WindowError as e: logger.warning("Could not move static window {}, removing from list: {}".format(window.name, e)) del window.is_static_window self.static_windows.remove(window) def show_window_info(self, *args, **kwargs): import pprint window = qtile.current_window if window: logger.warning(f"Window properties {window.name}\n{pprint.pformat(vars(window))}") if window.info(): logger.warning(f"Window info of {window.name}\n{pprint.pformat(window.info())}") def toggle_window_static(self, *args, **kwargs): window = qtile.current_window if window in self.static_windows: utils.notify(qtile, "Unpinned {}".format(window.name), "{} has been unpinned".format(window.name)) self.static_windows.remove(window) del window.is_static_window else: utils.notify(qtile, "Pinned {}".format(window.name), "{} has been pinned".format(window.name)) self.static_windows.append(window) window.is_static_window = True window.floating = True def set_random_wallpaper(self, *args, **kwargs): wallpapers = [] wallpaper_dir = Config.get("desktop_bg_folder", "") try: wallpapers = [x for x in os.listdir(wallpaper_dir) if ".vertical." not in x] except os.error as e: logger.warning("Could not load wallpapers from directory: {}".format(e)) if wallpapers: if Config.get("desktop_bg_override", False): wallpaper_file = Config.get("desktop_bg_override", "") else: wallpaper_file = os.path.join(wallpaper_dir, random.choice(wallpapers)) self.set_wallpaper(wallpaper_file) else: logger.warning("Random wallpaper requested but no wallpapers are available.") def set_wallpaper(self, filename): if qtile.core.name == "x11": p = utils.execute_once(f"{Config.get('wallpaper_config_command', 'wal-nitrogen-noupdate')} {filename}") p.wait() else: # Wayland can set wallpaper in qtile directly per screen for screen_i, screen in enumerate(qtile.screens): sinfo = screen.info() sfilename = filename if sinfo.get('width', 100) < sinfo.get('height', 10): # Vertical screen, see if there is a vertical alt wallpaper basename, ext = os.path.splitext(filename) new_filename = f"{basename}.vertical{ext}" if os.path.isfile(new_filename): sfilename = new_filename logger.warning(f"Setting Screen#{screen_i} wallpaper to {sfilename}.") screen.set_wallpaper(sfilename, "fill") self.current_wallpaper = filename def update_colorscheme(self, *args, **kwargs): if self.current_wallpaper: logger.warning(f"Updating wal colors for wallpaper {self.current_wallpaper}") p = utils.execute(["wal", "-n", "-i", "{}".format(self.current_wallpaper)]) p.wait() colors = None if os.path.isfile("/home/kevin/.cache/wal/colors.json"): with open("/home/kevin/.cache/wal/colors.json", 'r') as f: try: colors = json.load(f)['colors'] except KeyError: colors = None if colors: # Update Config Config.foreground = colors['color15'] Config.background = colors['color0'] Config.highlight = colors['color3'] Config.inactive_light = colors['color4'] Config.inactive_dark = colors['color5'] Config.bar_background = colors['color1'] # Update border colors in layouts for group in qtile.groups: for layout in group.layouts: if isinstance(layout, kuro_layouts.KuroWmii): layout.border_focus = colors['color15'] layout.border_focus_stack = colors['color1'] layout.border_normal = colors['color1'] layout.border_normal_stack = colors['color1'] for screen_i, screen in enumerate(qtile.screens): bar = screen.top logger.warning(f"Updating colorscheme for screen {screen_i}") if bar: bar.background = f"{colors['color1']}{Config.get('bar_rgba_opacity', 'AA')}" bar.drawer.clear(bar.background) def update_widget(w): if hasattr(w, '_update_drawer'): try: w._update_drawer() except Exception as e: logger.error("Error while updating drawer for widget {}: {}".format(w, e)) if hasattr(w, 'foreground'): w.foreground = colors['color15'] if hasattr(w, 'foreground_normal'): w.foreground_normal = colors['color15'] if hasattr(w, 'foreground_alert'): w.foreground_alert = colors['color3'] if hasattr(w, 'border'): w.border = colors['color15'] if hasattr(w, 'active'): w.active = colors['color15'] if hasattr(w, 'highlight_color'): w.highlight_color = colors['color3'] if hasattr(w, 'inactive'): w.inactive = colors['color8'] if hasattr(w, 'this_current_screen_border'): w.this_current_screen_border = colors['color15'] if hasattr(w, 'this_screen_border'): w.this_screen_border = colors['color15'] if hasattr(w, 'other_current_screen_border'): w.other_current_screen_border = colors['color8'] if hasattr(w, 'other_screen_border'): w.other_screen_border = colors['color8'] if isinstance(w, widget.WidgetBox): for subw in w.widgets: update_widget(subw) for w in bar.widgets: update_widget(w) bar.draw() else: logger.warning(f"Screen {screen_i} has no bar?") # Attempt to call pywalfox to update firefox/thunderbird colors try: logger.warning(f"Calling 'pywalfox update'...") p = utils.execute(["pywalfox", "update"]) p.wait() except subprocess.SubprocessError as e: logger.error(f"Error running 'pywalfox update': {e}") utils.notify(qtile, "Updated colorscheme!", f"active: {colors['color15']}, inactive: {colors['color1']}")