From d6870c56b0d304da31bb72adf73d983ccbcd8b20 Mon Sep 17 00:00:00 2001 From: Kevin Alberts Date: Sun, 8 Feb 2026 22:36:33 +0100 Subject: [PATCH] Float out the bar a little bit if there are no windows, or multiple windows. Make single windows always fullscreen with no margins. Make bar transparent except when hovered over. --- config.py | 1 + kuro/base.py | 3 ++ kuro/config/__init__.py | 5 ++- kuro/theme.py | 97 +++++++++++++++++++++++++++++++++++++---- kuro/utils/bar.py | 48 ++++++++++++++++++++ kuro/utils/widgets.py | 34 +++++++++++++++ 6 files changed, 178 insertions(+), 10 deletions(-) create mode 100644 kuro/utils/bar.py diff --git a/config.py b/config.py index c530902..9f93d2b 100644 --- a/config.py +++ b/config.py @@ -68,6 +68,7 @@ try: hook.subscribe.focus_change(Theme.callback_focus_change) hook.subscribe.float_change(Theme.callback_float_change) hook.subscribe.group_window_add(Theme.callback_group_window_add) + hook.subscribe.group_window_remove(Theme.callback_group_window_remove) hook.subscribe.client_new(Theme.callback_client_new) hook.subscribe.client_managed(Theme.callback_client_managed) hook.subscribe.client_killed(Theme.callback_client_killed) diff --git a/kuro/base.py b/kuro/base.py index 8f02d65..ced1ff8 100644 --- a/kuro/base.py +++ b/kuro/base.py @@ -202,6 +202,9 @@ class BaseTheme: def callback_group_window_add(self, *args, **kwargs): pass + def callback_group_window_remove(self, *args, **kwargs): + pass + def callback_client_new(self, *args, **kwargs): pass diff --git a/kuro/config/__init__.py b/kuro/config/__init__.py index 630827f..23b8389 100644 --- a/kuro/config/__init__.py +++ b/kuro/config/__init__.py @@ -151,6 +151,7 @@ class Config(BaseConfig): # Sizes width_border = 1 margin_layout = 8 + margin_layout_single = 0 width_spacer = 1 padding_spacer = 4 grow_amount = 5 @@ -164,7 +165,8 @@ class Config(BaseConfig): # Bar variables bar_background = background - bar_rgba_opacity = "AA" + bar_rgba_opacity = "00" + bar_rgba_opacity_hover = "AA" bar_opacity = 1.0 bar_hover_opacity = 1 @@ -180,6 +182,7 @@ class Config(BaseConfig): colour_groupbox_border_focus = foreground colour_groupbox_icon_active = foreground colour_groupbox_icon_inactive = inactive_light + colour_groupbox_highlight = highlight # Tasklist variables tasklist_border = foreground diff --git a/kuro/theme.py b/kuro/theme.py index aed645e..b2a4927 100644 --- a/kuro/theme.py +++ b/kuro/theme.py @@ -30,6 +30,7 @@ logger.warning("Importing kuro utils...") import kuro.utils.widgets from kuro.utils import general as utils +from kuro.utils.bar import KuroBar from kuro.utils.suntime import Sun logger.warning("Importing variables and other utils...") @@ -242,6 +243,7 @@ class Kuro(BaseTheme): border_width=Config.get('width_border', "1"), grow_amount=Config.get('grow_amount', "5"), margin=Config.get('margin_layout', "0"), + margin_on_single=Config.get('margin_layout_single', "0"), ), layout.Max(), layout.Zoomy( @@ -360,17 +362,44 @@ class Kuro(BaseTheme): widgets = [ # Workspaces kuro.utils.widgets.KuroGroupBox( + + # Active group font colour active=Config.get('colour_groupbox_icon_active', '#ffffff'), - borderwidth=Config.get('width_groupbox_border', 1), + # Inactive group font colour + inactive=Config.get('colour_groupbox_icon_inactive', '#444444'), + + # Border or line colour for group on this screen when focused. + this_current_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'), + # Border or line colour for group on this screen when unfocused. + this_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'), + + # Border or line colour for group on other screen when focused. + other_current_screen_border=Config.get('colour_groupbox_border_normal', '#444444'), + # Border or line colour for group on other screen when unfocused. + other_screen_border=Config.get('colour_groupbox_border_normal', '#444444'), + + # Urgent group font color + urgent_text=Config.get('colour_groupbox_urgent_text', '#FF0000'), + # Urgent border or line color + urgent_border=Config.get('colour_groupbox_urgent_border', '#FF0000'), + + # Method of highlighting ('border', 'block', 'text', or 'line') + highlight_method="line", + # Active group highlight color when using 'line' highlight method + highlight_color=Config.get("colour_groupbox_highlight", '#888888'), + + # Method for alerting you of WM urgent hints (one of 'border', 'text', 'block', or 'line') + urgent_alert_method="block", + # To round or not to round box borders + rounded=Config.get('bool_groupbox_rounded_borders', True), + # Disable dragging and dropping of group names on widget disable_drag=Config.get('bool_groupbox_disable_drag', False), + + # Other settings + borderwidth=Config.get('width_groupbox_border', 1), 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) + margin=Config.get('margin_groupbox', 0), ), # Spawn prompt (only shown if activated) @@ -558,11 +587,14 @@ class Kuro(BaseTheme): ]) # Build the bar - return bar.Bar( + return KuroBar( background=f"{Config.get('bar_background', '#000000')}{Config.get('bar_rgba_opacity', 'AA')}", + background_normal=f"{Config.get('bar_background', '#000000')}{Config.get('bar_rgba_opacity', 'AA')}", + background_hover=f"{Config.get('bar_background', '#000000')}{Config.get('bar_rgba_opacity_hover', 'AA')}", opacity=Config.get('bar_opacity', 1.0), widgets=widgets, - size=Config.get('height_groupbox', 30) + size=Config.get('height_groupbox', 30), + margin=[8, 8, 0, 8] ) # QTile base callbacks @@ -606,6 +638,13 @@ class Kuro(BaseTheme): # Save theme instance in qtile qtile.theme_instance = self + for screen in self.screens: + if screen.top: + screen.top.window.window.set_property( + "_NET_WM_WINDOW_TYPE", + [screen.top.window.window.conn.atoms["_NET_WM_WINDOW_TYPE_DOCK"]] + ) + # Update color scheme self.update_colorscheme() @@ -874,3 +913,43 @@ class Kuro(BaseTheme): subprocess.Popen(["systemctl", "--user", "import-environment", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP"]) subprocess.Popen(["dbus-update-activation-environment", "--systemd", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP=qtile"]) subprocess.Popen(["systemctl", "--user", "restart", "xdg-desktop-portal"]) + + def update_bar_margins(self, screen_ref, layout_ref, extra_windows=None, ignore_windows=None): + if extra_windows is None: + extra_windows = [] + if ignore_windows is None: + ignore_windows = [] + if screen_ref and screen_ref.top: + logger.warning(f"Updating margins of bar {screen_ref.top} on screen {screen_ref} for layout {layout_ref}") + + # Update margins. Bar should have no margins when there is only one client on screen (Max layout or Columns with 1 window) + # and should have margins in all other cases. + if isinstance(layout_ref, layout.max.Max): + screen_ref.top.margin = [0, 0, 0, 0] + elif isinstance(layout_ref, layout.columns.Columns): + clients = extra_windows + for column in layout_ref.columns: + clients.extend(column.clients) + is_single = len([c for c in clients if c not in ignore_windows]) == 1 + if is_single: + screen_ref.top.margin = [0, 0, 0, 0] + else: + screen_ref.top.margin = [8, 8, 0, 8] + else: + screen_ref.top.margin = [8, 8, 0, 8] + + # Re-render the top bar to apply margins + screen_ref.top._configure(qtile=qtile, screen=screen_ref, reconfigure=True) + + # Re-render visible clients on the screen to apply margins + layout_ref.group.layout_all() + + def callback_layout_change(self, new_layout, new_group): + logger.warning(f"Changed layout in group {new_group} to {new_layout}") + self.update_bar_margins(new_group.screen, new_layout) + + def callback_group_window_add(self, group, window): + self.update_bar_margins(group.screen, group.layout, extra_windows=[window]) + + def callback_group_window_remove(self, group, window): + self.update_bar_margins(group.screen, group.layout, ignore_windows=[window]) diff --git a/kuro/utils/bar.py b/kuro/utils/bar.py new file mode 100644 index 0000000..63d5d4f --- /dev/null +++ b/kuro/utils/bar.py @@ -0,0 +1,48 @@ +from libqtile import bar +from libqtile.widget.groupbox import GroupBox +from libqtile.log_utils import logger + + +class KuroBar(bar.Bar): + defaults = [ + ("background", "#000000", "Background colour."), + ("background_normal", "#000000", "Background colour normally."), + ("background_hover", "#000000", "Background colour on hover."), + ("opacity", 1, "Bar window opacity."), + ("margin", 0, "Space around bar as int or list of ints [N E S W]."), + ("border_color", "#000000", "Border colour as str or list of str [N E S W]"), + ("border_width", 0, "Width of border as int of list of ints [N E S W]"), + ( + "reserve", + True, + "Reserve screen space (when set to 'False', bar will be drawn above windows).", + ), + ] + + def process_pointer_enter(self, x: int, y: int) -> None: + super().process_pointer_enter(x=x, y=y) + self.background = self.background_hover + # GroupBox Widget background color + for widget in self.widgets: + if isinstance(widget, GroupBox): + if len(widget.highlight_color) in [6, 7]: + widget.highlight_color = widget.highlight_color + "FF" + else: + widget.highlight_color = widget.highlight_color[:-2] + "FF" + logger.warning(f"Highlight: {widget.highlight_color}") + self.drawer.clear(self.background) + self.draw() + + def process_pointer_leave(self, x: int, y: int) -> None: + super().process_pointer_leave(x=x, y=y) + self.background = self.background_normal + # GroupBox Widget background color + for widget in self.widgets: + if isinstance(widget, GroupBox): + if len(widget.highlight_color) in [6, 7]: + widget.highlight_color = widget.highlight_color + "88" + else: + widget.highlight_color = widget.highlight_color[:-2] + "88" + logger.warning(f"Highlight: {widget.highlight_color}") + self.drawer.clear(self.background) + self.draw() diff --git a/kuro/utils/widgets.py b/kuro/utils/widgets.py index 3a30e23..23891cb 100644 --- a/kuro/utils/widgets.py +++ b/kuro/utils/widgets.py @@ -16,10 +16,44 @@ from libqtile.widget.wlan import get_status from libqtile.widget.groupbox import GroupBox from libqtile.command.base import expose_command +from qtile_extras.widget.decorations import RectDecoration + from kuro.utils.general import notify, BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT, BUTTON_DOWN, BUTTON_UP, BUTTON_MUTE, \ call_process, execute +def get_widget_style(config): + # 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), + + # 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'] + return { + "foreground": "#ffffff", + "decorations": [ + RectDecoration( + colour=config.background, + radius=12, + filled=True, + group=True, + ) # type: ignore + ], + } + + class DualPaneTextboxBase(base._Widget): """ Base class for widgets that are two boxes next to each other both containing text.