By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

[WIP] regenerate panels when outputs change

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):