By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 main.py

View raw Download
text/x-script.python • 8.12 kiB
Python script, ASCII text executable
        
            
1
import os
2
import sys
3
import importlib
4
from itertools import accumulate, chain
5
from pathlib import Path
6
import ruamel.yaml as yaml
7
8
os.environ["GI_TYPELIB_PATH"] = "/usr/local/lib/x86_64-linux-gnu/girepository-1.0"
9
10
from ctypes import CDLL
11
CDLL('libgtk4-layer-shell.so')
12
13
import gi
14
gi.require_version("Gtk", "4.0")
15
gi.require_version("Gtk4LayerShell", "1.0")
16
17
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio
18
19
sys.path.insert(0, str((Path(__file__).parent / "shared").resolve()))
20
21
import panorama_panel
22
23
24
def get_applet_directories():
25
data_home = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share"))
26
data_dirs = [Path(d) for d in os.getenv("XDG_DATA_DIRS", "/usr/local/share:/usr/share").split(":")]
27
28
all_paths = [data_home / "panorama-panel" / "applets"] + [d / "panorama-panel" / "applets" for d in data_dirs]
29
return [d for d in all_paths if d.is_dir()]
30
31
32
def get_config_file():
33
config_home = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
34
35
return config_home / "panorama-panel" / "config.yaml"
36
37
38
panels = []
39
40
41
class ManagerWindow(Gtk.Window):
42
def __init__(self):
43
super().__init__()
44
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
45
self.set_child(box)
46
switch_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
47
self.edit_mode_switch = Gtk.Switch()
48
switch_box.append(self.edit_mode_switch)
49
edit_mode_label = Gtk.Label()
50
edit_mode_label.set_text("Panel editing")
51
edit_mode_label.set_mnemonic_widget(self.edit_mode_switch)
52
switch_box.append(edit_mode_label)
53
box.append(switch_box)
54
55
self.edit_mode_switch.connect("state-set", self.set_edit_mode)
56
57
self.set_title("Panel configuration")
58
59
self.connect("close-request", self.on_destroy)
60
61
def on_destroy(self, widget):
62
global manager_window
63
self.destroy()
64
manager_window = None
65
66
def set_edit_mode(self, switch, value):
67
print(f"Editing is {value}")
68
69
if value:
70
for panel in panels:
71
panel.set_edit_mode(True)
72
else:
73
for panel in panels:
74
panel.set_edit_mode(False)
75
76
77
manager_window = None
78
79
80
class AppletArea(Gtk.Box):
81
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL):
82
super().__init__()
83
84
def set_edit_mode(self, value):
85
child = self.get_first_child()
86
while child is not None:
87
child.set_sensitive(not value)
88
child.set_opacity(0.75 if value else 1)
89
child = child.get_next_sibling()
90
91
92
class Panel(Gtk.Window):
93
def __init__(self, monitor: Gdk.Monitor, position: Gtk.PositionType = Gtk.PositionType.TOP, size: int = 40):
94
super().__init__()
95
self.set_default_size(800, size)
96
self.set_decorated(False)
97
98
Gtk4LayerShell.init_for_window(self)
99
100
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.TOP)
101
102
Gtk4LayerShell.auto_exclusive_zone_enable(self)
103
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
104
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
105
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
106
107
box = Gtk.CenterBox()
108
109
match position:
110
case Gtk.PositionType.TOP | Gtk.PositionType.BOTTOM:
111
box.set_orientation(Gtk.Orientation.HORIZONTAL)
112
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
113
box.set_orientation(Gtk.Orientation.VERTICAL)
114
115
self.set_child(box)
116
117
self.left_area = AppletArea(orientation=box.get_orientation())
118
self.centre_area = AppletArea(orientation=box.get_orientation())
119
self.right_area = AppletArea(orientation=box.get_orientation())
120
121
box.set_start_widget(self.left_area)
122
box.set_center_widget(self.centre_area)
123
box.set_end_widget(self.right_area)
124
125
# Add a context menu
126
menu = Gio.Menu()
127
128
menu.append("Open _manager", "panel.manager")
129
130
self.context_menu = Gtk.PopoverMenu.new_from_model(menu)
131
self.context_menu.set_has_arrow(False)
132
self.context_menu.set_parent(self)
133
self.context_menu.set_halign(Gtk.Align.START)
134
self.context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
135
136
right_click_controller = Gtk.GestureClick()
137
right_click_controller.set_button(3)
138
right_click_controller.connect("pressed", self.show_context_menu)
139
140
self.add_controller(right_click_controller)
141
142
action_group = Gio.SimpleActionGroup()
143
manager_action = Gio.SimpleAction.new("manager", None)
144
manager_action.connect("activate", self.show_manager)
145
action_group.add_action(manager_action)
146
self.insert_action_group("panel", action_group)
147
148
def set_edit_mode(self, value):
149
for area in (self.left_area, self.centre_area, self.right_area):
150
area.set_edit_mode(value)
151
152
def show_context_menu(self, gesture, n_presses, x, y):
153
rect = Gdk.Rectangle()
154
rect.x = int(x)
155
rect.y = int(y)
156
rect.width = 1
157
rect.height = 1
158
159
self.context_menu.set_pointing_to(rect)
160
self.context_menu.popup()
161
162
def show_manager(self, _0=None, _1=None):
163
print("Showing manager")
164
global manager_window
165
if not manager_window:
166
manager_window = ManagerWindow()
167
manager_window.present()
168
169
def get_orientation(self):
170
box = self.get_first_child()
171
return box.get_orientation()
172
173
174
display = Gdk.Display.get_default()
175
monitors = display.get_monitors()
176
177
for i, monitor in enumerate(monitors):
178
geometry = monitor.get_geometry()
179
print(f"Monitor {i}: {geometry.width}x{geometry.height} at {geometry.x},{geometry.y}")
180
181
182
def get_all_subclasses(klass: type) -> list[type]:
183
subclasses = []
184
for subclass in klass.__subclasses__():
185
subclasses.append(subclass)
186
subclasses += get_all_subclasses(subclass)
187
188
return subclasses
189
190
191
def load_packages_from_dir(dir_path: Path):
192
loaded_modules = []
193
194
for path in dir_path.iterdir():
195
if path.name.startswith("_"):
196
continue
197
198
if path.is_dir() and (path / "__init__.py").exists():
199
module_name = path.name
200
spec = importlib.util.spec_from_file_location(module_name, path / "__init__.py")
201
module = importlib.util.module_from_spec(spec)
202
spec.loader.exec_module(module)
203
loaded_modules.append(module)
204
else:
205
continue
206
207
return loaded_modules
208
209
210
all_applets = list(chain.from_iterable(load_packages_from_dir(d) for d in get_applet_directories()))
211
212
213
print("Applets:")
214
subclasses = get_all_subclasses(panorama_panel.Applet)
215
applets_by_name = {}
216
for subclass in subclasses:
217
if subclass.__name__ in applets_by_name:
218
print(f"Name conflict for applet {subclass.__name__}. Only one will be loaded.", file=sys.stderr)
219
applets_by_name[subclass.__name__] = subclass
220
221
222
PANEL_POSITIONS = {
223
"top": Gtk.PositionType.TOP,
224
"bottom": Gtk.PositionType.BOTTOM,
225
"left": Gtk.PositionType.LEFT,
226
"right": Gtk.PositionType.RIGHT,
227
}
228
229
230
with open(get_config_file(), "r") as config_file:
231
yaml_loader = yaml.YAML(typ="rt")
232
yaml_file = yaml_loader.load(config_file)
233
for panel_data in yaml_file["panels"]:
234
position = PANEL_POSITIONS[panel_data["position"]]
235
monitor_index = panel_data["monitor"]
236
monitor = monitors[monitor_index]
237
size = panel_data["size"]
238
239
panel = Panel(monitor, position, size)
240
panels.append(panel)
241
panel.show()
242
243
print(f"{size}px panel on {position} edge of monitor {monitor_index}")
244
245
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
246
applet_list = panel_data["applets"].get(area_name)
247
if applet_list is None:
248
continue
249
250
for applet in applet_list:
251
item = list(applet.items())[0]
252
AppletClass = applets_by_name[item[0]]
253
options = item[1]
254
applet_widget = AppletClass(orientation=panel.get_orientation(), config=options)
255
256
area.append(applet_widget)
257
258
259
loop = GLib.MainLoop()
260
loop.run()
261