soreau,
created on Thursday, 4 September 2025, 04:27:38 (1756960058),
received on Saturday, 6 September 2025, 21:54:28 (1757195668)
Author identity: Scott Moreau <oreaus@gmail.com>
e46dce59d8e839e8af8cba31d8bdf30c8fbd5cd4
applets/wf-window-list/__init__.py
@@ -134,9 +134,10 @@ class WindowButtonOptions:
self.max_width = max_width
class WindowButtonLayoutManager(Gtk.LayoutManager):
def __init__(self, options: WindowButtonOptions, **kwargs):
def __init__(self, options: WindowButtonOptions, button: Gtk.ToggleButton, **kwargs):
super().__init__(**kwargs)
self.options = options
self.button = button
def do_measure(self, widget, orientation, for_size):
child = widget.get_first_child()
@@ -162,7 +163,11 @@ class WindowButtonLayoutManager(Gtk.LayoutManager):
alloc.width = alloc_width
alloc.height = height
child.allocate(alloc.width, alloc.height, baseline)
if alloc.width > 0 and alloc.height > 0 and self.button.window_list.wl_surface:
x, y = widget.translate_coordinates(self.button.get_root(), 0, 0)
x = int(x)
y = int(y)
self.button.window_id.set_rectangle(self.button.window_list.wl_surface, x, y, alloc.width, alloc.height)
class WindowButton(Gtk.ToggleButton):
def __init__(self, window_id, window_title, window_list, **kwargs):
@@ -252,31 +257,26 @@ class WindowButton(Gtk.ToggleButton):
return True
def on_button_enter(self, button, x, y):
print("on_button_enter")
if self.popover_open:
return
view_id = self.wf_ipc_id
#self.window_list.set_live_preview_output_name("live-preview-" + str(view_id))
message = self.window_list.get_msg_template("live_previews/request_stream")
message["data"]["id"] = view_id
self.wf_sock.send_json(message)
def on_button_leave(self, button):
print("on_button_leave")
if self.popover_open:
return
message = self.window_list.get_msg_template("live_previews/release_output")
self.wf_sock.send_json(message)
self.window_list.set_live_preview_output(None)
#self.window_list.set_live_preview_output_name(None)
def show_menu(self, gesture, n_presses, x, y):
print("show_menu")
self.on_button_leave(None)
self.popover_open = True
self.popover_menu.popup()
def hide_menu(self, popover):
print("hide_menu")
self.popover_open = False
self.popover_menu.popdown()
@@ -436,7 +436,7 @@ class WFWindowList(panorama_panel.Applet):
self.my_output = None
self.wl_surface = None
self.compositor = None
self.seat = None
self.wl_seat = None
self.output = None
self.wl_shm = None
#self.manager = None
@@ -580,37 +580,26 @@ class WFWindowList(panorama_panel.Applet):
self.remove(button)
def get_wl_resources(self, widget):
if self.wf_socket is not None:
self.filter_to_wf_workspace()
if self.wl_shm:
return
self.my_output = None
#self.wl_surface = self.get_root().get_application().wl_surface
self.compositor = self.get_root().get_application().compositor
self.seat = self.get_root().get_application().seat
self.wl_seat = self.get_root().get_application().wl_seat
self.output = None
self.wl_shm = self.get_root().get_application().wl_shm
#self.manager = self.get_root().get_application().manager
self.screencopy_manager = self.get_root().get_application().screencopy_manager
self.live_preview_output_name = None
if self.wf_socket is not None:
self.filter_to_wf_workspace()
def set_live_preview_output(self, output):
self.output = output
def get_live_preview_output(self):
return self.output
#def set_live_preview_output_name(self, name):
# self.live_preview_output_name = name
#
#def get_live_preview_output_name(self):
# return self.live_preview_output_name
def wl_output_enter(self, output, name):
print("wl_output_enter")
self.set_live_preview_output(None)
if name.startswith("live-preview"):
self.set_live_preview_output(output)
@@ -633,19 +622,7 @@ class WFWindowList(panorama_panel.Applet):
if output != button.output:
self.foreign_toplevel_closed(handle)
def wl_output_leave(self, output, name):
print("wl_output_leave")
if self.get_live_preview_output() == output:
self.set_live_preview_output(None)
return
if name.startswith("live-preview"):
return
toplevel_buttons = self.toplevel_buttons.copy()
for handle in toplevel_buttons:
self.foreign_toplevel_closed(handle)
def foreign_toplevel_output_enter(self, handle, output):
print("foreign_toplevel_output_enter")
if self.show_only_this_output and (not hasattr(output, "name") or output.name != self.get_root().monitor_name):
return
@@ -655,15 +632,13 @@ class WFWindowList(panorama_panel.Applet):
button.output = output
self.set_title(button, handle.title)
button.set_group(self.initial_button)
button.set_layout_manager(WindowButtonLayoutManager(self.window_button_options))
button.set_layout_manager(WindowButtonLayoutManager(self.window_button_options, button))
button.connect("clicked", self.on_button_click)
self.set_app_id(button, handle.app_id)
self.toplevel_buttons[handle] = button
self.append(button)
self.set_all_rectangles()
def foreign_toplevel_output_leave(self, handle, output):
print("foreign_toplevel_output_leave")
if handle in self.toplevel_buttons:
button = self.toplevel_buttons[handle]
if button.get_parent() == self:
@@ -696,12 +671,12 @@ class WFWindowList(panorama_panel.Applet):
# Set a rectangle for animation
button.window_id.set_rectangle(self.wl_surface, *get_widget_rect(button))
if button.window_state.minimised:
button.window_id.activate(self.seat)
button.window_id.activate(self.wl_seat)
else:
if button.window_state.focused:
button.window_id.set_minimized()
else:
button.window_id.activate(self.seat)
button.window_id.activate(self.wl_seat)
def foreign_toplevel_state(self, handle, states):
if handle in self.toplevel_buttons:
@@ -713,8 +688,6 @@ class WFWindowList(panorama_panel.Applet):
else:
self.initial_button.set_active(True)
self.set_all_rectangles()
def foreign_toplevel_refresh(self):
for button in self.toplevel_buttons.values():
self.remove(button)
@@ -813,9 +786,6 @@ class WFWindowList(panorama_panel.Applet):
"show_only_this_output": self.show_only_this_output,
}
def output_changed(self):
self.get_wl_resources()
def make_draggable(self):
for button in self.toplevel_buttons.values():
button.remove_controller(button.drag_source)
main.py
@@ -32,7 +32,7 @@ from pathlib import Path
import ruamel.yaml as yaml
from pywayland.client import Display, EventQueue
from pywayland.protocol.wayland.wl_output import WlOutputProxy
from pywayland.protocol.wayland import WlRegistry, WlSeat, WlSurface, WlCompositor, WlOutput, WlShm
from pywayland.protocol.wayland import WlRegistry, WlSeat, WlSurface, WlOutput, WlShm
from pywayland.protocol.wlr_foreign_toplevel_management_unstable_v1 import (
ZwlrForeignToplevelManagerV1,
ZwlrForeignToplevelHandleV1
@@ -580,13 +580,6 @@ class Panel(Gtk.Window):
applet.foreign_toplevel_output_enter(toplevel, output)
applet = applet.get_next_sibling()
def foreign_toplevel_output_leave(self, toplevel, output):
for area in (self.left_area, self.centre_area, self.right_area):
applet = area.get_first_child()
while applet:
applet.foreign_toplevel_output_leave(toplevel, output)
applet = applet.get_next_sibling()
def foreign_toplevel_app_id(self, toplevel, app_id):
for area in (self.left_area, self.centre_area, self.right_area):
applet = area.get_first_child()
@@ -677,17 +670,13 @@ class PanoramaPanel(Gtk.Application):
)
self.display = Gdk.Display.get_default()
self.monitors = self.display.get_monitors()
for monitor in self.monitors:
monitor.output_proxy = None
self.applets_by_name: dict[str, panorama_panel.Applet] = {}
self.panels: list[Panel] = []
self.wl_globals = []
self.wl_display = None
self.compositor = None
self.seat = None
self.output = None
self.wl_seat = None
self.wl_shm = None
self.manager = None
self.foreign_toplevel_manager = None
self.screencopy_manager = None
self.manager_window = None
self.edit_mode = False
@@ -707,7 +696,6 @@ class PanoramaPanel(Gtk.Application):
def on_new_toplevel(self, manager: ZwlrForeignToplevelManagerV1,
handle: ZwlrForeignToplevelHandleV1):
print("on_new_toplevel")
for panel in self.panels:
panel.foreign_toplevel_new(manager, handle)
handle.dispatcher["title"] = self.on_title_changed
@@ -717,14 +705,14 @@ class PanoramaPanel(Gtk.Application):
handle.dispatcher["state"] = self.on_state_changed
handle.dispatcher["closed"] = self.on_closed
def on_output_leave(self, handle, output):
for panel in self.panels:
panel.foreign_toplevel_output_leave(handle, output)
def on_output_enter(self, handle, output):
for panel in self.panels:
panel.foreign_toplevel_output_enter(handle, output)
def on_output_leave(self, handle, output):
for panel in self.panels:
panel.foreign_toplevel_output_leave(handle, output)
def on_title_changed(self, handle, title):
for panel in self.panels:
panel.foreign_toplevel_title(handle, title)
@@ -741,6 +729,10 @@ class PanoramaPanel(Gtk.Application):
for panel in self.panels:
panel.foreign_toplevel_closed(handle)
def foreign_toplevel_refresh(self):
for panel in self.panels:
panel.foreign_toplevel_refresh()
def receive_output_name(self, output: WlOutput, name: str):
output.name = name
@@ -759,7 +751,6 @@ class PanoramaPanel(Gtk.Application):
def on_global(self, registry, name, interface, version):
if interface == "zwlr_foreign_toplevel_manager_v1":
print("Interface registered")
self.foreign_toplevel_manager_id = name
self.foreign_toplevel_version = version
elif interface == "wl_output":
@@ -779,32 +770,26 @@ class PanoramaPanel(Gtk.Application):
for panel in self.panels:
panel.wl_output_enter(output, output.name)
if not output.name.startswith("live-preview"):
if self.manager:
self.manager.destroy()
for panel in self.panels:
panel.foreign_toplevel_refresh()
self.wl_globals.remove(self.manager)
self.manager = self.registry.bind(self.foreign_toplevel_manager_id, ZwlrForeignToplevelManagerV1, self.foreign_toplevel_version)
self.manager.id = self.foreign_toplevel_manager_id
self.wl_globals.append(self.manager)
self.manager.dispatcher["toplevel"] = self.on_new_toplevel
self.manager.dispatcher["finished"] = lambda *a: print("Toplevel manager finished")
if self.foreign_toplevel_manager:
self.foreign_toplevel_manager.destroy()
self.wl_globals.remove(self.foreign_toplevel_manager)
self.foreign_toplevel_refresh()
self.foreign_toplevel_manager = self.registry.bind(self.foreign_toplevel_manager_id, ZwlrForeignToplevelManagerV1, self.foreign_toplevel_version)
self.foreign_toplevel_manager.id = self.foreign_toplevel_manager_id
self.wl_globals.append(self.foreign_toplevel_manager)
self.foreign_toplevel_manager.dispatcher["toplevel"] = self.on_new_toplevel
self.foreign_toplevel_manager.dispatcher["finished"] = lambda *a: print("Toplevel manager finished")
self.wl_display.roundtrip()
self.panels_generated = True
print(f"Monitor {name} ({interface}) connected", file=sys.stderr)
print(f"Monitor {output.name} ({interface}) connected", file=sys.stderr)
elif interface == "wl_seat":
print("Seat found")
self.seat = registry.bind(name, WlSeat, version)
self.seat.id = name
self.wl_globals.append(self.seat)
self.wl_seat = registry.bind(name, WlSeat, version)
self.wl_seat.id = name
self.wl_globals.append(self.wl_seat)
elif interface == "wl_shm":
self.wl_shm = registry.bind(name, WlShm, version)
self.wl_shm.id = name
self.wl_globals.append(self.wl_shm)
elif interface == "wl_compositor":
self.compositor = registry.bind(name, WlCompositor, version)
self.compositor.id = name
self.wl_globals.append(self.compositor)
elif interface == "zwlr_screencopy_manager_v1":
self.screencopy_manager = registry.bind(name, ZwlrScreencopyManagerV1, version)
self.screencopy_manager.id = name
@@ -813,12 +798,6 @@ class PanoramaPanel(Gtk.Application):
def on_global_remove(self, registry, name):
if name in self.wl_output_ids:
print(f"Monitor {name} disconnected", file=sys.stderr)
#self.generate_panels()
#for g in self.wl_globals:
# if g.id == name:
# for panel in self.panels:
# panel.wl_output_leave(g, g.name)
# break
self.wl_output_ids.discard(name)
for g in self.wl_globals:
if g.id == name:
@@ -841,7 +820,6 @@ class PanoramaPanel(Gtk.Application):
yaml_loader = yaml.YAML(typ="rt")
yaml_file = yaml_loader.load(config_file)
for panel_data in yaml_file["panels"]:
print(f"comparing {panel_data["monitor"]} =? {output_name}")
if panel_data["monitor"] != output_name:
continue
position = PANEL_POSITIONS[panel_data["position"]]
@@ -882,11 +860,6 @@ class PanoramaPanel(Gtk.Application):
panel.realize()
panel.monitor_name = my_monitor.get_connector()
def prepare(self):
self.release()
print("started", file=sys.stderr)
def do_startup(self):
Gtk.Application.do_startup(self)
@@ -908,9 +881,6 @@ class PanoramaPanel(Gtk.Application):
ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer(self.display.__gpointer__, None)))
self.wl_display._ptr = wl_display_ptr
# Intentionally commented: the display is already connected by GTK
# self.display.connect() # Iterate through monitors and get their Wayland output (wl_output)
self.panels_generated = False
self.got_output_name = False
self.foreign_toplevel_manager_id = None
@@ -921,6 +891,10 @@ class PanoramaPanel(Gtk.Application):
self.registry.dispatcher["global"] = self.on_global
self.registry.dispatcher["global_remove"] = self.on_global_remove
self.wl_display.roundtrip()
if self.foreign_toplevel_version:
print("Foreign Toplevel Interface: Found")
else:
print("Foreign Toplevel Interface: Not Found")
self.hold()
shared/panorama_panel.py
@@ -78,9 +78,6 @@ class Applet(Gtk.Box):
def foreign_toplevel_output_enter(self, toplevel, output):
pass
def foreign_toplevel_output_leave(self, toplevel, output):
pass
def foreign_toplevel_app_id(self, toplevel, app_id):
pass