roundabout,
created on Friday, 8 August 2025, 08:43:58 (1754642638),
received on Friday, 8 August 2025, 08:44:04 (1754642644)
Author identity: Vlad <vlad.muntoiu@gmail.com>
6b8a247171f0ea5077f1343767fec19416aaaee5
applets/wf-window-list/__init__.py
@@ -21,6 +21,7 @@ import dataclasses
import os import sys import locale import typingfrom pathlib import Path from pywayland.client import Display from pywayland.protocol.wayland import WlRegistry, WlSeat, WlSurface, WlCompositor, WlOutput
@@ -160,6 +161,7 @@ class WindowButton(Gtk.ToggleButton):
super().__init__(**kwargs) self.window_id: ZwlrForeignToplevelHandleV1 = window_id self.wf_ipc_id: typing.Optional[int] = Noneself.set_has_frame(False) self.label = Gtk.Label() self.icon = Gtk.Image.new_from_icon_name("application-x-executable")
@@ -208,8 +210,15 @@ class WindowButton(Gtk.ToggleButton):
self.label.set_text(value) def set_icon_from_app_id(self, app_id): # Try getting an icon from the correct themeapp_ids = app_id.split() # If on Wayfire, find the IPC ID for app_id in app_ids: if app_id.startswith("wf-ipc-"): self.wf_ipc_id = int(app_id.removeprefix("wf-ipc-")) break # Try getting an icon from the correct themeicon_theme = Gtk.IconTheme.get_for_display(self.get_display()) for app_id in app_ids:
@@ -242,6 +251,7 @@ class WFWindowList(panorama_panel.Applet):
self.window_button_options = WindowButtonOptions(config.get("max_button_width", 256)) self.toplevel_buttons: dict[ZwlrForeignToplevelHandleV1, WindowButton] = {} self.toplevel_buttons_by_wf_id: dict[int, WindowButton] = {}# This button doesn't belong to any window but is used for the button group and to be # selected when no window is focused self.initial_button = Gtk.ToggleButton()
@@ -279,6 +289,42 @@ class WFWindowList(panorama_panel.Applet):
self.add_controller(self.drop_target) # Make a Wayfire socket for workspace handling try: import wayfire self.wf_socket = wayfire.WayfireSocket() self.wf_socket.watch() fd = self.wf_socket.client.fileno() GLib.io_add_watch(fd, GLib.IO_IN, self.on_wf_event) except: # Wayfire raises Exception itself, so it cannot be narrowed down self.wf_socket = None def on_wf_event(self, source, condition): if condition == GLib.IO_IN: try: message = self.wf_socket.read_next_event() event = message.get("event") match event: case "view-workspace-changed": view = message.get("view", {}) output = self.wf_socket.get_output(self.get_root().monitor_index + 1) current_workspace = output["workspace"]["x"], output["workspace"]["y"] if (message["to"]["x"], message["to"]["y"]) == current_workspace: self.append(self.toplevel_buttons_by_wf_id[view["id"]]) else: # Remove out-of-workspace window self.remove(self.toplevel_buttons_by_wf_id[view["id"]]) case "wset-workspace-changed": output_id = self.get_root().monitor_index + 1 if message["wset-data"]["output-id"] == output_id: # It has changed on this monitor; refresh the window list self.filter_to_wf_workspace() except Exception as e: print("Error reading Wayfire event:", e) return True def drop_button(self, drop_target: Gtk.DropTarget, value: int, x: float, y: float): button: WindowButton = self.get_root().get_application().drags.pop(value) if button.get_parent() is not self:
@@ -310,6 +356,23 @@ class WFWindowList(panorama_panel.Applet):
self.set_all_rectangles() return True def filter_to_wf_workspace(self): output = self.wf_socket.get_output(self.get_root().monitor_index + 1) for wf_id, button in self.toplevel_buttons_by_wf_id.items(): view = self.wf_socket.get_view(wf_id) mid_x = view["geometry"]["x"] + view["geometry"]["width"] / 2 mid_y = view["geometry"]["y"] + view["geometry"]["height"] / 2 output_width = output["geometry"]["width"] output_height = output["geometry"]["height"] if 0 <= mid_x < output_width and 0 <= mid_y < output_height: # It is in this workspace; keep it if not button.get_realized(): self.append(button) else: # Remove it from this window list if button.get_parent() is self: self.remove(button) def get_wl_resources(self): ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p ctypes.pythonapi.PyCapsule_GetPointer.argtypes = (ctypes.py_object,)
@@ -343,6 +406,9 @@ class WFWindowList(panorama_panel.Applet):
fd = self.display.get_fd() GLib.io_add_watch(fd, GLib.IO_IN, self.on_display_event) if self.wf_socket is not None: self.filter_to_wf_workspace() def on_display_event(self, source, condition): if condition == GLib.IO_IN: self.display.dispatch(block=True)
@@ -376,6 +442,7 @@ class WFWindowList(panorama_panel.Applet):
def on_output_entered(self, handle, output): # TODO: make this configurable # TODO: on wayfire, append/remove buttons when the workspace changesif output != self.my_output: return if handle in self.toplevel_buttons:
@@ -442,11 +509,17 @@ class WFWindowList(panorama_panel.Applet):
if handle in self.toplevel_buttons: button = self.toplevel_buttons[handle] button.set_icon_from_app_id(app_id) app_ids = app_id.split() for app_id in app_ids: if app_id.startswith("wf-ipc-"): self.toplevel_buttons_by_wf_id[int(app_id.removeprefix("wf-ipc-"))] = buttondef on_closed(self, handle): if handle in self.toplevel_buttons: self.remove(self.toplevel_buttons[handle]) self.toplevel_buttons.pop(handle) if handle in self.toplevel_buttons_by_wf_id: self.toplevel_buttons_by_wf_id.pop(handle)self.set_all_rectangles()
config.yaml
@@ -1,12 +1,13 @@
panels: - position: top- position: bottommonitor: 0 size: 40 autohide: false hide_time: 300 applets: left: - WFWindowList: {}- WFWindowList: max_button_width: 256centre: [] right: - ClockApplet: