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.4 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_namespace(self, "com.roundabout_host.panorama.panel")
339
Gtk4LayerShell.set_monitor(self, monitor)
340
341
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.TOP)
342
343
box = Gtk.CenterBox()
344
345
self.set_child(box)
346
self.set_position(position)
347
self.set_size(size)
348
self.set_autohide(autohide, hide_time)
349
350
self.left_area = AppletArea(orientation=box.get_orientation())
351
self.centre_area = AppletArea(orientation=box.get_orientation())
352
self.right_area = AppletArea(orientation=box.get_orientation())
353
354
box.set_start_widget(self.left_area)
355
box.set_center_widget(self.centre_area)
356
box.set_end_widget(self.right_area)
357
358
# Add a context menu
359
menu = Gio.Menu()
360
361
menu.append(_("Open _manager"), "panel.manager")
362
363
self.context_menu = Gtk.PopoverMenu.new_from_model(menu)
364
self.context_menu.set_has_arrow(False)
365
self.context_menu.set_parent(self)
366
self.context_menu.set_halign(Gtk.Align.START)
367
self.context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
368
panorama_panel.track_popover(self.context_menu)
369
370
right_click_controller = Gtk.GestureClick()
371
right_click_controller.set_button(3)
372
right_click_controller.connect("pressed", self.show_context_menu)
373
374
self.add_controller(right_click_controller)
375
376
action_group = Gio.SimpleActionGroup()
377
manager_action = Gio.SimpleAction.new("manager", None)
378
manager_action.connect("activate", self.show_manager)
379
action_group.add_action(manager_action)
380
self.insert_action_group("panel", action_group)
381
382
def reset_margins(self):
383
for edge in POSITION_TO_LAYER_SHELL_EDGE.values():
384
Gtk4LayerShell.set_margin(self, edge, 0)
385
386
def set_edit_mode(self, value):
387
for area in (self.left_area, self.centre_area, self.right_area):
388
area.set_edit_mode(value)
389
390
def show_context_menu(self, gesture, n_presses, x, y):
391
rect = Gdk.Rectangle()
392
rect.x = int(x)
393
rect.y = int(y)
394
rect.width = 1
395
rect.height = 1
396
397
self.context_menu.set_pointing_to(rect)
398
self.context_menu.popup()
399
400
def slide_in(self):
401
if not self.autohide:
402
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 0)
403
return False
404
405
if Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) >= 0:
406
return False
407
408
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) + 1)
409
return True
410
411
def slide_out(self):
412
if not self.autohide:
413
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 0)
414
return False
415
416
if Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) <= 1 - self.size:
417
return False
418
419
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) - 1)
420
return True
421
422
def show_manager(self, _0=None, _1=None):
423
if self.get_application():
424
if not self.get_application().manager_window:
425
self.get_application().manager_window = PanelManager(self.get_application())
426
self.get_application().manager_window.connect("close-request", self.get_application().reset_manager_window)
427
self.get_application().manager_window.present()
428
429
def get_orientation(self):
430
box = self.get_first_child()
431
return box.get_orientation()
432
433
def set_size(self, value: int):
434
self.size = int(value)
435
if self.get_orientation() == Gtk.Orientation.HORIZONTAL:
436
self.set_size_request(800, self.size)
437
self.set_default_size(800, self.size)
438
else:
439
self.set_size_request(self.size, 600)
440
self.set_default_size(self.size, 600)
441
442
def set_position(self, position: Gtk.PositionType):
443
self.position = position
444
self.reset_margins()
445
match self.position:
446
case Gtk.PositionType.TOP:
447
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, False)
448
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
449
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
450
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
451
case Gtk.PositionType.BOTTOM:
452
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, False)
453
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
454
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
455
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
456
case Gtk.PositionType.LEFT:
457
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, False)
458
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
459
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
460
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
461
case Gtk.PositionType.RIGHT:
462
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, False)
463
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
464
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
465
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
466
box = self.get_first_child()
467
match self.position:
468
case Gtk.PositionType.TOP | Gtk.PositionType.BOTTOM:
469
box.set_orientation(Gtk.Orientation.HORIZONTAL)
470
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
471
box.set_orientation(Gtk.Orientation.VERTICAL)
472
473
if self.autohide:
474
if not self.open_popovers:
475
GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out)
476
477
def set_autohide(self, autohide: bool, hide_time: int):
478
self.autohide = autohide
479
self.hide_time = hide_time
480
if not self.autohide:
481
self.reset_margins()
482
Gtk4LayerShell.auto_exclusive_zone_enable(self)
483
if self.motion_controller is not None:
484
self.remove_controller(self.motion_controller)
485
if self.drop_motion_controller is not None:
486
self.remove_controller(self.drop_motion_controller)
487
self.motion_controller = None
488
self.drop_motion_controller = None
489
self.reset_margins()
490
else:
491
Gtk4LayerShell.set_exclusive_zone(self, 0)
492
# Only leave 1px of the window as a "mouse sensor"
493
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 1 - self.size)
494
self.motion_controller = Gtk.EventControllerMotion()
495
self.add_controller(self.motion_controller)
496
self.drop_motion_controller = Gtk.DropControllerMotion()
497
self.add_controller(self.drop_motion_controller)
498
499
self.motion_controller.connect("enter", lambda *args: GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_in))
500
self.drop_motion_controller.connect("enter", lambda *args: GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_in))
501
self.motion_controller.connect("leave", lambda *args: not self.open_popovers and GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out))
502
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))
503
504
def get_all_subclasses(klass: type) -> list[type]:
505
subclasses = []
506
for subclass in klass.__subclasses__():
507
subclasses.append(subclass)
508
subclasses += get_all_subclasses(subclass)
509
510
return subclasses
511
512
def load_packages_from_dir(dir_path: Path):
513
loaded_modules = []
514
515
for path in dir_path.iterdir():
516
if path.name.startswith("_"):
517
continue
518
519
if path.is_dir() and (path / "__init__.py").exists():
520
module_name = path.name
521
spec = importlib.util.spec_from_file_location(module_name, path / "__init__.py")
522
module = importlib.util.module_from_spec(spec)
523
spec.loader.exec_module(module)
524
loaded_modules.append(module)
525
else:
526
continue
527
528
return loaded_modules
529
530
531
PANEL_POSITIONS = {
532
"top": Gtk.PositionType.TOP,
533
"bottom": Gtk.PositionType.BOTTOM,
534
"left": Gtk.PositionType.LEFT,
535
"right": Gtk.PositionType.RIGHT,
536
}
537
538
539
PANEL_POSITIONS_REVERSE = {
540
Gtk.PositionType.TOP: "top",
541
Gtk.PositionType.BOTTOM: "bottom",
542
Gtk.PositionType.LEFT: "left",
543
Gtk.PositionType.RIGHT: "right",
544
}
545
546
547
class PanoramaPanel(Gtk.Application):
548
def __init__(self):
549
super().__init__()
550
self.display = Gdk.Display.get_default()
551
self.monitors = self.display.get_monitors()
552
self.applets_by_name: dict[str, panorama_panel.Applet] = {}
553
self.panels: list[Panel] = []
554
self.manager_window = None
555
self.edit_mode = False
556
self.drags = {}
557
558
def do_startup(self):
559
Gtk.Application.do_startup(self)
560
for i, monitor in enumerate(self.monitors):
561
geometry = monitor.get_geometry()
562
print(f"Monitor {i}: {geometry.width}x{geometry.height} at {geometry.x},{geometry.y}")
563
564
all_applets = list(chain.from_iterable(load_packages_from_dir(d) for d in get_applet_directories()))
565
print("Applets:")
566
subclasses = get_all_subclasses(panorama_panel.Applet)
567
for subclass in subclasses:
568
if subclass.__name__ in self.applets_by_name:
569
print(f"Name conflict for applet {subclass.__name__}. Only one will be loaded.", file=sys.stderr)
570
self.applets_by_name[subclass.__name__] = subclass
571
572
with open(get_config_file(), "r") as config_file:
573
yaml_loader = yaml.YAML(typ="rt")
574
yaml_file = yaml_loader.load(config_file)
575
for panel_data in yaml_file["panels"]:
576
position = PANEL_POSITIONS[panel_data["position"]]
577
monitor_index = panel_data["monitor"]
578
if monitor_index >= len(self.monitors):
579
continue
580
monitor = self.monitors[monitor_index]
581
size = panel_data["size"]
582
autohide = panel_data["autohide"]
583
hide_time = panel_data["hide_time"]
584
585
panel = Panel(self, monitor, position, size, monitor_index, autohide, hide_time)
586
self.panels.append(panel)
587
588
print(f"{size}px panel on {position} edge of monitor {monitor_index}, autohide is {autohide} ({hide_time}ms)")
589
590
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
591
applet_list = panel_data["applets"].get(area_name)
592
if applet_list is None:
593
continue
594
595
for applet in applet_list:
596
item = list(applet.items())[0]
597
AppletClass = self.applets_by_name[item[0]]
598
options = item[1]
599
applet_widget = AppletClass(orientation=panel.get_orientation(), config=options)
600
applet_widget.set_panel_position(panel.position)
601
602
area.append(applet_widget)
603
604
panel.present()
605
panel.realize()
606
607
def do_activate(self):
608
Gio.Application.do_activate(self)
609
610
def save_config(self):
611
with open(get_config_file(), "w") as config_file:
612
yaml_writer = yaml.YAML(typ="rt")
613
data = {"panels": []}
614
for panel in self.panels:
615
panel_data = {
616
"position": PANEL_POSITIONS_REVERSE[panel.position],
617
"monitor": panel.monitor_index,
618
"size": panel.size,
619
"autohide": panel.autohide,
620
"hide_time": panel.hide_time,
621
"applets": {}
622
}
623
624
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
625
panel_data["applets"][area_name] = []
626
applet = area.get_first_child()
627
while applet is not None:
628
panel_data["applets"][area_name].append({
629
applet.__class__.__name__: applet.get_config(),
630
})
631
632
applet = applet.get_next_sibling()
633
634
data["panels"].append(panel_data)
635
636
yaml_writer.dump(data, config_file)
637
638
def do_shutdown(self):
639
print("Shutting down")
640
Gtk.Application.do_shutdown(self)
641
self.save_config()
642
643
def set_edit_mode(self, value):
644
self.edit_mode = value
645
for panel in self.panels:
646
panel.set_edit_mode(value)
647
648
def reset_manager_window(self, *args):
649
self.manager_window = None
650
651
652
if __name__ == "__main__":
653
app = PanoramaPanel()
654
app.run(sys.argv)
655