From 736e1a47c9008cb0b988fcfae3c88307904f454b Mon Sep 17 00:00:00 2001 From: Makesesama Date: Mon, 5 May 2025 09:31:17 +0200 Subject: [PATCH] urgent tags --- bar/bar.css | 13 ++++++++++-- bar/river/service.py | 22 ++++++++++++++++++++- bar/river/widgets.py | 47 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/bar/bar.css b/bar/bar.css index 865c137..4d8ed74 100644 --- a/bar/bar.css +++ b/bar/bar.css @@ -73,12 +73,21 @@ button { font-size: 0px; } -#workspaces>button:hover { +#workspaces button.hover { background-color: var(--ws-hover); } -#workspaces>button.urgent { +#workspaces button.urgent { background-color: var(--ws-urgent); + color: var(--foreground); + font-weight: bold; + animation: urgent-blink 1s infinite; +} + +@keyframes urgent-blink { + 0% { opacity: 1.0; } + 50% { opacity: 0.5; } + 100% { opacity: 1.0; } } #workspaces>button.empty { diff --git a/bar/river/service.py b/bar/river/service.py index ee04b41..c6d10e4 100644 --- a/bar/river/service.py +++ b/bar/river/service.py @@ -10,7 +10,7 @@ from fabric.utils.helpers import idle_add # Import pywayland components - ensure these imports are correct from pywayland.client import Display -from pywayland.protocol.wayland import WlOutput, WlRegistry, WlSeat +from pywayland.protocol.wayland import WlOutput, WlSeat from .generated.river_status_unstable_v1 import ZriverStatusManagerV1 @@ -23,6 +23,7 @@ class OutputInfo: status: Any = None # ZriverOutputStatusV1 tags_view: List[int] = field(default_factory=list) tags_focused: List[int] = field(default_factory=list) + tags_urgent: List[int] = field(default_factory=list) @dataclass(frozen=True) @@ -190,11 +191,23 @@ class River(Service): return handler + def make_urgent_tags_handler(output_id): + def handler(_, tags): + decoded = self._decode_bitfields(tags) + state["outputs"][output_id].tags_urgent = decoded + logger.debug( + f"[RiverService] Output {output_id} urgent tags: {decoded}" + ) + idle_add(lambda: self._emit_urgent_tags(output_id, decoded)) + + return handler + # Bind output status listeners for name, info in list(state["outputs"].items()): status = state["river_status_mgr"].get_river_output_status(info.output) status.dispatcher["view_tags"] = make_view_tags_handler(name) status.dispatcher["focused_tags"] = make_focused_tags_handler(name) + status.dispatcher["urgent_tags"] = make_urgent_tags_handler(name) info.status = status logger.info(f"[RiverService] Set up status for output {name}") @@ -251,6 +264,13 @@ class River(Service): self.notify("active-window") return False # Don't repeat + def _emit_urgent_tags(self, output_id, tags): + """Emit urgent_tags events (called on main thread)""" + event = RiverEvent("urgent_tags", tags, output_id) + self.emit("event::urgent_tags", event) + self.emit(f"event::urgent_tags::{output_id}", tags) + return False # Don't repeat + @staticmethod def _decode_bitfields(bitfields) -> List[int]: """Decode River's tag bitfields into a list of tag indices""" diff --git a/bar/river/widgets.py b/bar/river/widgets.py index 3835a9e..be4f9ab 100644 --- a/bar/river/widgets.py +++ b/bar/river/widgets.py @@ -43,11 +43,36 @@ class RiverWorkspaceButton(Button): self._empty = value (self.remove_style_class if not value else self.add_style_class)("empty") + @Property(bool, "read-write", default_value=False) + def urgent(self) -> bool: + return self._urgent + + @urgent.setter + def urgent(self, value: bool): + self._urgent = value + self._update_style() + def __init__(self, id: int, label: str = None, **kwargs): super().__init__(label or str(id), **kwargs) self._id = id self._active = False self._empty = True + self._urgent = False + + def _update_style(self): + """Update button styles based on states""" + # Remove all state-related styles first + self.remove_style_class("active") + self.remove_style_class("empty") + self.remove_style_class("urgent") + + # Then apply current states + if self._active: + self.add_style_class("active") + if self._empty: + self.add_style_class("empty") + if self._urgent: + self.add_style_class("urgent") class RiverWorkspaces(EventBox): @@ -71,6 +96,7 @@ class RiverWorkspaces(EventBox): # Connect to service events self.service.connect("event::focused_tags", self.on_focus_change_general) self.service.connect("event::view_tags", self.on_view_change_general) + self.service.connect("event::urgent_tags", self.on_urgent_change_general) self.service.connect("event::output_removed", self.on_output_removed) # Initial setup when service is ready @@ -100,14 +126,16 @@ class RiverWorkspaces(EventBox): # Access fields directly on the OutputInfo dataclass focused_tags = output_info.tags_focused view_tags = output_info.tags_view + urgent_tags = output_info.tags_urgent logger.debug( - f"[RiverWorkspaces] Initial state - focused: {focused_tags}, view: {view_tags}" + f"[RiverWorkspaces] Initial state - focused: {focused_tags}, view: {view_tags}, urgent: {urgent_tags}" ) for i, btn in self._buttons.items(): btn.active = i in focused_tags btn.empty = i not in view_tags + btn.urgent = i in urgent_tags def on_focus_change(self, _, tags): """Handle focused tags change for our specific output""" @@ -143,6 +171,23 @@ class RiverWorkspaces(EventBox): ) self.on_view_change(_, event.data) + def on_urgent_change(self, _, tags): + """Handle urgent tags change for our specific output""" + logger.debug( + f"[RiverWorkspaces] Urgent change on output {self.output_id}: {tags}" + ) + for i, btn in self._buttons.items(): + btn.urgent = i in tags + + def on_urgent_change_general(self, _, event): + """Handle general urgent tags event""" + # Only handle event if it's for our output + if event.output_id == self.output_id: + logger.debug( + f"[RiverWorkspaces] General urgent change for output {self.output_id}" + ) + self.on_urgent_change(_, event.data) + def on_output_removed(self, _, event): """Handle output removal""" removed_id = event.data[0]