From d43e0a484e59204595797e37efe2146f105f378b Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 6 Jan 2025 10:37:55 -0500 Subject: [PATCH 01/12] add bind_many, urgent workspaces for hyprland --- ignis/gobject.py | 46 +++++++++++++++++++++--------- ignis/services/hyprland/service.py | 27 ++++++++++++++++++ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/ignis/gobject.py b/ignis/gobject.py index fcc704e3..8e7f5322 100644 --- a/ignis/gobject.py +++ b/ignis/gobject.py @@ -10,11 +10,11 @@ class Binding(GObject.Object): def __init__( self, target: GObject.Object, - target_property: str, + target_properties: list[str], transform: Callable | None = None, ): self._target = target - self._target_property = target_property + self._target_properties = target_properties self._transform = transform super().__init__() @@ -28,13 +28,13 @@ def target(self) -> GObject.Object: return self._target @GObject.Property - def target_property(self) -> str: + def target_properties(self) -> list[str]: """ - required, read-only - The property on the target GObject. + The properties on the target GObject to bind. """ - return self._target_property + return self._target_properties @GObject.Property def transform(self) -> Callable | None: @@ -106,7 +106,7 @@ def set_property(self, property_name: str, value: Any) -> None: self.bind_property2( source_property=property_name, target=value.target, - target_property=value.target_property, + target_properties=value.target_properties, transform=value.transform, ) else: @@ -116,26 +116,34 @@ def bind_property2( self, source_property: str, target: GObject.Object, - target_property: str, + target_properties: list[str], transform: Callable | None = None, ) -> None: """ - Bind ``source_property`` on ``self`` with ``target_property`` on ``target``. + Bind ``source_property`` on ``self`` with ``target_properties`` on ``target``. Args: source_property: The property on ``self`` to bind. target: the target ``GObject.Object``. - target_property: the property on ``target`` to bind. + target_properties: the properties on ``target`` to bind. transform: The function that accepts a new property value and returns the processed value. """ def callback(*args): - value = target.get_property(target_property.replace("-", "_")) + values = [] + for target_property in target_properties: + values.append(target.get_property(target_property.replace("-", "_"))) + if transform: - value = transform(value) + value = transform(*values) + else: + if len(values) != 1: + raise IndexError("No transform function on muliple binding") + value = values[0] self.set_property(source_property, value) - target.connect(f"notify::{target_property.replace('_', '-')}", callback) + for target_property in target_properties: + target.connect(f"notify::{target_property.replace('_', '-')}", callback) callback() def bind(self, property_name: str, transform: Callable | None = None) -> Binding: @@ -148,7 +156,19 @@ def bind(self, property_name: str, transform: Callable | None = None) -> Binding Returns: :class:`~ignis.gobject.Binding` """ - return Binding(self, property_name, transform) + return Binding(self, [property_name], transform) + + def bind_many(self, property_names: list[str], transform: Callable) -> Binding: + """ + Creates ``Binding`` from property name on ``self``. + + Args: + property_names: List of property names of ``self``. + transform: The function that accepts a new property value and returns the processed value. + Returns: + :class:`~ignis.gobject.Binding` + """ + return Binding(self, property_names, transform) def __getattribute__(self, name: str) -> Any: # This modified __getattribute__ method redirect all "set_" methods to set_property method to provive bindings support. diff --git a/ignis/services/hyprland/service.py b/ignis/services/hyprland/service.py index e3a8caa1..92b4fe61 100644 --- a/ignis/services/hyprland/service.py +++ b/ignis/services/hyprland/service.py @@ -88,6 +88,7 @@ def __init__(self): self._active_workspace: dict[str, Any] = {} self._kb_layout: str = "" self._active_window: dict[str, Any] = {} + self._urgent_workspaces: set[str] = set() if self.is_available: self.__listen_events() @@ -114,6 +115,15 @@ def workspaces(self) -> list[dict[str, Any]]: """ return self._workspaces + @GObject.Property + def urgent_workspaces(self) -> list[str]: + """ + - read-only + + A list of urgent workspaces. + """ + return list(self._urgent_workspaces) + @GObject.Property def active_workspace(self) -> dict[str, Any]: """ @@ -161,11 +171,17 @@ def __on_event_received(self, event: str) -> None: elif event.startswith("activewindow>>"): self.__sync_active_window() + elif event.startswith("urgent>>"): + self.__sync_urgent(event[len("urgent>>"):]) + def __sync_workspaces(self) -> None: self._workspaces = sorted( json.loads(self.send_command("j/workspaces")), key=lambda x: x["id"] ) self._active_workspace = json.loads(self.send_command("j/activeworkspace")) + for i in self._active_workspace.values(): + if i in self._urgent_workspaces: + self._urgent_workspaces.remove(i) self.notify("workspaces") self.notify("active-workspace") @@ -179,6 +195,17 @@ def __sync_active_window(self) -> None: self._active_window = json.loads(self.send_command("j/activewindow")) self.notify("active_window") + def __sync_urgent(self, urgent_window) -> None: + clients = json.loads(self.send_command("j/clients")) + urgent_workspace = None + for i in clients: + if i["address"] == f"0x{urgent_window}": + urgent_workspace = i["workspace"]["id"] + break + if urgent_workspace: + self._urgent_workspaces.add(urgent_workspace) + self.notify("urgent_workspaces") + def send_command(self, cmd: str) -> str: """ Send a command to the Hyprland IPC. From dae7963f482db69163107fedf22d2c39c5193efd Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 6 Jan 2025 10:45:03 -0500 Subject: [PATCH 02/12] change example bar config to use hyprland urgent workspaces and bind_many --- examples/bar/config.py | 8 +++++--- examples/bar/style.scss | 8 +++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/bar/config.py b/examples/bar/config.py index e00d6243..25e75cef 100644 --- a/examples/bar/config.py +++ b/examples/bar/config.py @@ -30,6 +30,8 @@ def hyprland_workspace_button(workspace: dict) -> Widget.Button: ) if workspace["id"] == hyprland.active_workspace["id"]: widget.add_css_class("active") + if workspace["id"] in hyprland.urgent_workspaces: + widget.add_css_class("urgent") return widget @@ -96,9 +98,9 @@ def hyprland_workspaces() -> Widget.EventBox: on_scroll_down=lambda x: scroll_workspaces("down"), css_classes=["workspaces"], spacing=5, - child=hyprland.bind( - "workspaces", - transform=lambda value: [workspace_button(i) for i in value], + child=hyprland.bind_many( + ["workspaces", "urgent_workspaces"], + transform=lambda value, _: [workspace_button(i) for i in value], ), ) diff --git a/examples/bar/style.scss b/examples/bar/style.scss index 8a7ec747..3486f296 100644 --- a/examples/bar/style.scss +++ b/examples/bar/style.scss @@ -8,6 +8,7 @@ $bg-light: #261d1e; $fg: #f0dedf; $active: #ffb2bc; $unactive: #d7c1c3; +$urgent: #eb6f92; .bar { padding: 0.4rem 1rem; @@ -54,6 +55,11 @@ $unactive: #d7c1c3; color: $bg; } +.workspace.urgent { + background-color: $urgent; + color: $bg; +} + .tray-item { all: unset; } @@ -97,4 +103,4 @@ popover.menu { } } -} \ No newline at end of file +} From 35d6cd69104117f1522f5f8f0d630e45441c70d8 Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 6 Jan 2025 11:14:35 -0500 Subject: [PATCH 03/12] add ability to access urgent_window --- ignis/services/hyprland/service.py | 36 +++++++++++++++++++----------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/ignis/services/hyprland/service.py b/ignis/services/hyprland/service.py index 92b4fe61..1974b802 100644 --- a/ignis/services/hyprland/service.py +++ b/ignis/services/hyprland/service.py @@ -88,7 +88,7 @@ def __init__(self): self._active_workspace: dict[str, Any] = {} self._kb_layout: str = "" self._active_window: dict[str, Any] = {} - self._urgent_workspaces: set[str] = set() + self._urgent_windows: set[str] = set() if self.is_available: self.__listen_events() @@ -115,6 +115,15 @@ def workspaces(self) -> list[dict[str, Any]]: """ return self._workspaces + @GObject.Property + def urgent_windows(self) -> list[str]: + """ + - read-only + + A list of urgent windows. + """ + return list(self._urgent_windows) + @GObject.Property def urgent_workspaces(self) -> list[str]: """ @@ -122,7 +131,12 @@ def urgent_workspaces(self) -> list[str]: A list of urgent workspaces. """ - return list(self._urgent_workspaces) + clients = json.loads(self.send_command("j/clients")) + urgent_workspaces = [] + for i in clients: + if i["address"][len("0x"):] in self._urgent_windows: + urgent_workspaces.append(i["workspace"]["id"]) + return urgent_workspaces @GObject.Property def active_workspace(self) -> dict[str, Any]: @@ -179,9 +193,6 @@ def __sync_workspaces(self) -> None: json.loads(self.send_command("j/workspaces")), key=lambda x: x["id"] ) self._active_workspace = json.loads(self.send_command("j/activeworkspace")) - for i in self._active_workspace.values(): - if i in self._urgent_workspaces: - self._urgent_workspaces.remove(i) self.notify("workspaces") self.notify("active-workspace") @@ -193,17 +204,16 @@ def __sync_kb_layout(self) -> None: def __sync_active_window(self) -> None: self._active_window = json.loads(self.send_command("j/activewindow")) + active_window_id = self._active_window["address"][len("0x"):] + if active_window_id in self._urgent_windows: + self._urgent_windows.remove(active_window_id) self.notify("active_window") + self.notify("urgent_windows") + self.notify("urgent_workspaces") def __sync_urgent(self, urgent_window) -> None: - clients = json.loads(self.send_command("j/clients")) - urgent_workspace = None - for i in clients: - if i["address"] == f"0x{urgent_window}": - urgent_workspace = i["workspace"]["id"] - break - if urgent_workspace: - self._urgent_workspaces.add(urgent_workspace) + self._urgent_windows.add(urgent_window) + self.notify("urgent_windows") self.notify("urgent_workspaces") def send_command(self, cmd: str) -> str: From fedcf400c66c2950f1913e8420ef7566b5a5fbc1 Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 6 Jan 2025 13:37:41 -0500 Subject: [PATCH 04/12] fix execution when startup when there is no focused window --- ignis/services/hyprland/service.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ignis/services/hyprland/service.py b/ignis/services/hyprland/service.py index 1974b802..d79267ad 100644 --- a/ignis/services/hyprland/service.py +++ b/ignis/services/hyprland/service.py @@ -204,9 +204,10 @@ def __sync_kb_layout(self) -> None: def __sync_active_window(self) -> None: self._active_window = json.loads(self.send_command("j/activewindow")) - active_window_id = self._active_window["address"][len("0x"):] - if active_window_id in self._urgent_windows: - self._urgent_windows.remove(active_window_id) + if self._active_window: + active_window_id = self._active_window["address"][len("0x"):] + if active_window_id in self._urgent_windows: + self._urgent_windows.remove(active_window_id) self.notify("active_window") self.notify("urgent_windows") self.notify("urgent_workspaces") From a9b9f2f2a7cb1049cd4cd15d70fccc3150b4ae1e Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 8 Sep 2025 23:47:08 -0400 Subject: [PATCH 05/12] fix hyprland focusing (and the code in general it contained merge messages) --- ignis/services/hyprland/service.py | 54 ++++++++---------------------- 1 file changed, 14 insertions(+), 40 deletions(-) diff --git a/ignis/services/hyprland/service.py b/ignis/services/hyprland/service.py index afdffb66..8d964e8d 100644 --- a/ignis/services/hyprland/service.py +++ b/ignis/services/hyprland/service.py @@ -57,19 +57,13 @@ class HyprlandService(BaseService): def __init__(self): super().__init__() -<<<<<<< HEAD - self._workspaces: list[dict[str, Any]] = [] - self._active_workspace: dict[str, Any] = {} - self._kb_layout: str = "" - self._active_window: dict[str, Any] = {} - self._urgent_windows: set[str] = set() -======= self._workspaces: dict[int, HyprlandWorkspace] = {} self._active_workspace: HyprlandWorkspace = HyprlandWorkspace(self) self._main_keyboard: HyprlandKeyboard = HyprlandKeyboard(self) self._windows: dict[str, HyprlandWindow] = {} self._active_window: HyprlandWindow = HyprlandWindow() self._monitors: dict[str, HyprlandMonitor] = {} + self._urgent_windows = set() self._OBJ_TYPES: dict[str, _HyprlandObjDesc] = { "workspace": _HyprlandObjDesc( @@ -98,7 +92,6 @@ def __init__(self): prop_name="monitors", ), } ->>>>>>> main if self.is_available: self.__listen_events() @@ -151,8 +144,7 @@ def workspaces(self) -> list[HyprlandWorkspace]: """ return list(self._workspaces.values()) -<<<<<<< HEAD - @GObject.Property + @IgnisProperty def urgent_windows(self) -> list[str]: """ - read-only @@ -161,7 +153,7 @@ def urgent_windows(self) -> list[str]: """ return list(self._urgent_windows) - @GObject.Property + @IgnisProperty def urgent_workspaces(self) -> list[str]: """ - read-only @@ -175,12 +167,8 @@ def urgent_workspaces(self) -> list[str]: urgent_workspaces.append(i["workspace"]["id"]) return urgent_workspaces - @GObject.Property - def active_workspace(self) -> dict[str, Any]: -======= @IgnisProperty def active_workspace(self) -> HyprlandWorkspace: ->>>>>>> main """ The currently active workspace. """ @@ -229,18 +217,9 @@ def get_full_w_addr(addr: str) -> str: event_type, event_value = event_data value_list = event_value.split(",") -<<<<<<< HEAD - elif event.startswith("urgent>>"): - self.__sync_urgent(event[len("urgent>>"):]) - - def __sync_workspaces(self) -> None: - self._workspaces = sorted( - json.loads(self.send_command("j/workspaces")), key=lambda x: x["id"] - ) - self._active_workspace = json.loads(self.send_command("j/activeworkspace")) - self.notify("workspaces") -======= match event_type: + case "urgent": + self.__sync_urgent(value_list[0]) case "destroyworkspacev2": self.__destroy_workspace(int(value_list[0])) case "createworkspacev2": @@ -390,7 +369,6 @@ def __sort_workspaces(self) -> None: def __sync_active_workspace(self) -> None: workspace_data = json.loads(self.send_command("j/activeworkspace")) self._active_workspace.sync(workspace_data) ->>>>>>> main self.notify("active-workspace") def __sync_main_keyboard(self) -> None: @@ -405,27 +383,24 @@ def __sync_main_keyboard(self) -> None: def __sync_active_layout(self, layout: str) -> None: self._main_keyboard.sync({"active_keymap": layout}) - def __sync_active_window(self) -> None: -<<<<<<< HEAD - self._active_window = json.loads(self.send_command("j/activewindow")) - if self._active_window: - active_window_id = self._active_window["address"][len("0x"):] - if active_window_id in self._urgent_windows: - self._urgent_windows.remove(active_window_id) - self.notify("active_window") - self.notify("urgent_windows") - self.notify("urgent_workspaces") - def __sync_urgent(self, urgent_window) -> None: self._urgent_windows.add(urgent_window) self.notify("urgent_windows") self.notify("urgent_workspaces") -======= + + def __sync_active_window(self) -> None: active_window_data = json.loads(self.send_command("j/activewindow")) if active_window_data == {}: active_window_data = HyprlandWindow().data self.active_window.sync(active_window_data) + + active_window_id = active_window_data['address'][len('0x'):] + if active_window_id in self._urgent_windows: + self._urgent_windows.remove(active_window_id) + self.notify("urgent_windows") + self.notify("urgent_workspaces") + self.notify("active-window") def __open_window(self, address: str) -> None: @@ -500,7 +475,6 @@ def __change_special_ws_on_monitor( key=monitor_name, data={"specialWorkspace": {"id": workspace_id, "name": workspace_name}}, ) ->>>>>>> main def send_command(self, cmd: str) -> str: """ From a2209fb04e6d4ccc618563079732b78238c4fde4 Mon Sep 17 00:00:00 2001 From: John Hao Date: Tue, 9 Sep 2025 16:50:59 -0400 Subject: [PATCH 06/12] fix example bar --- examples/bar/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/bar/config.py b/examples/bar/config.py index 1fb5ee0d..fca8efc5 100644 --- a/examples/bar/config.py +++ b/examples/bar/config.py @@ -106,8 +106,8 @@ def hyprland_workspaces() -> widgets.EventBox: css_classes=["workspaces"], spacing=5, child=hyprland.bind_many( # bind also to active_workspace to regenerate workspaces list when active workspace changes - ["workspaces", "urgent_workspaces"], - transform=lambda workspaces, active_workspace: [ + ["workspaces", "active_workspace", "urgent_workspaces"], + transform=lambda workspaces, active_workspace, urgent_workspaces: [ workspace_button(i) for i in workspaces ], ), From 25f04376c0cc35c0b1741a01788dcb99efe8f8b3 Mon Sep 17 00:00:00 2001 From: John Hao Date: Tue, 9 Sep 2025 16:53:32 -0400 Subject: [PATCH 07/12] remove unnecessary merged comment changes --- examples/bar/style.scss | 1 + ignis/gobject.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/bar/style.scss b/examples/bar/style.scss index 3486f296..129c91ef 100644 --- a/examples/bar/style.scss +++ b/examples/bar/style.scss @@ -104,3 +104,4 @@ popover.menu { } } + diff --git a/ignis/gobject.py b/ignis/gobject.py index 736f2541..0553eb53 100644 --- a/ignis/gobject.py +++ b/ignis/gobject.py @@ -37,8 +37,6 @@ def target(self) -> GObject.Object: @GObject.Property def target_properties(self) -> list[str]: """ - - required, read-only - The properties on the target GObject to bind. """ return self._target_properties @@ -135,16 +133,18 @@ def bind_property2( """ def callback(*args): - values = [] - for target_property in target_properties: - values.append(target.get_property(target_property.replace("-", "_"))) + values = [ + target.get_property(target_property.replace("-", "_")) + for target_property in target_properties + ] if transform: value = transform(*values) else: if len(values) != 1: - raise IndexError("No transform function on muliple binding") + raise IndexError("No transform function on multiple binding") value = values[0] + self.set_property(source_property, value) for target_property in target_properties: @@ -166,11 +166,11 @@ def bind(self, property_name: str, transform: Callable | None = None) -> Binding def bind_many(self, property_names: list[str], transform: Callable) -> Binding: """ - Creates ``Binding`` from property name on ``self``. + Creates ``Binding`` from property names on ``self``. Args: property_names: List of property names of ``self``. - transform: The function that accepts a new property value and returns the processed value. + transform: The function that accepts a new property values and returns the processed value. The values will be passed according to the order in ``property_names``. Returns: :class:`~ignis.gobject.Binding` """ From 8ef9ae237b07a6b96c4b64c4d8f8bf690a8deba2 Mon Sep 17 00:00:00 2001 From: John Hao Date: Tue, 9 Sep 2025 17:02:17 -0400 Subject: [PATCH 08/12] fix linter errors --- examples/bar/config.py | 2 +- ignis/services/hyprland/service.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/bar/config.py b/examples/bar/config.py index fca8efc5..1e12bcbd 100644 --- a/examples/bar/config.py +++ b/examples/bar/config.py @@ -39,7 +39,7 @@ def hyprland_workspace_button(workspace: HyprlandWorkspace) -> widgets.Button: ) if workspace.id == hyprland.active_workspace.id: widget.add_css_class("active") - if workspace["id"] in hyprland.urgent_workspaces: + if workspace.id in hyprland.urgent_workspaces: widget.add_css_class("urgent") return widget diff --git a/ignis/services/hyprland/service.py b/ignis/services/hyprland/service.py index 8d964e8d..9536a231 100644 --- a/ignis/services/hyprland/service.py +++ b/ignis/services/hyprland/service.py @@ -163,7 +163,7 @@ def urgent_workspaces(self) -> list[str]: clients = json.loads(self.send_command("j/clients")) urgent_workspaces = [] for i in clients: - if i["address"][len("0x"):] in self._urgent_windows: + if i["address"][len("0x") :] in self._urgent_windows: urgent_workspaces.append(i["workspace"]["id"]) return urgent_workspaces @@ -395,7 +395,7 @@ def __sync_active_window(self) -> None: self.active_window.sync(active_window_data) - active_window_id = active_window_data['address'][len('0x'):] + active_window_id = active_window_data["address"][len("0x") :] if active_window_id in self._urgent_windows: self._urgent_windows.remove(active_window_id) self.notify("urgent_windows") From d8e8a88f10f2fc64cb531f77cef959d71b585d2f Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 15 Sep 2025 09:39:28 -0400 Subject: [PATCH 09/12] use urgent tag for windows --- ignis/services/hyprland/service.py | 27 +++++++++++++-------------- ignis/services/hyprland/window.py | 8 ++++++++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ignis/services/hyprland/service.py b/ignis/services/hyprland/service.py index 9536a231..72ca8a8c 100644 --- a/ignis/services/hyprland/service.py +++ b/ignis/services/hyprland/service.py @@ -63,7 +63,6 @@ def __init__(self): self._windows: dict[str, HyprlandWindow] = {} self._active_window: HyprlandWindow = HyprlandWindow() self._monitors: dict[str, HyprlandMonitor] = {} - self._urgent_windows = set() self._OBJ_TYPES: dict[str, _HyprlandObjDesc] = { "workspace": _HyprlandObjDesc( @@ -147,23 +146,22 @@ def workspaces(self) -> list[HyprlandWorkspace]: @IgnisProperty def urgent_windows(self) -> list[str]: """ - - read-only - A list of urgent windows. """ - return list(self._urgent_windows) + return [i for i in self._windows.values() if i.is_urgent] @IgnisProperty def urgent_workspaces(self) -> list[str]: """ - - read-only - A list of urgent workspaces. """ clients = json.loads(self.send_command("j/clients")) urgent_workspaces = [] for i in clients: - if i["address"][len("0x") :] in self._urgent_windows: + address = i["address"] + if address not in self._windows: + continue + if self._windows[address].is_urgent: urgent_workspaces.append(i["workspace"]["id"]) return urgent_workspaces @@ -219,7 +217,7 @@ def get_full_w_addr(addr: str) -> str: match event_type: case "urgent": - self.__sync_urgent(value_list[0]) + self.__sync_urgent(get_full_w_addr(value_list[0])) case "destroyworkspacev2": self.__destroy_workspace(int(value_list[0])) case "createworkspacev2": @@ -384,9 +382,10 @@ def __sync_active_layout(self, layout: str) -> None: self._main_keyboard.sync({"active_keymap": layout}) def __sync_urgent(self, urgent_window) -> None: - self._urgent_windows.add(urgent_window) - self.notify("urgent_windows") - self.notify("urgent_workspaces") + if urgent_window in self._windows: + self._windows[urgent_window]._urgent = True + self.notify("urgent_windows") + self.notify("urgent_workspaces") def __sync_active_window(self) -> None: active_window_data = json.loads(self.send_command("j/activewindow")) @@ -395,9 +394,9 @@ def __sync_active_window(self) -> None: self.active_window.sync(active_window_data) - active_window_id = active_window_data["address"][len("0x") :] - if active_window_id in self._urgent_windows: - self._urgent_windows.remove(active_window_id) + active_window_id = active_window_data["address"] + if active_window_id in self._windows and self._windows[active_window_id]._urgent: + self._windows[active_window_id]._urgent = False self.notify("urgent_windows") self.notify("urgent_workspaces") diff --git a/ignis/services/hyprland/window.py b/ignis/services/hyprland/window.py index de7ff5c2..854e444a 100644 --- a/ignis/services/hyprland/window.py +++ b/ignis/services/hyprland/window.py @@ -43,6 +43,7 @@ def __init__(self): self._swallowing: str = "" self._focus_history_id: int = -1 self._inhibiting_idle: bool = False + self._urgent: bool = False def sync(self, data: dict[str, Any]) -> None: """ @@ -227,3 +228,10 @@ def inhibiting_idle(self) -> bool: The inhibiting idle status. """ return self._inhibiting_idle + + @IgnisProperty + def is_urgent(self) -> bool: + """ + Whether this window is urgent or not + """ + return self._urgent From dfa726390580963f905a4c2b21961512f9a043d5 Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 15 Sep 2025 09:43:31 -0400 Subject: [PATCH 10/12] fix linting --- ignis/services/hyprland/service.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ignis/services/hyprland/service.py b/ignis/services/hyprland/service.py index 72ca8a8c..f391e145 100644 --- a/ignis/services/hyprland/service.py +++ b/ignis/services/hyprland/service.py @@ -144,7 +144,7 @@ def workspaces(self) -> list[HyprlandWorkspace]: return list(self._workspaces.values()) @IgnisProperty - def urgent_windows(self) -> list[str]: + def urgent_windows(self) -> list[HyprlandWindow]: """ A list of urgent windows. """ @@ -395,7 +395,10 @@ def __sync_active_window(self) -> None: self.active_window.sync(active_window_data) active_window_id = active_window_data["address"] - if active_window_id in self._windows and self._windows[active_window_id]._urgent: + if ( + active_window_id in self._windows + and self._windows[active_window_id]._urgent + ): self._windows[active_window_id]._urgent = False self.notify("urgent_windows") self.notify("urgent_workspaces") From 371fb22027fb4a3f73a8820c6bf75103e87f0a0e Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 15 Sep 2025 10:35:28 -0400 Subject: [PATCH 11/12] add is_urgent to workspace --- examples/bar/config.py | 2 +- ignis/services/hyprland/service.py | 12 ++---------- ignis/services/hyprland/workspace.py | 12 ++++++++++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/bar/config.py b/examples/bar/config.py index 1e12bcbd..97a1c0ff 100644 --- a/examples/bar/config.py +++ b/examples/bar/config.py @@ -39,7 +39,7 @@ def hyprland_workspace_button(workspace: HyprlandWorkspace) -> widgets.Button: ) if workspace.id == hyprland.active_workspace.id: widget.add_css_class("active") - if workspace.id in hyprland.urgent_workspaces: + if workspace.is_urgent: widget.add_css_class("urgent") return widget diff --git a/ignis/services/hyprland/service.py b/ignis/services/hyprland/service.py index f391e145..03f2dc2b 100644 --- a/ignis/services/hyprland/service.py +++ b/ignis/services/hyprland/service.py @@ -151,19 +151,11 @@ def urgent_windows(self) -> list[HyprlandWindow]: return [i for i in self._windows.values() if i.is_urgent] @IgnisProperty - def urgent_workspaces(self) -> list[str]: + def urgent_workspaces(self) -> list[HyprlandWorkspace]: """ A list of urgent workspaces. """ - clients = json.loads(self.send_command("j/clients")) - urgent_workspaces = [] - for i in clients: - address = i["address"] - if address not in self._windows: - continue - if self._windows[address].is_urgent: - urgent_workspaces.append(i["workspace"]["id"]) - return urgent_workspaces + return [i for i in self._workspaces.values() if i.is_urgent] @IgnisProperty def active_workspace(self) -> HyprlandWorkspace: diff --git a/ignis/services/hyprland/workspace.py b/ignis/services/hyprland/workspace.py index 925227e2..421b9e04 100644 --- a/ignis/services/hyprland/workspace.py +++ b/ignis/services/hyprland/workspace.py @@ -1,4 +1,5 @@ from ignis.gobject import IgnisProperty, IgnisSignal, DataGObject +from .window import HyprlandWindow MATCH_DICT = { "monitorID": "monitor_id", @@ -68,6 +69,13 @@ def windows(self) -> int: """ return self._windows + @IgnisProperty + def window_objs(self) -> list[HyprlandWindow]: + """ + All window objects on the workspace (parsed) + """ + return [i for i in self.__service._windows.values() if i.workspace_id == self._id] + @IgnisProperty def has_fullscreen(self) -> bool: """ @@ -96,6 +104,10 @@ def is_persistent(self) -> bool: """ return self._is_persistent + @IgnisProperty + def is_urgent(self): + return any(i.is_urgent for i in self.window_objs) + def switch_to(self) -> None: """ Switch to this workspace. From 50e6a7e7c5a4047ab8bf89e7b282683060bf3dab Mon Sep 17 00:00:00 2001 From: John Hao Date: Mon, 15 Sep 2025 10:36:30 -0400 Subject: [PATCH 12/12] fix ruff --- ignis/services/hyprland/workspace.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ignis/services/hyprland/workspace.py b/ignis/services/hyprland/workspace.py index 421b9e04..7cd85356 100644 --- a/ignis/services/hyprland/workspace.py +++ b/ignis/services/hyprland/workspace.py @@ -74,7 +74,9 @@ def window_objs(self) -> list[HyprlandWindow]: """ All window objects on the workspace (parsed) """ - return [i for i in self.__service._windows.values() if i.workspace_id == self._id] + return [ + i for i in self.__service._windows.values() if i.workspace_id == self._id + ] @IgnisProperty def has_fullscreen(self) -> bool: