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 • 11.09 kiB
Python script, ASCII text executable
        
            
1
from __future__ import annotations
2
3
import os
4
import sys
5
import importlib
6
from itertools import accumulate, chain
7
from pathlib import Path
8
import ruamel.yaml as yaml
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
gi.require_version("Gtk", "4.0")
17
gi.require_version("Gtk4LayerShell", "1.0")
18
19
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio
20
21
sys.path.insert(0, str((Path(__file__).parent / "shared").resolve()))
22
23
import panorama_panel
24
25
26
@Gtk.Template(filename="panel-manager.ui")
27
class PanelManager(Gtk.Window):
28
__gtype_name__ = "PanelManager"
29
30
panel_editing_switch = Gtk.Template.Child()
31
32
def __init__(self, application: Gtk.Application, **kwargs):
33
super().__init__(application=application, **kwargs)
34
35
self.connect("close-request", lambda *args: self.destroy())
36
37
action_group = Gio.SimpleActionGroup()
38
39
toggle_edit_mode_action = Gio.SimpleAction(name="toggle-edit-mode-switch")
40
action_group.add_action(toggle_edit_mode_action)
41
toggle_edit_mode_action.connect("activate", lambda *args: self.panel_editing_switch.set_active(not self.panel_editing_switch.get_active()))
42
43
self.insert_action_group("win", action_group)
44
if isinstance(self.get_application(), PanoramaPanel):
45
self.panel_editing_switch.set_active(application.edit_mode)
46
self.panel_editing_switch.connect("state-set", self.set_edit_mode)
47
48
self.connect("close-request", lambda *args: self.destroy())
49
50
def set_edit_mode(self, switch, value):
51
if isinstance(self.get_application(), PanoramaPanel):
52
self.get_application().set_edit_mode(value)
53
54
@Gtk.Template.Callback()
55
def save_settings(self, *args):
56
if isinstance(self.get_application(), PanoramaPanel):
57
print("Saving settings as user requested")
58
self.get_application().save_config()
59
60
61
def get_applet_directories():
62
data_home = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share"))
63
data_dirs = [Path(d) for d in os.getenv("XDG_DATA_DIRS", "/usr/local/share:/usr/share").split(":")]
64
65
all_paths = [data_home / "panorama-panel" / "applets"] + [d / "panorama-panel" / "applets" for d in data_dirs]
66
return [d for d in all_paths if d.is_dir()]
67
68
69
def get_config_file():
70
config_home = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
71
72
return config_home / "panorama-panel" / "config.yaml"
73
74
75
class AppletArea(Gtk.Box):
76
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL):
77
super().__init__()
78
79
def set_edit_mode(self, value):
80
child = self.get_first_child()
81
while child is not None:
82
child.set_sensitive(not value)
83
child.set_opacity(0.75 if value else 1)
84
child = child.get_next_sibling()
85
86
87
class Panel(Gtk.Window):
88
def __init__(self, application: Gtk.Application, monitor: Gdk.Monitor, position: Gtk.PositionType = Gtk.PositionType.TOP, size: int = 40, monitor_index: int = 0):
89
super().__init__(application=application)
90
self.set_default_size(800, size)
91
self.set_decorated(False)
92
self.position = position
93
self.monitor_index = monitor_index
94
self.size = size
95
96
Gtk4LayerShell.init_for_window(self)
97
98
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.TOP)
99
100
Gtk4LayerShell.auto_exclusive_zone_enable(self)
101
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
102
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
103
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
104
105
box = Gtk.CenterBox()
106
107
match position:
108
case Gtk.PositionType.TOP | Gtk.PositionType.BOTTOM:
109
box.set_orientation(Gtk.Orientation.HORIZONTAL)
110
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
111
box.set_orientation(Gtk.Orientation.VERTICAL)
112
113
self.set_child(box)
114
115
self.left_area = AppletArea(orientation=box.get_orientation())
116
self.centre_area = AppletArea(orientation=box.get_orientation())
117
self.right_area = AppletArea(orientation=box.get_orientation())
118
119
box.set_start_widget(self.left_area)
120
box.set_center_widget(self.centre_area)
121
box.set_end_widget(self.right_area)
122
123
# Add a context menu
124
menu = Gio.Menu()
125
126
menu.append("Open _manager", "panel.manager")
127
128
self.context_menu = Gtk.PopoverMenu.new_from_model(menu)
129
self.context_menu.set_has_arrow(False)
130
self.context_menu.set_parent(self)
131
self.context_menu.set_halign(Gtk.Align.START)
132
self.context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
133
134
right_click_controller = Gtk.GestureClick()
135
right_click_controller.set_button(3)
136
right_click_controller.connect("pressed", self.show_context_menu)
137
138
self.add_controller(right_click_controller)
139
140
action_group = Gio.SimpleActionGroup()
141
manager_action = Gio.SimpleAction.new("manager", None)
142
manager_action.connect("activate", self.show_manager)
143
action_group.add_action(manager_action)
144
self.insert_action_group("panel", action_group)
145
146
def set_edit_mode(self, value):
147
for area in (self.left_area, self.centre_area, self.right_area):
148
area.set_edit_mode(value)
149
150
def show_context_menu(self, gesture, n_presses, x, y):
151
rect = Gdk.Rectangle()
152
rect.x = int(x)
153
rect.y = int(y)
154
rect.width = 1
155
rect.height = 1
156
157
self.context_menu.set_pointing_to(rect)
158
self.context_menu.popup()
159
160
def show_manager(self, _0=None, _1=None):
161
print("Showing manager")
162
if self.get_application():
163
if not self.get_application().manager_window:
164
self.get_application().manager_window = PanelManager(self.get_application())
165
self.get_application().manager_window.connect("close-request", self.get_application().reset_manager_window)
166
self.get_application().manager_window.present()
167
168
def get_orientation(self):
169
box = self.get_first_child()
170
return box.get_orientation()
171
172
173
def get_all_subclasses(klass: type) -> list[type]:
174
subclasses = []
175
for subclass in klass.__subclasses__():
176
subclasses.append(subclass)
177
subclasses += get_all_subclasses(subclass)
178
179
return subclasses
180
181
def load_packages_from_dir(dir_path: Path):
182
loaded_modules = []
183
184
for path in dir_path.iterdir():
185
if path.name.startswith("_"):
186
continue
187
188
if path.is_dir() and (path / "__init__.py").exists():
189
module_name = path.name
190
spec = importlib.util.spec_from_file_location(module_name, path / "__init__.py")
191
module = importlib.util.module_from_spec(spec)
192
spec.loader.exec_module(module)
193
loaded_modules.append(module)
194
else:
195
continue
196
197
return loaded_modules
198
199
200
PANEL_POSITIONS = {
201
"top": Gtk.PositionType.TOP,
202
"bottom": Gtk.PositionType.BOTTOM,
203
"left": Gtk.PositionType.LEFT,
204
"right": Gtk.PositionType.RIGHT,
205
}
206
207
208
PANEL_POSITIONS_REVERSE = {
209
Gtk.PositionType.TOP: "top",
210
Gtk.PositionType.BOTTOM: "bottom",
211
Gtk.PositionType.LEFT: "left",
212
Gtk.PositionType.RIGHT: "right",
213
}
214
215
216
class PanoramaPanel(Gtk.Application):
217
def __init__(self):
218
super().__init__(application_id="com.roundabout_host.panorama.panel")
219
self.display = Gdk.Display.get_default()
220
self.monitors = self.display.get_monitors()
221
self.applets_by_name = {}
222
self.panels = []
223
self.manager_window = None
224
self.edit_mode = False
225
226
def do_startup(self):
227
Gtk.Application.do_startup(self)
228
for i, monitor in enumerate(self.monitors):
229
geometry = monitor.get_geometry()
230
print(f"Monitor {i}: {geometry.width}x{geometry.height} at {geometry.x},{geometry.y}")
231
232
all_applets = list(chain.from_iterable(load_packages_from_dir(d) for d in get_applet_directories()))
233
print("Applets:")
234
subclasses = get_all_subclasses(panorama_panel.Applet)
235
for subclass in subclasses:
236
if subclass.__name__ in self.applets_by_name:
237
print(f"Name conflict for applet {subclass.__name__}. Only one will be loaded.", file=sys.stderr)
238
self.applets_by_name[subclass.__name__] = subclass
239
240
with open(get_config_file(), "r") as config_file:
241
yaml_loader = yaml.YAML(typ="rt")
242
yaml_file = yaml_loader.load(config_file)
243
for panel_data in yaml_file["panels"]:
244
position = PANEL_POSITIONS[panel_data["position"]]
245
monitor_index = panel_data["monitor"]
246
monitor = self.monitors[monitor_index]
247
size = panel_data["size"]
248
249
panel = Panel(self, monitor, position, size, monitor_index)
250
self.panels.append(panel)
251
panel.show()
252
253
print(f"{size}px panel on {position} edge of monitor {monitor_index}")
254
255
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
256
applet_list = panel_data["applets"].get(area_name)
257
if applet_list is None:
258
continue
259
260
for applet in applet_list:
261
item = list(applet.items())[0]
262
AppletClass = self.applets_by_name[item[0]]
263
options = item[1]
264
applet_widget = AppletClass(orientation=panel.get_orientation(), config=options)
265
266
area.append(applet_widget)
267
268
def do_activate(self):
269
Gio.Application.do_activate(self)
270
271
def save_config(self):
272
with open(get_config_file(), "w") as config_file:
273
yaml_writer = yaml.YAML(typ="rt")
274
data = {"panels": []}
275
for panel in self.panels:
276
panel_data = {
277
"position": PANEL_POSITIONS_REVERSE[panel.position],
278
"monitor": panel.monitor_index,
279
"size": panel.size,
280
"applets": {}
281
}
282
283
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
284
panel_data["applets"][area_name] = []
285
applet = area.get_first_child()
286
while applet is not None:
287
panel_data["applets"][area_name].append({
288
applet.__class__.__name__: applet.get_config(),
289
})
290
291
applet = applet.get_next_sibling()
292
293
data["panels"].append(panel_data)
294
295
yaml_writer.dump(data, config_file)
296
297
def do_shutdown(self):
298
print("Shutting down")
299
Gtk.Application.do_shutdown(self)
300
self.save_config()
301
302
def set_edit_mode(self, value):
303
self.edit_mode = value
304
for panel in self.panels:
305
panel.set_edit_mode(value)
306
307
def reset_manager_window(self, *args):
308
self.manager_window = None
309
310
311
if __name__ == "__main__":
312
app = PanoramaPanel()
313
app.run(sys.argv)
314