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.37 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
# Make the applets aware of the changed monitor
158
for area in (self.panel.left_area, self.panel.centre_area, self.panel.right_area):
159
applet = area.get_first_child()
160
while applet:
161
applet.output_changed()
162
applet = applet.get_next_sibling()
163
164
165
PANEL_POSITIONS_HUMAN = {
166
Gtk.PositionType.TOP: "top",
167
Gtk.PositionType.BOTTOM: "bottom",
168
Gtk.PositionType.LEFT: "left",
169
Gtk.PositionType.RIGHT: "right",
170
}
171
172
173
@Gtk.Template(filename="panel-manager.ui")
174
class PanelManager(Gtk.Window):
175
__gtype_name__ = "PanelManager"
176
177
panel_editing_switch: Gtk.Switch = Gtk.Template.Child()
178
panel_stack: Gtk.Stack = Gtk.Template.Child()
179
current_panel: typing.Optional[Panel] = None
180
181
def __init__(self, application: Gtk.Application, **kwargs):
182
super().__init__(application=application, **kwargs)
183
184
self.connect("close-request", lambda *args: self.destroy())
185
186
action_group = Gio.SimpleActionGroup()
187
188
self.next_panel_action = Gio.SimpleAction(name="next-panel")
189
action_group.add_action(self.next_panel_action)
190
self.next_panel_action.connect("activate", lambda *args: self.panel_stack.set_visible_child(self.panel_stack.get_visible_child().get_next_sibling()))
191
192
self.previous_panel_action = Gio.SimpleAction(name="previous-panel")
193
action_group.add_action(self.previous_panel_action)
194
self.previous_panel_action.connect("activate", lambda *args: self.panel_stack.set_visible_child(self.panel_stack.get_visible_child().get_prev_sibling()))
195
196
self.insert_action_group("win", action_group)
197
if isinstance(self.get_application(), PanoramaPanel):
198
self.panel_editing_switch.set_active(application.edit_mode)
199
self.panel_editing_switch.connect("state-set", self.set_edit_mode)
200
201
self.connect("close-request", lambda *args: self.unflash_old_panel())
202
self.connect("close-request", lambda *args: self.destroy())
203
204
if isinstance(self.get_application(), PanoramaPanel):
205
app: PanoramaPanel = self.get_application()
206
for panel in app.panels:
207
configurator = PanelConfigurator(panel)
208
self.panel_stack.add_child(configurator)
209
configurator.monitor_number_adjustment.set_upper(len(app.monitors))
210
211
self.panel_stack.set_visible_child(self.panel_stack.get_first_child())
212
self.panel_stack.connect("notify::visible-child", self.set_visible_panel)
213
self.panel_stack.notify("visible-child")
214
215
def unflash_old_panel(self):
216
if self.current_panel:
217
for area in (self.current_panel.left_area, self.current_panel.centre_area, self.current_panel.right_area):
218
area.unflash()
219
220
def set_visible_panel(self, stack: Gtk.Stack, pspec: GObject.ParamSpec):
221
self.unflash_old_panel()
222
223
panel: Panel = stack.get_visible_child().panel
224
225
self.current_panel = panel
226
self.next_panel_action.set_enabled(stack.get_visible_child().get_next_sibling() is not None)
227
self.previous_panel_action.set_enabled(stack.get_visible_child().get_prev_sibling() is not None)
228
229
# Start an animation to show the user what panel is being edited
230
for area in (panel.left_area, panel.centre_area, panel.right_area):
231
area.flash()
232
233
def set_edit_mode(self, switch, value):
234
if isinstance(self.get_application(), PanoramaPanel):
235
self.get_application().set_edit_mode(value)
236
237
@Gtk.Template.Callback()
238
def save_settings(self, *args):
239
if isinstance(self.get_application(), PanoramaPanel):
240
self.get_application().save_config()
241
242
243
def get_applet_directories():
244
data_home = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share"))
245
data_dirs = [Path(d) for d in os.getenv("XDG_DATA_DIRS", "/usr/local/share:/usr/share").split(":")]
246
247
all_paths = [data_home / "panorama-panel" / "applets"] + [d / "panorama-panel" / "applets" for d in data_dirs]
248
return [d for d in all_paths if d.is_dir()]
249
250
251
def get_config_file():
252
config_home = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
253
254
return config_home / "panorama-panel" / "config.yaml"
255
256
257
class AppletArea(Gtk.Box):
258
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL):
259
super().__init__()
260
261
self.drop_target = Gtk.DropTarget.new(GObject.TYPE_UINT64, Gdk.DragAction.MOVE)
262
self.drop_target.set_gtypes([GObject.TYPE_UINT64])
263
self.drop_target.connect("drop", self.drop_applet)
264
265
def drop_applet(self, drop_target: Gtk.DropTarget, value: int, x: float, y: float):
266
old_area: AppletArea = applet.get_parent()
267
old_area.remove(applet)
268
# Find the position where to insert the applet
269
child = self.get_first_child()
270
while child:
271
allocation = child.get_allocation()
272
child_x, child_y = self.translate_coordinates(self, 0, 0)
273
if self.get_orientation() == Gtk.Orientation.HORIZONTAL:
274
midpoint = child_x + allocation.width / 2
275
if x < midpoint:
276
applet.insert_before(self, child)
277
break
278
elif self.get_orientation() == Gtk.Orientation.VERTICAL:
279
midpoint = child_y + allocation.height / 2
280
if y < midpoint:
281
applet.insert_before(self, child)
282
break
283
child = child.get_next_sibling()
284
else:
285
self.append(applet)
286
applet.show()
287
return True
288
289
def set_edit_mode(self, value):
290
panel: Panel = self.get_root()
291
child = self.get_first_child()
292
while child is not None:
293
if value:
294
child.make_draggable()
295
else:
296
child.restore_drag()
297
child.set_opacity(0.75 if value else 1)
298
child = child.get_next_sibling()
299
300
if value:
301
self.add_controller(self.drop_target)
302
if panel.get_orientation() == Gtk.Orientation.HORIZONTAL:
303
self.set_size_request(48, 0)
304
elif panel.get_orientation() == Gtk.Orientation.VERTICAL:
305
self.set_size_request(0, 48)
306
else:
307
self.remove_controller(self.drop_target)
308
self.set_size_request(0, 0)
309
310
def flash(self):
311
self.add_css_class("panel-flash")
312
313
def unflash(self):
314
self.remove_css_class("panel-flash")
315
316
317
POSITION_TO_LAYER_SHELL_EDGE = {
318
Gtk.PositionType.TOP: Gtk4LayerShell.Edge.TOP,
319
Gtk.PositionType.BOTTOM: Gtk4LayerShell.Edge.BOTTOM,
320
Gtk.PositionType.LEFT: Gtk4LayerShell.Edge.LEFT,
321
Gtk.PositionType.RIGHT: Gtk4LayerShell.Edge.RIGHT,
322
}
323
324
325
class Panel(Gtk.Window):
326
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):
327
super().__init__(application=application)
328
self.drop_motion_controller = None
329
self.motion_controller = None
330
self.set_decorated(False)
331
self.position = None
332
self.autohide = None
333
self.monitor_index = monitor_index
334
self.hide_time = None
335
self.open_popovers: set[int] = set()
336
337
Gtk4LayerShell.init_for_window(self)
338
Gtk4LayerShell.set_monitor(self, monitor)
339
340
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.TOP)
341
342
box = Gtk.CenterBox()
343
344
self.set_child(box)
345
self.set_position(position)
346
self.set_size(size)
347
self.set_autohide(autohide, hide_time)
348
349
self.left_area = AppletArea(orientation=box.get_orientation())
350
self.centre_area = AppletArea(orientation=box.get_orientation())
351
self.right_area = AppletArea(orientation=box.get_orientation())
352
353
box.set_start_widget(self.left_area)
354
box.set_center_widget(self.centre_area)
355
box.set_end_widget(self.right_area)
356
357
# Add a context menu
358
menu = Gio.Menu()
359
360
menu.append(_("Open _manager"), "panel.manager")
361
362
self.context_menu = Gtk.PopoverMenu.new_from_model(menu)
363
self.context_menu.set_has_arrow(False)
364
self.context_menu.set_parent(self)
365
self.context_menu.set_halign(Gtk.Align.START)
366
self.context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
367
panorama_panel.track_popover(self.context_menu)
368
369
right_click_controller = Gtk.GestureClick()
370
right_click_controller.set_button(3)
371
right_click_controller.connect("pressed", self.show_context_menu)
372
373
self.add_controller(right_click_controller)
374
375
action_group = Gio.SimpleActionGroup()
376
manager_action = Gio.SimpleAction.new("manager", None)
377
manager_action.connect("activate", self.show_manager)
378
action_group.add_action(manager_action)
379
self.insert_action_group("panel", action_group)
380
381
def reset_margins(self):
382
for edge in POSITION_TO_LAYER_SHELL_EDGE.values():
383
Gtk4LayerShell.set_margin(self, edge, 0)
384
385
def set_edit_mode(self, value):
386
for area in (self.left_area, self.centre_area, self.right_area):
387
area.set_edit_mode(value)
388
389
def show_context_menu(self, gesture, n_presses, x, y):
390
rect = Gdk.Rectangle()
391
rect.x = int(x)
392
rect.y = int(y)
393
rect.width = 1
394
rect.height = 1
395
396
self.context_menu.set_pointing_to(rect)
397
self.context_menu.popup()
398
399
def slide_in(self):
400
if not self.autohide:
401
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 0)
402
return False
403
404
if Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) >= 0:
405
return False
406
407
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) + 1)
408
return True
409
410
def slide_out(self):
411
if not self.autohide:
412
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 0)
413
return False
414
415
if Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) <= 1 - self.size:
416
return False
417
418
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) - 1)
419
return True
420
421
def show_manager(self, _0=None, _1=None):
422
if self.get_application():
423
if not self.get_application().manager_window:
424
self.get_application().manager_window = PanelManager(self.get_application())
425
self.get_application().manager_window.connect("close-request", self.get_application().reset_manager_window)
426
self.get_application().manager_window.present()
427
428
def get_orientation(self):
429
box = self.get_first_child()
430
return box.get_orientation()
431
432
def set_size(self, value: int):
433
self.size = int(value)
434
if self.get_orientation() == Gtk.Orientation.HORIZONTAL:
435
self.set_size_request(800, self.size)
436
self.set_default_size(800, self.size)
437
else:
438
self.set_size_request(self.size, 600)
439
self.set_default_size(self.size, 600)
440
441
def set_position(self, position: Gtk.PositionType):
442
self.position = position
443
self.reset_margins()
444
match self.position:
445
case Gtk.PositionType.TOP:
446
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, False)
447
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
448
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
449
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
450
case Gtk.PositionType.BOTTOM:
451
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, False)
452
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
453
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
454
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
455
case Gtk.PositionType.LEFT:
456
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, False)
457
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
458
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
459
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
460
case Gtk.PositionType.RIGHT:
461
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, False)
462
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
463
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
464
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
465
box = self.get_first_child()
466
match self.position:
467
case Gtk.PositionType.TOP | Gtk.PositionType.BOTTOM:
468
box.set_orientation(Gtk.Orientation.HORIZONTAL)
469
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
470
box.set_orientation(Gtk.Orientation.VERTICAL)
471
472
if self.autohide:
473
if not self.open_popovers:
474
GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out)
475
476
def set_autohide(self, autohide: bool, hide_time: int):
477
self.autohide = autohide
478
self.hide_time = hide_time
479
if not self.autohide:
480
self.reset_margins()
481
Gtk4LayerShell.auto_exclusive_zone_enable(self)
482
if self.motion_controller is not None:
483
self.remove_controller(self.motion_controller)
484
if self.drop_motion_controller is not None:
485
self.remove_controller(self.drop_motion_controller)
486
self.motion_controller = None
487
self.drop_motion_controller = None
488
self.reset_margins()
489
else:
490
Gtk4LayerShell.set_exclusive_zone(self, 0)
491
# Only leave 1px of the window as a "mouse sensor"
492
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 1 - self.size)
493
self.motion_controller = Gtk.EventControllerMotion()
494
self.add_controller(self.motion_controller)
495
self.drop_motion_controller = Gtk.DropControllerMotion()
496
self.add_controller(self.drop_motion_controller)
497
498
self.motion_controller.connect("enter", lambda *args: GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_in))
499
self.drop_motion_controller.connect("enter", lambda *args: GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_in))
500
self.motion_controller.connect("leave", lambda *args: not self.open_popovers and GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out))
501
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))
502
503
def get_all_subclasses(klass: type) -> list[type]:
504
subclasses = []
505
for subclass in klass.__subclasses__():
506
subclasses.append(subclass)
507
subclasses += get_all_subclasses(subclass)
508
509
return subclasses
510
511
def load_packages_from_dir(dir_path: Path):
512
loaded_modules = []
513
514
for path in dir_path.iterdir():
515
if path.name.startswith("_"):
516
continue
517
518
if path.is_dir() and (path / "__init__.py").exists():
519
module_name = path.name
520
spec = importlib.util.spec_from_file_location(module_name, path / "__init__.py")
521
module = importlib.util.module_from_spec(spec)
522
spec.loader.exec_module(module)
523
loaded_modules.append(module)
524
else:
525
continue
526
527
return loaded_modules
528
529
530
PANEL_POSITIONS = {
531
"top": Gtk.PositionType.TOP,
532
"bottom": Gtk.PositionType.BOTTOM,
533
"left": Gtk.PositionType.LEFT,
534
"right": Gtk.PositionType.RIGHT,
535
}
536
537
538
PANEL_POSITIONS_REVERSE = {
539
Gtk.PositionType.TOP: "top",
540
Gtk.PositionType.BOTTOM: "bottom",
541
Gtk.PositionType.LEFT: "left",
542
Gtk.PositionType.RIGHT: "right",
543
}
544
545
546
class PanoramaPanel(Gtk.Application):
547
def __init__(self):
548
super().__init__(application_id="com.roundabout_host.panorama.panel")
549
self.display = Gdk.Display.get_default()
550
self.monitors = self.display.get_monitors()
551
self.applets_by_name: dict[str, panorama_panel.Applet] = {}
552
self.panels: list[Panel] = []
553
self.manager_window = None
554
self.edit_mode = False
555
self.drags = {}
556
557
def do_startup(self):
558
Gtk.Application.do_startup(self)
559
for i, monitor in enumerate(self.monitors):
560
geometry = monitor.get_geometry()
561
print(f"Monitor {i}: {geometry.width}x{geometry.height} at {geometry.x},{geometry.y}")
562
563
all_applets = list(chain.from_iterable(load_packages_from_dir(d) for d in get_applet_directories()))
564
print("Applets:")
565
subclasses = get_all_subclasses(panorama_panel.Applet)
566
for subclass in subclasses:
567
if subclass.__name__ in self.applets_by_name:
568
print(f"Name conflict for applet {subclass.__name__}. Only one will be loaded.", file=sys.stderr)
569
self.applets_by_name[subclass.__name__] = subclass
570
571
with open(get_config_file(), "r") as config_file:
572
yaml_loader = yaml.YAML(typ="rt")
573
yaml_file = yaml_loader.load(config_file)
574
for panel_data in yaml_file["panels"]:
575
position = PANEL_POSITIONS[panel_data["position"]]
576
monitor_index = panel_data["monitor"]
577
if monitor_index >= len(self.monitors):
578
continue
579
monitor = self.monitors[monitor_index]
580
size = panel_data["size"]
581
autohide = panel_data["autohide"]
582
hide_time = panel_data["hide_time"]
583
584
panel = Panel(self, monitor, position, size, monitor_index, autohide, hide_time)
585
self.panels.append(panel)
586
587
print(f"{size}px panel on {position} edge of monitor {monitor_index}, autohide is {autohide} ({hide_time}ms)")
588
589
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
590
applet_list = panel_data["applets"].get(area_name)
591
if applet_list is None:
592
continue
593
594
for applet in applet_list:
595
item = list(applet.items())[0]
596
AppletClass = self.applets_by_name[item[0]]
597
options = item[1]
598
applet_widget = AppletClass(orientation=panel.get_orientation(), config=options)
599
applet_widget.set_panel_position(panel.position)
600
601
area.append(applet_widget)
602
603
panel.present()
604
panel.realize()
605
606
def do_activate(self):
607
Gio.Application.do_activate(self)
608
609
def save_config(self):
610
with open(get_config_file(), "w") as config_file:
611
yaml_writer = yaml.YAML(typ="rt")
612
data = {"panels": []}
613
for panel in self.panels:
614
panel_data = {
615
"position": PANEL_POSITIONS_REVERSE[panel.position],
616
"monitor": panel.monitor_index,
617
"size": panel.size,
618
"autohide": panel.autohide,
619
"hide_time": panel.hide_time,
620
"applets": {}
621
}
622
623
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
624
panel_data["applets"][area_name] = []
625
applet = area.get_first_child()
626
while applet is not None:
627
panel_data["applets"][area_name].append({
628
applet.__class__.__name__: applet.get_config(),
629
})
630
631
applet = applet.get_next_sibling()
632
633
data["panels"].append(panel_data)
634
635
yaml_writer.dump(data, config_file)
636
637
def do_shutdown(self):
638
print("Shutting down")
639
Gtk.Application.do_shutdown(self)
640
self.save_config()
641
642
def set_edit_mode(self, value):
643
self.edit_mode = value
644
for panel in self.panels:
645
panel.set_edit_mode(value)
646
647
def reset_manager_window(self, *args):
648
self.manager_window = None
649
650
651
if __name__ == "__main__":
652
app = PanoramaPanel()
653
app.run(sys.argv)
654