__init__.py
Python script, ASCII text executable
1
"""
2
Menu applet with system/power options for the Panorama panel.
3
Copyright 2025, roundabout-host.com <vlad@roundabout-host.com>
4
5
This program is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public Licence as published by
7
the Free Software Foundation, either version 3 of the Licence, or
8
(at your option) any later version.
9
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public Licence for more details.
14
15
You should have received a copy of the GNU General Public Licence
16
along with this program. If not, see <https://www.gnu.org/licenses/>.
17
"""
18
19
import os
20
from pathlib import Path
21
import locale
22
import panorama_panel
23
24
import gi
25
gi.require_version("Gtk", "4.0")
26
27
from gi.repository import Gtk, GLib, Gio, GioUnix, Gdk
28
29
30
module_directory = Path(__file__).resolve().parent
31
32
locale.bindtextdomain("panorama-session-menu", module_directory / "locale")
33
_ = lambda x: locale.dgettext("panorama-session-menu", x)
34
35
36
class SessionMenu(panorama_panel.Applet):
37
name = _("Session menu")
38
description = _("Options for system power and the session")
39
icon = Gio.ThemedIcon.new("system-shutdown")
40
41
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None, **kwargs):
42
super().__init__(orientation=orientation, config=config, **kwargs)
43
if config is None:
44
config = {}
45
46
self.trigger_name = config.get("trigger_name", "system-menu")
47
self.icon_name = config.get("icon_name", "system-shutdown-symbolic")
48
49
self.button = Gtk.MenuButton()
50
self.button.set_has_frame(False) # flat look
51
self.icon = Gtk.Image.new_from_icon_name(self.icon_name)
52
self.icon.set_pixel_size(config.get("icon_size", 24))
53
self.button.set_child(self.icon)
54
55
self.menu = Gio.Menu()
56
self.popover = Gtk.PopoverMenu.new_from_model_full(self.menu, Gtk.PopoverMenuFlags.NESTED)
57
self.popover.set_halign(Gtk.Align.START)
58
self.popover.set_has_arrow(False)
59
panorama_panel.track_popover(self.popover)
60
self.button.set_popover(self.popover)
61
62
self.append(self.button)
63
64
self.context_menu = self.make_context_menu()
65
panorama_panel.track_popover(self.context_menu)
66
67
right_click_controller = Gtk.GestureClick()
68
right_click_controller.set_button(3)
69
right_click_controller.connect("pressed", self.show_context_menu)
70
71
self.add_controller(right_click_controller)
72
73
# Introduce the actions
74
action_group = Gio.SimpleActionGroup()
75
options_action = Gio.SimpleAction.new("options", None)
76
options_action.connect("activate", self.show_options)
77
action_group.add_action(options_action)
78
action_to_command = {
79
"shutdown": "systemctl poweroff",
80
"reboot": "systemctl reboot",
81
"hibernate": "systemctl hibernate",
82
"suspend": "systemctl suspend",
83
"logout": "wayland-logout",
84
"switch-user": "dm-tool switch-to-greeter",
85
}
86
if "lock_command" in config:
87
action_to_command["lock"] = config["lock_command"]
88
self.can_lock = True
89
else:
90
self.can_lock = False
91
for action_name, command in action_to_command.items():
92
action = Gio.SimpleAction.new(action_name, None)
93
action.connect("activate", lambda *args, command=command: GLib.spawn_command_line_async(command))
94
action_group.add_action(action)
95
self.insert_action_group("applet", action_group)
96
97
# Power menu
98
self.power_menu = Gio.Menu()
99
self.power_menu.append(_("_Shut down"), "applet.shutdown")
100
self.power_menu.append(_("_Restart"), "applet.reboot")
101
self.power_menu.append(_("_Hibernate"), "applet.hibernate")
102
self.power_menu.append(_("Sus_pend"), "applet.suspend")
103
104
# Session menu
105
self.session_menu = Gio.Menu()
106
if self.can_lock:
107
self.session_menu.append(_("_Lock screen"), "applet.lock")
108
self.session_menu.append(_("Switch _user"), "applet.switch-user")
109
self.session_menu.append(_("Log _out"), "applet.logout")
110
111
self.menu.append_section(None, self.session_menu)
112
self.menu.append_section(None, self.power_menu)
113
114
self.options_window = None
115
116
def add_trigger_to_app(self):
117
if self.trigger_name:
118
app: Gtk.Application = self.get_root().get_application()
119
action = Gio.SimpleAction.new(self.trigger_name, None)
120
action.connect("activate", lambda *args: self.button.popup())
121
app.add_action(action)
122
123
def make_context_menu(self):
124
menu = Gio.Menu()
125
menu.append(_("Menu _options"), "applet.options")
126
context_menu = Gtk.PopoverMenu.new_from_model(menu)
127
context_menu.set_has_arrow(False)
128
context_menu.set_parent(self)
129
context_menu.set_halign(Gtk.Align.START)
130
context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
131
return context_menu
132
133
def show_context_menu(self, gesture, n_presses, x, y):
134
rect = Gdk.Rectangle()
135
rect.x = int(x)
136
rect.y = int(y)
137
rect.width = 1
138
rect.height = 1
139
140
self.context_menu.set_pointing_to(rect)
141
self.context_menu.popup()
142
143
def show_options(self, _0=None, _1=None):
144
...
145
146
def shutdown(self, app: Gtk.Application):
147
app.remove_action(self.trigger_name)
148
149
def get_config(self):
150
return {
151
"trigger_name": self.trigger_name,
152
"icon_name": self.icon_name,
153
"icon_size": self.icon.get_pixel_size(),
154
}
155
156
def set_panel_position(self, position):
157
self.popover.set_position(panorama_panel.OPPOSITE_POSITION[position])
158
self.button.set_direction(panorama_panel.POSITION_TO_ARROW[panorama_panel.OPPOSITE_POSITION[position]])
159