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.14 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
print("Destroyed")
64
self.destroy()
65
manager_window = None
66
67
def set_edit_mode(self, switch, value):
68
print(f"Editing is {value}")
69
70
if value:
71
for panel in panels:
72
panel.set_edit_mode(True)
73
else:
74
for panel in panels:
75
panel.set_edit_mode(False)
76
77
78
manager_window = None
79
80
81
class AppletArea(Gtk.Box):
82
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL):
83
super().__init__()
84
85
def set_edit_mode(self, value):
86
child = self.get_first_child()
87
while child is not None:
88
child.set_sensitive(not value)
89
child.set_opacity(0.75 if value else 1)
90
child = child.get_next_sibling()
91
92
93
class Panel(Gtk.Window):
94
def __init__(self, monitor: Gdk.Monitor, position: Gtk.PositionType = Gtk.PositionType.TOP, size: int = 40):
95
super().__init__()
96
self.set_default_size(800, size)
97
self.set_decorated(False)
98
99
Gtk4LayerShell.init_for_window(self)
100
101
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.TOP)
102
103
Gtk4LayerShell.auto_exclusive_zone_enable(self)
104
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
105
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
106
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
107
108
box = Gtk.CenterBox()
109
110
match position:
111
case Gtk.PositionType.TOP | Gtk.PositionType.BOTTOM:
112
box.set_orientation(Gtk.Orientation.HORIZONTAL)
113
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
114
box.set_orientation(Gtk.Orientation.VERTICAL)
115
116
self.set_child(box)
117
118
self.left_area = AppletArea(orientation=box.get_orientation())
119
self.centre_area = AppletArea(orientation=box.get_orientation())
120
self.right_area = AppletArea(orientation=box.get_orientation())
121
122
box.set_start_widget(self.left_area)
123
box.set_center_widget(self.centre_area)
124
box.set_end_widget(self.right_area)
125
126
# Add a context menu
127
menu = Gio.Menu()
128
129
menu.append("Open _manager", "panel.manager")
130
131
self.context_menu = Gtk.PopoverMenu.new_from_model(menu)
132
self.context_menu.set_has_arrow(False)
133
self.context_menu.set_parent(self)
134
self.context_menu.set_halign(Gtk.Align.START)
135
self.context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
136
137
right_click_controller = Gtk.GestureClick()
138
right_click_controller.set_button(3)
139
right_click_controller.connect("pressed", self.show_context_menu)
140
141
self.add_controller(right_click_controller)
142
143
action_group = Gio.SimpleActionGroup()
144
manager_action = Gio.SimpleAction.new("manager", None)
145
manager_action.connect("activate", self.show_manager)
146
action_group.add_action(manager_action)
147
self.insert_action_group("panel", action_group)
148
149
def set_edit_mode(self, value):
150
for area in (self.left_area, self.centre_area, self.right_area):
151
area.set_edit_mode(value)
152
153
def show_context_menu(self, gesture, n_presses, x, y):
154
rect = Gdk.Rectangle()
155
rect.x = int(x)
156
rect.y = int(y)
157
rect.width = 1
158
rect.height = 1
159
160
self.context_menu.set_pointing_to(rect)
161
self.context_menu.popup()
162
163
def show_manager(self, _0=None, _1=None):
164
print("Showing manager")
165
global manager_window
166
if not manager_window:
167
manager_window = ManagerWindow()
168
manager_window.present()
169
170
def get_orientation(self):
171
box = self.get_first_child()
172
return box.get_orientation()
173
174
175
display = Gdk.Display.get_default()
176
monitors = display.get_monitors()
177
178
for i, monitor in enumerate(monitors):
179
geometry = monitor.get_geometry()
180
print(f"Monitor {i}: {geometry.width}x{geometry.height} at {geometry.x},{geometry.y}")
181
182
183
def get_all_subclasses(klass: type) -> list[type]:
184
subclasses = []
185
for subclass in klass.__subclasses__():
186
subclasses.append(subclass)
187
subclasses += get_all_subclasses(subclass)
188
189
return subclasses
190
191
192
def load_packages_from_dir(dir_path: Path):
193
loaded_modules = []
194
195
for path in dir_path.iterdir():
196
if path.name.startswith("_"):
197
continue
198
199
if path.is_dir() and (path / "__init__.py").exists():
200
module_name = path.name
201
spec = importlib.util.spec_from_file_location(module_name, path / "__init__.py")
202
module = importlib.util.module_from_spec(spec)
203
spec.loader.exec_module(module)
204
loaded_modules.append(module)
205
else:
206
continue
207
208
return loaded_modules
209
210
211
all_applets = list(chain.from_iterable(load_packages_from_dir(d) for d in get_applet_directories()))
212
213
214
print("Applets:")
215
subclasses = get_all_subclasses(panorama_panel.Applet)
216
applets_by_name = {}
217
for subclass in subclasses:
218
if subclass.__name__ in applets_by_name:
219
print(f"Name conflict for applet {subclass.__name__}. Only one will be loaded.", file=sys.stderr)
220
applets_by_name[subclass.__name__] = subclass
221
222
223
PANEL_POSITIONS = {
224
"top": Gtk.PositionType.TOP,
225
"bottom": Gtk.PositionType.BOTTOM,
226
"left": Gtk.PositionType.LEFT,
227
"right": Gtk.PositionType.RIGHT,
228
}
229
230
231
with open(get_config_file(), "r") as config_file:
232
yaml_loader = yaml.YAML(typ="rt")
233
yaml_file = yaml_loader.load(config_file)
234
for panel_data in yaml_file["panels"]:
235
position = PANEL_POSITIONS[panel_data["position"]]
236
monitor_index = panel_data["monitor"]
237
monitor = monitors[monitor_index]
238
size = panel_data["size"]
239
240
panel = Panel(monitor, position, size)
241
panels.append(panel)
242
panel.show()
243
244
print(f"{size}px panel on {position} edge of monitor {monitor_index}")
245
246
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
247
applet_list = panel_data["applets"].get(area_name)
248
if applet_list is None:
249
continue
250
251
for applet in applet_list:
252
item = list(applet.items())[0]
253
AppletClass = applets_by_name[item[0]]
254
options = item[1]
255
applet_widget = AppletClass(orientation=panel.get_orientation(), config=options)
256
257
area.append(applet_widget)
258
259
260
loop = GLib.MainLoop()
261
loop.run()
262