roundabout,
created on Sunday, 31 August 2025, 18:27:27 (1756664847),
received on Sunday, 31 August 2025, 18:27:31 (1756664851)
Author identity: Vlad <vlad.muntoiu@gmail.com>
c127eebea00b6b18953da110fdd0befb374ef2e3
main.py
@@ -30,8 +30,8 @@ import locale
from itertools import accumulate, chain
from pathlib import Path
import ruamel.yaml as yaml
from pywayland.client import Display
from pywayland.protocol.wayland import WlRegistry, WlSeat, WlSurface, WlCompositor
from pywayland.client import Display, EventQueue
from pywayland.protocol.wayland import WlRegistry, WlSeat, WlSurface, WlCompositor, WlOutput
faulthandler.enable()
@@ -47,6 +47,16 @@ gi.require_version("Gtk4LayerShell", "1.0")
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio, GObject
from gi.events import GLibEventLoopPolicy
import ctypes
from cffi import FFI
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")
sys.path.insert(0, str((Path(__file__).parent / "shared").resolve()))
locale.bindtextdomain("panorama-panel", "locale")
@@ -594,6 +604,8 @@ class PanoramaPanel(Gtk.Application):
self.manager_window = None
self.edit_mode = False
self.drags = {}
self.wl_output_ids = set()
self.started = False
self.add_main_option(
"trigger",
@@ -604,20 +616,42 @@ class PanoramaPanel(Gtk.Application):
_("NAME")
)
def do_startup(self):
Gtk.Application.do_startup(self)
def receive_output_name(self, output: WlOutput, name: str):
output.name = name
def on_global(self, registry, name, interface, version):
if interface == "wl_output":
output = registry.bind(name, WlOutput, version)
print(f"global {name} {interface} {version}")
output.global_name = name
output.dispatcher["name"] = self.receive_output_name
output.name = None
while not output.name:
self.wl_display.dispatch(block=True)
print(output.name)
if not output.name.startswith("live-preview"):
self.wl_output_ids.add(output.global_name)
self.generate_panels()
print(f"Monitor {name} ({interface}) connected", file=sys.stderr)
def on_global_remove(self, registry, name):
print("global removed")
if name in self.wl_output_ids:
print(f"Monitor {name} disconnected", file=sys.stderr)
self.generate_panels()
self.wl_output_ids.discard(name)
def generate_panels(self):
print("Generating panels...", file=sys.stderr)
self.exit_all_applets()
for panel in self.panels:
panel.destroy()
for i, monitor in enumerate(self.monitors):
geometry = monitor.get_geometry()
print(f"Monitor {i}: {geometry.width}x{geometry.height} at {geometry.x},{geometry.y}")
all_applets = list(chain.from_iterable(load_packages_from_dir(d) for d in get_applet_directories()))
print("Applets:")
subclasses = get_all_subclasses(panorama_panel.Applet)
for subclass in subclasses:
if subclass.__name__ in self.applets_by_name:
print(f"Name conflict for applet {subclass.__name__}. Only one will be loaded.", file=sys.stderr)
self.applets_by_name[subclass.__name__] = subclass
with open(get_config_file(), "r") as config_file:
yaml_loader = yaml.YAML(typ="rt")
yaml_file = yaml_loader.load(config_file)
@@ -655,6 +689,41 @@ class PanoramaPanel(Gtk.Application):
panel.present()
panel.realize()
def prepare(self):
self.release()
print("started", file=sys.stderr)
def do_startup(self):
Gtk.Application.do_startup(self)
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = (ctypes.py_object,)
all_applets = list(
chain.from_iterable(load_packages_from_dir(d) for d in get_applet_directories()))
print("Applets:")
subclasses = get_all_subclasses(panorama_panel.Applet)
for subclass in subclasses:
if subclass.__name__ in self.applets_by_name:
print(f"Name conflict for applet {subclass.__name__}. Only one will be loaded.",
file=sys.stderr)
self.applets_by_name[subclass.__name__] = subclass
self.wl_display = Display()
wl_display_ptr = gtk.gdk_wayland_display_get_wl_display(
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()
self.registry = self.wl_display.get_registry()
self.registry.dispatcher["global"] = self.on_global
self.registry.dispatcher["global_remove"] = self.on_global_remove
self.hold()
def do_activate(self):
Gio.Application.do_activate(self)
@@ -688,8 +757,7 @@ class PanoramaPanel(Gtk.Application):
yaml_writer.dump(data, config_file)
def do_shutdown(self):
print("Shutting down")
def exit_all_applets(self):
for panel in self.panels:
for area in (panel.left_area, panel.centre_area, panel.right_area):
applet = area.get_first_child()
@@ -697,6 +765,10 @@ class PanoramaPanel(Gtk.Application):
applet.shutdown(self)
applet = applet.get_next_sibling()
def do_shutdown(self):
print("Shutting down")
self.exit_all_applets()
Gtk.Application.do_shutdown(self)
def do_command_line(self, command_line: Gio.ApplicationCommandLine):