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

 main.py

View raw Download
text/x-script.python • 4.54 kiB
Python script, ASCII text executable
        
            
1
import os
2
import faulthandler
3
import ruamel.yaml as yaml
4
from pywayland.client import Display, EventQueue
5
from pywayland.protocol.wayland import WlOutput
6
from pathlib import Path
7
8
faulthandler.enable()
9
10
os.environ["GI_TYPELIB_PATH"] = "/usr/local/lib/x86_64-linux-gnu/girepository-1.0"
11
12
from ctypes import CDLL
13
CDLL("libgtk4-layer-shell.so")
14
15
import gi
16
17
gi.require_version("Gtk", "4.0")
18
gi.require_version("Gtk4LayerShell", "1.0")
19
20
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio, GObject
21
22
import ctypes
23
from cffi import FFI
24
ffi = FFI()
25
ffi.cdef("""
26
void * gdk_wayland_display_get_wl_display (void * display);
27
void * gdk_wayland_surface_get_wl_surface (void * surface);
28
void * gdk_wayland_monitor_get_wl_output (void * monitor);
29
""")
30
gtk = ffi.dlopen("libgtk-4.so.1")
31
32
33
def get_config_file():
34
config_home = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
35
36
return config_home / "panorama-bg" / "config.yaml"
37
38
39
FITTING_MODES = {
40
"cover": Gtk.ContentFit.COVER,
41
"contain": Gtk.ContentFit.CONTAIN,
42
"fill": Gtk.ContentFit.FILL,
43
"scale_down": Gtk.ContentFit.SCALE_DOWN
44
}
45
46
47
class PanoramaBG(Gtk.Application):
48
def __init__(self):
49
super().__init__(
50
application_id="com.roundabout_host.roundabout.PanoramaBG"
51
)
52
self.wl_output_ids: dict[int, WlOutput] = {}
53
self.wl_display = None
54
self.display = None
55
56
def make_backgrounds(self):
57
for window in self.get_windows():
58
window.destroy()
59
60
with open(get_config_file(), "r") as config_file:
61
yaml_loader = yaml.YAML(typ="rt")
62
yaml_file = yaml_loader.load(config_file)
63
64
monitors = self.display.get_monitors()
65
66
for monitor in monitors:
67
if monitor.get_connector() in yaml_file["monitors"]:
68
background_window = BackgroundWindow(self, monitor)
69
background_window.picture.set_filename(yaml_file["monitors"][monitor.get_connector()]["sources"][0])
70
background_window.picture.set_content_fit(FITTING_MODES[yaml_file["monitors"][monitor.get_connector()]["fitting_mode"]])
71
background_window.present()
72
73
def do_startup(self):
74
Gtk.Application.do_startup(self)
75
self.hold()
76
77
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
78
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = (ctypes.py_object,)
79
80
self.display = Gdk.Display.get_default()
81
82
self.wl_display = Display()
83
wl_display_ptr = gtk.gdk_wayland_display_get_wl_display(
84
ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer(self.display.__gpointer__, None)))
85
self.wl_display._ptr = wl_display_ptr
86
self.registry = self.wl_display.get_registry()
87
self.registry.dispatcher["global"] = self.on_global
88
self.registry.dispatcher["global_remove"] = self.on_global_remove
89
self.wl_display.roundtrip()
90
91
def receive_output_name(self, output: WlOutput, name: str):
92
output.name = name
93
94
def on_global(self, registry, name, interface, version):
95
if interface == "wl_output":
96
output = registry.bind(name, WlOutput, version)
97
output.id = name
98
output.name = None
99
output.dispatcher["name"] = self.receive_output_name
100
while not output.name:
101
self.wl_display.dispatch(block=True)
102
if not output.name.startswith("live-preview"):
103
self.make_backgrounds()
104
self.wl_output_ids[name] = output
105
106
def on_global_remove(self, registry, name):
107
if name in self.wl_output_ids:
108
self.wl_output_ids[name].destroy()
109
del self.wl_output_ids[name]
110
111
112
class BackgroundWindow(Gtk.Window):
113
def __init__(self, application: Gtk.Application, monitor: Gdk.Monitor):
114
super().__init__(application=application)
115
self.set_decorated(False)
116
117
Gtk4LayerShell.init_for_window(self)
118
Gtk4LayerShell.set_namespace(self, "com.roundabout_host.panorama.background")
119
Gtk4LayerShell.set_monitor(self, monitor)
120
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.BACKGROUND)
121
122
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
123
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
124
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
125
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
126
127
self.picture = Gtk.Picture()
128
self.set_child(self.picture)
129
130
131
if __name__ == "__main__":
132
app = PanoramaBG()
133
app.run()
134
135