roundabout,
created on Tuesday, 5 August 2025, 16:47:35 (1754412455),
received on Saturday, 9 August 2025, 12:22:37 (1754742157)
Author identity: Vlad <vlad.muntoiu@gmail.com>
4afaf666ca9798e27880e39f0c392303f1e0ab4e
applets/wf-window-list/__init__.py
@@ -23,7 +23,8 @@ import sys
import locale
from pathlib import Path
from pywayland.client import Display
from pywayland.protocol.wayland import WlRegistry, WlSeat, WlSurface, WlCompositor
from pywayland.protocol.wayland import WlRegistry, WlSeat, WlSurface, WlCompositor, WlOutput
from pywayland.protocol.wayland.wl_output import WlOutputProxy
from pywayland.protocol.wlr_foreign_toplevel_management_unstable_v1 import (
ZwlrForeignToplevelManagerV1,
ZwlrForeignToplevelHandleV1
@@ -50,6 +51,7 @@ ffi = FFI()
ffi.cdef("""
void * gdk_wayland_display_get_wl_display (void * display);
void * gdk_wayland_surface_get_wl_surface (void * surface);
void * gdk_wayland_monitor_get_wl_output (void * monitor);
""")
gtk = ffi.dlopen("libgtk-4.so.1")
@@ -245,6 +247,7 @@ class WFWindowList(panorama_panel.Applet):
self.initial_button = Gtk.ToggleButton()
self.display = None
self.my_output = None
self.wl_surface_ptr = None
self.registry = None
self.compositor = None
@@ -319,6 +322,21 @@ class WFWindowList(panorama_panel.Applet):
# Intentionally commented: the display is already connected by GTK
# self.display.connect()
my_monitor = Gtk4LayerShell.get_monitor(self.get_root())
# Iterate through monitors and get their Wayland output (wl_output)
# This is a hack to ensure output_enter/leave is called for toplevels
for monitor in self.get_root().get_native().get_display().get_monitors():
wl_output = gtk.gdk_wayland_monitor_get_wl_output(ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer(monitor.__gpointer__, None)))
if wl_output:
print("Create proxy")
output_proxy = WlOutputProxy(wl_output, self.display)
output_proxy.interface.registry[output_proxy._ptr] = output_proxy
if monitor == my_monitor:
self.my_output = output_proxy
# End hack
self.registry = self.display.get_registry()
self.registry.dispatcher["global"] = self.on_global
self.display.roundtrip()
@@ -351,9 +369,28 @@ class WFWindowList(panorama_panel.Applet):
handle: ZwlrForeignToplevelHandleV1):
handle.dispatcher["title"] = lambda h, title: self.on_title_changed(h, title)
handle.dispatcher["app_id"] = lambda h, app_id: self.on_app_id_changed(h, app_id)
handle.dispatcher["output_enter"] = self.on_output_entered
handle.dispatcher["output_leave"] = self.on_output_left
handle.dispatcher["state"] = lambda h, states: self.on_state_changed(h, states)
handle.dispatcher["closed"] = lambda h: self.on_closed(h)
def on_output_entered(self, handle, output):
# TODO: make this configurable
if output != self.my_output:
return
if handle in self.toplevel_buttons:
button = self.toplevel_buttons[handle]
self.append(button)
self.set_all_rectangles()
def on_output_left(self, handle, output):
if output != self.my_output:
return
if handle in self.toplevel_buttons:
button = self.toplevel_buttons[handle]
self.remove(button)
self.set_all_rectangles()
def on_title_changed(self, handle, title):
if handle not in self.toplevel_buttons:
button = WindowButton(handle, title)
@@ -361,18 +398,19 @@ class WFWindowList(panorama_panel.Applet):
button.set_layout_manager(WindowButtonLayoutManager(self.window_button_options))
button.connect("clicked", self.on_button_click)
self.toplevel_buttons[handle] = button
self.append(button)
else:
button = self.toplevel_buttons[handle]
button.window_title = title
self.set_all_rectangles()
def set_all_rectangles(self):
for button in self.toplevel_buttons.values():
surface = WlSurface()
surface._ptr = self.wl_surface_ptr
button.window_id.set_rectangle(surface, *get_widget_rect(button))
child = self.get_first_child()
while child is not None:
if isinstance(child, WindowButton):
surface = WlSurface()
surface._ptr = self.wl_surface_ptr
child.window_id.set_rectangle(surface, *get_widget_rect(child))
child = child.get_next_sibling()
def on_button_click(self, button: WindowButton):
# Set a rectangle for animation