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