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/plain • 25.07 kiB
Python script, ASCII text executable
        
            
1
"""
2
Panorama panel: traditional desktop panel using Wayland layer-shell
3
protocol.
4
Copyright 2025, roundabout-host.com <vlad@roundabout-host.com>
5
6
This program is free software: you can redistribute it and/or modify
7
it under the terms of the GNU General Public Licence as published by
8
the Free Software Foundation, either version 3 of the Licence, or
9
(at your option) any later version.
10
11
This program is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
GNU General Public Licence for more details.
15
16
You should have received a copy of the GNU General Public Licence
17
along with this program. If not, see <https://www.gnu.org/licenses/>.
18
"""
19
20
from __future__ import annotations
21
22
import os
23
import sys
24
import importlib
25
import time
26
import traceback
27
import typing
28
import locale
29
from itertools import accumulate, chain
30
from pathlib import Path
31
import ruamel.yaml as yaml
32
from pywayland.client import Display
33
from pywayland.protocol.wayland import WlRegistry, WlSeat, WlSurface, WlCompositor
34
35
os.environ["GI_TYPELIB_PATH"] = "/usr/local/lib/x86_64-linux-gnu/girepository-1.0"
36
37
from ctypes import CDLL
38
CDLL('libgtk4-layer-shell.so')
39
40
import gi
41
gi.require_version("Gtk", "4.0")
42
gi.require_version("Gtk4LayerShell", "1.0")
43
44
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio, GObject
45
46
sys.path.insert(0, str((Path(__file__).parent / "shared").resolve()))
47
48
import panorama_panel
49
50
locale.bindtextdomain("panorama-panel", "locale")
51
locale.textdomain("panorama-panel")
52
locale.setlocale(locale.LC_ALL)
53
_ = locale.gettext
54
55
56
custom_css = """
57
.panel-flash {
58
animation: flash 333ms ease-in-out 0s 2;
59
}
60
61
@keyframes flash {
62
0% {
63
background-color: initial;
64
}
65
50% {
66
background-color: #ffff0080;
67
}
68
0% {
69
background-color: initial;
70
}
71
}
72
"""
73
74
css_provider = Gtk.CssProvider()
75
css_provider.load_from_data(custom_css)
76
Gtk.StyleContext.add_provider_for_display(
77
Gdk.Display.get_default(),
78
css_provider,
79
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
80
)
81
82
83
@Gtk.Template(filename="panel-configurator.ui")
84
class PanelConfigurator(Gtk.Frame):
85
__gtype_name__ = "PanelConfigurator"
86
87
panel_size_adjustment: Gtk.Adjustment = Gtk.Template.Child()
88
monitor_number_adjustment: Gtk.Adjustment = Gtk.Template.Child()
89
top_position_radio: Gtk.CheckButton = Gtk.Template.Child()
90
bottom_position_radio: Gtk.CheckButton = Gtk.Template.Child()
91
left_position_radio: Gtk.CheckButton = Gtk.Template.Child()
92
right_position_radio: Gtk.CheckButton = Gtk.Template.Child()
93
autohide_switch: Gtk.Switch = Gtk.Template.Child()
94
95
def __init__(self, panel: Panel, **kwargs):
96
super().__init__(**kwargs)
97
self.panel = panel
98
self.panel_size_adjustment.set_value(panel.size)
99
100
match self.panel.position:
101
case Gtk.PositionType.TOP:
102
self.top_position_radio.set_active(True)
103
case Gtk.PositionType.BOTTOM:
104
self.bottom_position_radio.set_active(True)
105
case Gtk.PositionType.LEFT:
106
self.left_position_radio.set_active(True)
107
case Gtk.PositionType.RIGHT:
108
self.right_position_radio.set_active(True)
109
110
self.top_position_radio.panel_position_target = Gtk.PositionType.TOP
111
self.bottom_position_radio.panel_position_target = Gtk.PositionType.BOTTOM
112
self.left_position_radio.panel_position_target = Gtk.PositionType.LEFT
113
self.right_position_radio.panel_position_target = Gtk.PositionType.RIGHT
114
self.autohide_switch.set_active(self.panel.autohide)
115
116
@Gtk.Template.Callback()
117
def update_panel_size(self, adjustment: Gtk.Adjustment):
118
if not self.get_root():
119
return
120
self.panel.set_size(int(adjustment.get_value()))
121
122
@Gtk.Template.Callback()
123
def toggle_autohide(self, switch: Gtk.Switch, value: bool):
124
if not self.get_root():
125
return
126
self.panel.set_autohide(value, self.panel.hide_time)
127
128
@Gtk.Template.Callback()
129
def move_panel(self, button: Gtk.CheckButton):
130
if not self.get_root():
131
return
132
if not button.get_active():
133
return
134
135
self.panel.set_position(button.panel_position_target)
136
self.update_panel_size(self.panel_size_adjustment)
137
138
# Make the applets aware of the changed orientation
139
for area in (self.panel.left_area, self.panel.centre_area, self.panel.right_area):
140
applet = area.get_first_child()
141
while applet:
142
applet.set_orientation(self.panel.get_orientation())
143
applet.set_panel_position(self.panel.position)
144
applet.queue_resize()
145
applet = applet.get_next_sibling()
146
147
@Gtk.Template.Callback()
148
def move_to_monitor(self, adjustment: Gtk.Adjustment):
149
if not self.get_root():
150
return
151
app: PanoramaPanel = self.get_root().get_application()
152
monitor = app.monitors[int(self.monitor_number_adjustment.get_value())]
153
self.panel.unmap()
154
Gtk4LayerShell.set_monitor(self.panel, monitor)
155
self.panel.show()
156
157
158
PANEL_POSITIONS_HUMAN = {
159
Gtk.PositionType.TOP: "top",
160
Gtk.PositionType.BOTTOM: "bottom",
161
Gtk.PositionType.LEFT: "left",
162
Gtk.PositionType.RIGHT: "right",
163
}
164
165
166
@Gtk.Template(filename="panel-manager.ui")
167
class PanelManager(Gtk.Window):
168
__gtype_name__ = "PanelManager"
169
170
panel_editing_switch: Gtk.Switch = Gtk.Template.Child()
171
panel_stack: Gtk.Stack = Gtk.Template.Child()
172
current_panel: typing.Optional[Panel] = None
173
174
def __init__(self, application: Gtk.Application, **kwargs):
175
super().__init__(application=application, **kwargs)
176
177
self.connect("close-request", lambda *args: self.destroy())
178
179
action_group = Gio.SimpleActionGroup()
180
181
self.next_panel_action = Gio.SimpleAction(name="next-panel")
182
action_group.add_action(self.next_panel_action)
183
self.next_panel_action.connect("activate", lambda *args: self.panel_stack.set_visible_child(self.panel_stack.get_visible_child().get_next_sibling()))
184
185
self.previous_panel_action = Gio.SimpleAction(name="previous-panel")
186
action_group.add_action(self.previous_panel_action)
187
self.previous_panel_action.connect("activate", lambda *args: self.panel_stack.set_visible_child(self.panel_stack.get_visible_child().get_prev_sibling()))
188
189
self.insert_action_group("win", action_group)
190
if isinstance(self.get_application(), PanoramaPanel):
191
self.panel_editing_switch.set_active(application.edit_mode)
192
self.panel_editing_switch.connect("state-set", self.set_edit_mode)
193
194
self.connect("close-request", lambda *args: self.unflash_old_panel())
195
self.connect("close-request", lambda *args: self.destroy())
196
197
if isinstance(self.get_application(), PanoramaPanel):
198
app: PanoramaPanel = self.get_application()
199
for panel in app.panels:
200
configurator = PanelConfigurator(panel)
201
self.panel_stack.add_child(configurator)
202
configurator.monitor_number_adjustment.set_upper(len(app.monitors))
203
204
self.panel_stack.set_visible_child(self.panel_stack.get_first_child())
205
self.panel_stack.connect("notify::visible-child", self.set_visible_panel)
206
self.panel_stack.notify("visible-child")
207
208
def unflash_old_panel(self):
209
if self.current_panel:
210
for area in (self.current_panel.left_area, self.current_panel.centre_area, self.current_panel.right_area):
211
area.unflash()
212
213
def set_visible_panel(self, stack: Gtk.Stack, pspec: GObject.ParamSpec):
214
self.unflash_old_panel()
215
216
panel: Panel = stack.get_visible_child().panel
217
218
self.current_panel = panel
219
self.next_panel_action.set_enabled(stack.get_visible_child().get_next_sibling() is not None)
220
self.previous_panel_action.set_enabled(stack.get_visible_child().get_prev_sibling() is not None)
221
222
# Start an animation to show the user what panel is being edited
223
for area in (panel.left_area, panel.centre_area, panel.right_area):
224
area.flash()
225
226
def set_edit_mode(self, switch, value):
227
if isinstance(self.get_application(), PanoramaPanel):
228
self.get_application().set_edit_mode(value)
229
230
@Gtk.Template.Callback()
231
def save_settings(self, *args):
232
if isinstance(self.get_application(), PanoramaPanel):
233
self.get_application().save_config()
234
235
236
def get_applet_directories():
237
data_home = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share"))
238
data_dirs = [Path(d) for d in os.getenv("XDG_DATA_DIRS", "/usr/local/share:/usr/share").split(":")]
239
240
all_paths = [data_home / "panorama-panel" / "applets"] + [d / "panorama-panel" / "applets" for d in data_dirs]
241
return [d for d in all_paths if d.is_dir()]
242
243
244
def get_config_file():
245
config_home = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
246
247
return config_home / "panorama-panel" / "config.yaml"
248
249
250
class AppletArea(Gtk.Box):
251
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL):
252
super().__init__()
253
254
self.drop_target = Gtk.DropTarget.new(GObject.TYPE_UINT64, Gdk.DragAction.MOVE)
255
self.drop_target.set_gtypes([GObject.TYPE_UINT64])
256
self.drop_target.connect("drop", self.drop_applet)
257
258
def drop_applet(self, drop_target: Gtk.DropTarget, value: int, x: float, y: float):
259
old_area: AppletArea = applet.get_parent()
260
old_area.remove(applet)
261
# Find the position where to insert the applet
262
child = self.get_first_child()
263
while child:
264
allocation = child.get_allocation()
265
child_x, child_y = self.translate_coordinates(self, 0, 0)
266
if self.get_orientation() == Gtk.Orientation.HORIZONTAL:
267
midpoint = child_x + allocation.width / 2
268
if x < midpoint:
269
applet.insert_before(self, child)
270
break
271
elif self.get_orientation() == Gtk.Orientation.VERTICAL:
272
midpoint = child_y + allocation.height / 2
273
if y < midpoint:
274
applet.insert_before(self, child)
275
break
276
child = child.get_next_sibling()
277
else:
278
self.append(applet)
279
applet.show()
280
return True
281
282
def set_edit_mode(self, value):
283
panel: Panel = self.get_root()
284
child = self.get_first_child()
285
while child is not None:
286
if value:
287
child.make_draggable()
288
else:
289
child.restore_drag()
290
child.set_opacity(0.75 if value else 1)
291
child = child.get_next_sibling()
292
293
if value:
294
self.add_controller(self.drop_target)
295
if panel.get_orientation() == Gtk.Orientation.HORIZONTAL:
296
self.set_size_request(48, 0)
297
elif panel.get_orientation() == Gtk.Orientation.VERTICAL:
298
self.set_size_request(0, 48)
299
else:
300
self.remove_controller(self.drop_target)
301
self.set_size_request(0, 0)
302
303
def flash(self):
304
self.add_css_class("panel-flash")
305
306
def unflash(self):
307
self.remove_css_class("panel-flash")
308
309
310
POSITION_TO_LAYER_SHELL_EDGE = {
311
Gtk.PositionType.TOP: Gtk4LayerShell.Edge.TOP,
312
Gtk.PositionType.BOTTOM: Gtk4LayerShell.Edge.BOTTOM,
313
Gtk.PositionType.LEFT: Gtk4LayerShell.Edge.LEFT,
314
Gtk.PositionType.RIGHT: Gtk4LayerShell.Edge.RIGHT,
315
}
316
317
318
class Panel(Gtk.Window):
319
def __init__(self, application: Gtk.Application, monitor: Gdk.Monitor, position: Gtk.PositionType = Gtk.PositionType.TOP, size: int = 40, monitor_index: int = 0, autohide: bool = False, hide_time: int = 0):
320
super().__init__(application=application)
321
self.drop_motion_controller = None
322
self.motion_controller = None
323
self.set_decorated(False)
324
self.position = None
325
self.autohide = None
326
self.monitor_index = monitor_index
327
self.hide_time = None
328
self.open_popovers: set[int] = set()
329
330
Gtk4LayerShell.init_for_window(self)
331
Gtk4LayerShell.set_monitor(self, monitor)
332
333
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.TOP)
334
335
box = Gtk.CenterBox()
336
337
self.set_child(box)
338
self.set_position(position)
339
self.set_size(size)
340
self.set_autohide(autohide, hide_time)
341
342
self.left_area = AppletArea(orientation=box.get_orientation())
343
self.centre_area = AppletArea(orientation=box.get_orientation())
344
self.right_area = AppletArea(orientation=box.get_orientation())
345
346
box.set_start_widget(self.left_area)
347
box.set_center_widget(self.centre_area)
348
box.set_end_widget(self.right_area)
349
350
# Add a context menu
351
menu = Gio.Menu()
352
353
menu.append(_("Open _manager"), "panel.manager")
354
355
self.context_menu = Gtk.PopoverMenu.new_from_model(menu)
356
self.context_menu.set_has_arrow(False)
357
self.context_menu.set_parent(self)
358
self.context_menu.set_halign(Gtk.Align.START)
359
self.context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
360
panorama_panel.track_popover(self.context_menu)
361
362
right_click_controller = Gtk.GestureClick()
363
right_click_controller.set_button(3)
364
right_click_controller.connect("pressed", self.show_context_menu)
365
366
self.add_controller(right_click_controller)
367
368
action_group = Gio.SimpleActionGroup()
369
manager_action = Gio.SimpleAction.new("manager", None)
370
manager_action.connect("activate", self.show_manager)
371
action_group.add_action(manager_action)
372
self.insert_action_group("panel", action_group)
373
374
def reset_margins(self):
375
for edge in POSITION_TO_LAYER_SHELL_EDGE.values():
376
Gtk4LayerShell.set_margin(self, edge, 0)
377
378
def set_edit_mode(self, value):
379
for area in (self.left_area, self.centre_area, self.right_area):
380
area.set_edit_mode(value)
381
382
def show_context_menu(self, gesture, n_presses, x, y):
383
rect = Gdk.Rectangle()
384
rect.x = int(x)
385
rect.y = int(y)
386
rect.width = 1
387
rect.height = 1
388
389
self.context_menu.set_pointing_to(rect)
390
self.context_menu.popup()
391
392
def slide_in(self):
393
if not self.autohide:
394
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 0)
395
return False
396
397
if Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) >= 0:
398
return False
399
400
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) + 1)
401
return True
402
403
def slide_out(self):
404
if not self.autohide:
405
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 0)
406
return False
407
408
if Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) <= 1 - self.size:
409
return False
410
411
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) - 1)
412
return True
413
414
def show_manager(self, _0=None, _1=None):
415
if self.get_application():
416
if not self.get_application().manager_window:
417
self.get_application().manager_window = PanelManager(self.get_application())
418
self.get_application().manager_window.connect("close-request", self.get_application().reset_manager_window)
419
self.get_application().manager_window.present()
420
421
def get_orientation(self):
422
box = self.get_first_child()
423
return box.get_orientation()
424
425
def set_size(self, value: int):
426
self.size = int(value)
427
if self.get_orientation() == Gtk.Orientation.HORIZONTAL:
428
self.set_size_request(800, self.size)
429
self.set_default_size(800, self.size)
430
else:
431
self.set_size_request(self.size, 600)
432
self.set_default_size(self.size, 600)
433
434
def set_position(self, position: Gtk.PositionType):
435
self.position = position
436
self.reset_margins()
437
match self.position:
438
case Gtk.PositionType.TOP:
439
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, False)
440
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
441
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
442
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
443
case Gtk.PositionType.BOTTOM:
444
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, False)
445
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
446
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
447
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
448
case Gtk.PositionType.LEFT:
449
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, False)
450
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
451
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
452
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
453
case Gtk.PositionType.RIGHT:
454
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, False)
455
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
456
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
457
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
458
box = self.get_first_child()
459
match self.position:
460
case Gtk.PositionType.TOP | Gtk.PositionType.BOTTOM:
461
box.set_orientation(Gtk.Orientation.HORIZONTAL)
462
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
463
box.set_orientation(Gtk.Orientation.VERTICAL)
464
465
if self.autohide:
466
if not self.open_popovers:
467
GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out)
468
469
def set_autohide(self, autohide: bool, hide_time: int):
470
self.autohide = autohide
471
self.hide_time = hide_time
472
if not self.autohide:
473
self.reset_margins()
474
Gtk4LayerShell.auto_exclusive_zone_enable(self)
475
if self.motion_controller is not None:
476
self.remove_controller(self.motion_controller)
477
if self.drop_motion_controller is not None:
478
self.remove_controller(self.drop_motion_controller)
479
self.motion_controller = None
480
self.drop_motion_controller = None
481
self.reset_margins()
482
else:
483
Gtk4LayerShell.set_exclusive_zone(self, 0)
484
# Only leave 1px of the window as a "mouse sensor"
485
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 1 - self.size)
486
self.motion_controller = Gtk.EventControllerMotion()
487
self.add_controller(self.motion_controller)
488
self.drop_motion_controller = Gtk.DropControllerMotion()
489
self.add_controller(self.drop_motion_controller)
490
491
self.motion_controller.connect("enter", lambda *args: GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_in))
492
self.drop_motion_controller.connect("enter", lambda *args: GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_in))
493
self.motion_controller.connect("leave", lambda *args: not self.open_popovers and GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out))
494
self.drop_motion_controller.connect("leave", lambda *args: not self.open_popovers and GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out))
495
496
def get_all_subclasses(klass: type) -> list[type]:
497
subclasses = []
498
for subclass in klass.__subclasses__():
499
subclasses.append(subclass)
500
subclasses += get_all_subclasses(subclass)
501
502
return subclasses
503
504
def load_packages_from_dir(dir_path: Path):
505
loaded_modules = []
506
507
for path in dir_path.iterdir():
508
if path.name.startswith("_"):
509
continue
510
511
if path.is_dir() and (path / "__init__.py").exists():
512
module_name = path.name
513
spec = importlib.util.spec_from_file_location(module_name, path / "__init__.py")
514
module = importlib.util.module_from_spec(spec)
515
spec.loader.exec_module(module)
516
loaded_modules.append(module)
517
else:
518
continue
519
520
return loaded_modules
521
522
523
PANEL_POSITIONS = {
524
"top": Gtk.PositionType.TOP,
525
"bottom": Gtk.PositionType.BOTTOM,
526
"left": Gtk.PositionType.LEFT,
527
"right": Gtk.PositionType.RIGHT,
528
}
529
530
531
PANEL_POSITIONS_REVERSE = {
532
Gtk.PositionType.TOP: "top",
533
Gtk.PositionType.BOTTOM: "bottom",
534
Gtk.PositionType.LEFT: "left",
535
Gtk.PositionType.RIGHT: "right",
536
}
537
538
539
class PanoramaPanel(Gtk.Application):
540
def __init__(self):
541
super().__init__(application_id="com.roundabout_host.panorama.panel")
542
self.display = Gdk.Display.get_default()
543
self.monitors = self.display.get_monitors()
544
self.applets_by_name: dict[str, panorama_panel.Applet] = {}
545
self.panels: list[Panel] = []
546
self.manager_window = None
547
self.edit_mode = False
548
self.drags = {}
549
550
def do_startup(self):
551
Gtk.Application.do_startup(self)
552
for i, monitor in enumerate(self.monitors):
553
geometry = monitor.get_geometry()
554
print(f"Monitor {i}: {geometry.width}x{geometry.height} at {geometry.x},{geometry.y}")
555
556
all_applets = list(chain.from_iterable(load_packages_from_dir(d) for d in get_applet_directories()))
557
print("Applets:")
558
subclasses = get_all_subclasses(panorama_panel.Applet)
559
for subclass in subclasses:
560
if subclass.__name__ in self.applets_by_name:
561
print(f"Name conflict for applet {subclass.__name__}. Only one will be loaded.", file=sys.stderr)
562
self.applets_by_name[subclass.__name__] = subclass
563
564
with open(get_config_file(), "r") as config_file:
565
yaml_loader = yaml.YAML(typ="rt")
566
yaml_file = yaml_loader.load(config_file)
567
for panel_data in yaml_file["panels"]:
568
position = PANEL_POSITIONS[panel_data["position"]]
569
monitor_index = panel_data["monitor"]
570
if monitor_index >= len(self.monitors):
571
continue
572
monitor = self.monitors[monitor_index]
573
size = panel_data["size"]
574
autohide = panel_data["autohide"]
575
hide_time = panel_data["hide_time"]
576
577
panel = Panel(self, monitor, position, size, monitor_index, autohide, hide_time)
578
self.panels.append(panel)
579
580
print(f"{size}px panel on {position} edge of monitor {monitor_index}, autohide is {autohide} ({hide_time}ms)")
581
582
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
583
applet_list = panel_data["applets"].get(area_name)
584
if applet_list is None:
585
continue
586
587
for applet in applet_list:
588
item = list(applet.items())[0]
589
AppletClass = self.applets_by_name[item[0]]
590
options = item[1]
591
applet_widget = AppletClass(orientation=panel.get_orientation(), config=options)
592
applet_widget.set_panel_position(panel.position)
593
594
area.append(applet_widget)
595
596
panel.present()
597
panel.realize()
598
599
def do_activate(self):
600
Gio.Application.do_activate(self)
601
602
def save_config(self):
603
with open(get_config_file(), "w") as config_file:
604
yaml_writer = yaml.YAML(typ="rt")
605
data = {"panels": []}
606
for panel in self.panels:
607
panel_data = {
608
"position": PANEL_POSITIONS_REVERSE[panel.position],
609
"monitor": panel.monitor_index,
610
"size": panel.size,
611
"autohide": panel.autohide,
612
"hide_time": panel.hide_time,
613
"applets": {}
614
}
615
616
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
617
panel_data["applets"][area_name] = []
618
applet = area.get_first_child()
619
while applet is not None:
620
panel_data["applets"][area_name].append({
621
applet.__class__.__name__: applet.get_config(),
622
})
623
624
applet = applet.get_next_sibling()
625
626
data["panels"].append(panel_data)
627
628
yaml_writer.dump(data, config_file)
629
630
def do_shutdown(self):
631
print("Shutting down")
632
Gtk.Application.do_shutdown(self)
633
self.save_config()
634
635
def set_edit_mode(self, value):
636
self.edit_mode = value
637
for panel in self.panels:
638
panel.set_edit_mode(value)
639
640
def reset_manager_window(self, *args):
641
self.manager_window = None
642
643
644
if __name__ == "__main__":
645
app = PanoramaPanel()
646
app.run(sys.argv)
647