pwr_tray 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pwr_tray/IniTool.py +193 -0
- pwr_tray/SwayIdleMgr.py +139 -0
- pwr_tray/Utils.py +162 -0
- pwr_tray/YamlDump.py +163 -0
- pwr_tray/__init__.py +0 -0
- pwr_tray/main.py +974 -0
- pwr_tray/resources/FullSun-v03.svg +81 -0
- pwr_tray/resources/GoingDown-v03.svg +79 -0
- pwr_tray/resources/PlayingNow-v03.svg +63 -0
- pwr_tray/resources/RisingMoon-v03.svg +75 -0
- pwr_tray/resources/SetA/pwr-inh-v03.svg +1 -0
- pwr_tray/resources/SetA/pwr-no-sleep-v03.svg +1 -0
- pwr_tray/resources/SetA/pwr-uninh-v03.svg +1 -0
- pwr_tray/resources/SetB/LoBattery-v03.svg +698 -0
- pwr_tray/resources/SetB/LockOnlyMode-v03.svg +18 -0
- pwr_tray/resources/SetB/NormMode-v03.svg +645 -0
- pwr_tray/resources/SetC/New-LockOnly-v03.svg +13 -0
- pwr_tray/resources/SetC/New-LowBattery-v03.svg +13 -0
- pwr_tray/resources/SetC/New-NormMode-v03.svg +13 -0
- pwr_tray/resources/SetC/New-PresMode-v03.svg +13 -0
- pwr_tray/resources/SettingSun-v03.svg +71 -0
- pwr_tray/resources/StopSign-v03.svg +90 -0
- pwr_tray/resources/Unlocked-v03.svg +116 -0
- pwr_tray/resources/UnlockedMoon-v03.svg +121 -0
- pwr_tray/resources/__init__.py +0 -0
- pwr_tray/resources/lockpaper.png +0 -0
- pwr_tray-1.0.2.dist-info/METADATA +169 -0
- pwr_tray-1.0.2.dist-info/RECORD +31 -0
- pwr_tray-1.0.2.dist-info/WHEEL +4 -0
- pwr_tray-1.0.2.dist-info/entry_points.txt +3 -0
- pwr_tray-1.0.2.dist-info/licenses/LICENSE +21 -0
pwr_tray/main.py
ADDED
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
This code is designed to control power matters from the tray.
|
|
5
|
+
"""
|
|
6
|
+
# pylint: disable=invalid-name,wrong-import-position,missing-function-docstring
|
|
7
|
+
# pylint: disable=broad-except,too-many-instance-attributes
|
|
8
|
+
# pylint: disable=global-statement,consider-using-with,too-many-lines
|
|
9
|
+
# pylint: disable=too-many-statements,too-few-public-methods
|
|
10
|
+
# pylint: disable=too-many-branches,too-many-public-methods
|
|
11
|
+
# pylint: disable=consider-using-from-import
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
needed = '/usr/lib/python3/dist-packages'
|
|
16
|
+
if needed not in sys.path:
|
|
17
|
+
sys.path.append(needed) # pick up external dependencies
|
|
18
|
+
import signal
|
|
19
|
+
import re
|
|
20
|
+
import subprocess
|
|
21
|
+
import json
|
|
22
|
+
import shutil
|
|
23
|
+
import atexit
|
|
24
|
+
import time
|
|
25
|
+
import traceback
|
|
26
|
+
from types import SimpleNamespace
|
|
27
|
+
import psutil
|
|
28
|
+
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction #, QMessageBox
|
|
29
|
+
from PyQt5.QtGui import QIcon, QCursor
|
|
30
|
+
from PyQt5.QtCore import QTimer
|
|
31
|
+
|
|
32
|
+
import pwr_tray.Utils as Utils
|
|
33
|
+
from pwr_tray.Utils import prt, PyKill
|
|
34
|
+
from pwr_tray.SwayIdleMgr import SwayIdleManager
|
|
35
|
+
from pwr_tray.IniTool import IniTool
|
|
36
|
+
|
|
37
|
+
class PwrTray:
|
|
38
|
+
""" pwr-tray main class.
|
|
39
|
+
NOTES:
|
|
40
|
+
- when icons are moved/edited, rename them or reboot to avoid cache confusion
|
|
41
|
+
"""
|
|
42
|
+
svg_info = SimpleNamespace(version='03', subdir='resources/SetD'
|
|
43
|
+
, bases= ['SettingSun', # Normal (SleepAfterLock)
|
|
44
|
+
'FullSun', # Presentation Mode
|
|
45
|
+
'Unlocked', # LockOnly Mode
|
|
46
|
+
'GoingDown', # LowBattery Mode
|
|
47
|
+
'PlayingNow', # inhibited by a/v player
|
|
48
|
+
'RisingMoon', # Normal and Locking Soon
|
|
49
|
+
'UnlockedMoon', # LockOnly and Locking Soon
|
|
50
|
+
'StopSign', # systemd inhibited
|
|
51
|
+
] )
|
|
52
|
+
singleton = None
|
|
53
|
+
@staticmethod
|
|
54
|
+
def get_environment():
|
|
55
|
+
desktop_session = os.environ.get('DESKTOP_SESSION', '').lower()
|
|
56
|
+
xdg_session_desktop = os.environ.get('XDG_SESSION_DESKTOP', '').lower()
|
|
57
|
+
xdg_current_desktop = os.environ.get('XDG_CURRENT_DESKTOP', '').lower()
|
|
58
|
+
sway_socket = os.environ.get('SWAYSOCK')
|
|
59
|
+
wayland_display = os.environ.get('WAYLAND_DISPLAY')
|
|
60
|
+
is_wayland = bool(wayland_display)
|
|
61
|
+
display = os.environ.get('DISPLAY')
|
|
62
|
+
|
|
63
|
+
if 'i3' in desktop_session and display: # Check for i3
|
|
64
|
+
prt(f'ENV: i3 {desktop_session=} {display=}')
|
|
65
|
+
return 'i3', is_wayland
|
|
66
|
+
if ('sway' in desktop_session or 'sway' in xdg_session_desktop
|
|
67
|
+
or 'sway' in xdg_current_desktop) and sway_socket: # Check for Sway
|
|
68
|
+
prt(f'ENV: sway {desktop_session=} {sway_socket=}')
|
|
69
|
+
return 'sway', is_wayland
|
|
70
|
+
if 'plasma' in desktop_session or 'kde' in xdg_current_desktop:
|
|
71
|
+
if is_wayland:
|
|
72
|
+
env = 'kde-wayland'
|
|
73
|
+
prt(f'ENV: {env} {desktop_session=} {wayland_display=}')
|
|
74
|
+
assert False, f'unsupported env: {env}'
|
|
75
|
+
return 'kde-wayland', is_wayland
|
|
76
|
+
if display:
|
|
77
|
+
env = 'kde-x11'
|
|
78
|
+
prt(f'ENV: {env} {desktop_session=} {display=}')
|
|
79
|
+
return env, is_wayland
|
|
80
|
+
if 'gnome' in desktop_session:
|
|
81
|
+
if is_wayland:
|
|
82
|
+
env='gnome-wayland'
|
|
83
|
+
prt(f'ENV: {env} {desktop_session=} {wayland_display=}')
|
|
84
|
+
assert False, f'unsupported env: {env}'
|
|
85
|
+
return env, is_wayland
|
|
86
|
+
if display:
|
|
87
|
+
env='gnome-x11'
|
|
88
|
+
prt(f'ENV: {env} {desktop_session=} {display=}')
|
|
89
|
+
assert False, f'unsupported env: {env}'
|
|
90
|
+
return 'gnome-x11', is_wayland
|
|
91
|
+
# Default case: no known environment detected
|
|
92
|
+
assert False, 'cannot determine if i3/sway/(kde|gnome)-(x11|wayland)'
|
|
93
|
+
|
|
94
|
+
default_variables = {
|
|
95
|
+
'suspend': 'systemctl suspend',
|
|
96
|
+
'poweroff': 'systemctl poweroff',
|
|
97
|
+
'reboot': 'systemctl reboot',
|
|
98
|
+
# 'dimmer': 'brightnessctl set {percent}%',
|
|
99
|
+
# 'undim': 'brightnessctl set 100%',
|
|
100
|
+
'logoff': '',
|
|
101
|
+
'monitors_off': '',
|
|
102
|
+
'locker': '',
|
|
103
|
+
'get_idle_ms': '',
|
|
104
|
+
'reset_idle': '',
|
|
105
|
+
'reload_wm': '',
|
|
106
|
+
'restart_wm': '',
|
|
107
|
+
# 'must_haves': 'systemctl brightnessctl'.split(),
|
|
108
|
+
'must_haves': 'systemctl'.split(),
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
overrides = {
|
|
112
|
+
'x11': {
|
|
113
|
+
'reset_idle': 'xset s reset',
|
|
114
|
+
'get_idle_ms': 'xprintidle',
|
|
115
|
+
'monitors_off': 'sleep 1.0; exec xset dpms force off',
|
|
116
|
+
'must_haves': 'xset xprintidle'.split(),
|
|
117
|
+
}, 'sway': {
|
|
118
|
+
# swayidle timeout 300 'swaylock' resume 'swaymsg "exec kill -USR1 $(pgrep swayidle)"' &
|
|
119
|
+
# kill -USR1 $(pgrep swayidle)
|
|
120
|
+
'reload_wm': 'swaymsg reload',
|
|
121
|
+
'logoff': 'swaymsg exit',
|
|
122
|
+
'locker': 'swaylock --ignore-empty-password --show-failed-attempt',
|
|
123
|
+
# 'monitors_off': """sleep 1.0; swaymsg 'output * dpms off'""",
|
|
124
|
+
'must_haves': 'swaymsg i3lock'.split(),
|
|
125
|
+
|
|
126
|
+
}, 'i3': {
|
|
127
|
+
'reload_wm': 'i3-msg reload',
|
|
128
|
+
'restart_wm': 'i3-msg restart',
|
|
129
|
+
'logoff': 'i3-msg exit',
|
|
130
|
+
'locker': 'pkill i3lock; sleep 0.5; i3lock --ignore-empty-password --show-failed-attempt',
|
|
131
|
+
'must_haves': 'i3-msg i3lock'.split(),
|
|
132
|
+
|
|
133
|
+
}, 'kde-x11': {
|
|
134
|
+
'locker': 'loginctl lock-session',
|
|
135
|
+
# 'logoff': 'loginctl terminate-session {XDG_SESSION_ID}',
|
|
136
|
+
'logoff': 'qdbus org.kde.ksmserver /KSMServer org.kde.KSMServerInterface.logout 0 0 0',
|
|
137
|
+
'restart_wm': 'killall plasmashell && kstart5 plasmashell && sleep 3 && pwr-tray',
|
|
138
|
+
'must_haves': 'loginctl qdbus'.split(),
|
|
139
|
+
}, 'kde-wayland': {
|
|
140
|
+
# sudo apt-get install xdg-utils
|
|
141
|
+
'reset_idle': 'qdbus org.freedesktop.ScreenSaver /ScreenSaver SimulateUserActivity',
|
|
142
|
+
# gdbus introspect --session --dest org.gnome.SessionManager
|
|
143
|
+
# --object-path /org/gnome/SessionManager
|
|
144
|
+
# gdbus call --session --dest org.gnome.SessionManager
|
|
145
|
+
# --object-path /org/gnome/SessionManager
|
|
146
|
+
# --method org.gnome.SessionManager.GetIdleTime
|
|
147
|
+
# from pydbus import SessionBus
|
|
148
|
+
# import time
|
|
149
|
+
# def get_idle_time():
|
|
150
|
+
# bus = SessionBus()
|
|
151
|
+
# screensaver = bus.get("org.freedesktop.ScreenSaver")
|
|
152
|
+
# # The GetSessionIdleTime method returns the idle time in seconds.
|
|
153
|
+
# idle_time = screensaver.GetSessionIdleTime()
|
|
154
|
+
# return idle_time
|
|
155
|
+
# if __name__ == "__main__":
|
|
156
|
+
# while True:
|
|
157
|
+
# idle_time = get_idle_time()
|
|
158
|
+
# print(f"Idle time in seconds: {idle_time}")
|
|
159
|
+
# time.sleep(5)
|
|
160
|
+
'locker': 'loginctl lock-session',
|
|
161
|
+
'must_haves': 'loginctl qdbus'.split(),
|
|
162
|
+
|
|
163
|
+
}, 'gnome-x11': {
|
|
164
|
+
|
|
165
|
+
}, 'gnome-wayland': {
|
|
166
|
+
# sudo apt-get install xdg-utils
|
|
167
|
+
'reset_idle': ('gdbus call --session --dest org.gnome.ScreenSaver --object-path'
|
|
168
|
+
' /org/gnome/ScreenSaver --method org.gnome.ScreenSaver.SimulateUserActivity'),
|
|
169
|
+
# - idle time:
|
|
170
|
+
# pip install pydbus
|
|
171
|
+
# from pydbus import SessionBus
|
|
172
|
+
# bus = SessionBus()
|
|
173
|
+
# screensaver = bus.get("org.gnome.Mutter.IdleMonitor",
|
|
174
|
+
# "/org/gnome/Mutter/IdleMonitor/Core")
|
|
175
|
+
# idle_time = screensaver.GetIdletime()
|
|
176
|
+
|
|
177
|
+
# print(f"Idle time in milliseconds: {idle_time}")
|
|
178
|
+
'locker': 'loginctl lock-session',
|
|
179
|
+
'must_haves': 'qdbus gnome-screensaver-command'.split(),
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
def __init__(self, ini_tool, quick=False):
|
|
185
|
+
PwrTray.singleton = self
|
|
186
|
+
self.app = QApplication([])
|
|
187
|
+
self.app.setQuitOnLastWindowClosed(False)
|
|
188
|
+
while not QSystemTrayIcon.isSystemTrayAvailable():
|
|
189
|
+
prt("System tray is not available. Retry in 1 second...")
|
|
190
|
+
time.sleep(1.0)
|
|
191
|
+
prt("System tray is available. Continuing...")
|
|
192
|
+
|
|
193
|
+
self.ini_tool = ini_tool
|
|
194
|
+
self.battery = SimpleNamespace(present=None,
|
|
195
|
+
plugged=True, percent=100, selector='Settings')
|
|
196
|
+
self.reconfig()
|
|
197
|
+
self.quick = quick
|
|
198
|
+
|
|
199
|
+
self.loop = 0
|
|
200
|
+
self.loop_sample = (1 if quick else 15)
|
|
201
|
+
|
|
202
|
+
## self.singleton.presentation_mode = False
|
|
203
|
+
self.singleton.mode = 'SleepAfterLock' # or 'LockOnly' or 'Presentation'
|
|
204
|
+
self.was_effective_mode = None
|
|
205
|
+
self.was_inhibited = None
|
|
206
|
+
self.was_play_state = ''
|
|
207
|
+
self.was_selector = None
|
|
208
|
+
self.was_output = ''
|
|
209
|
+
self.here_dir = os.path.dirname(os.path.abspath(__file__))
|
|
210
|
+
self.svgs = []
|
|
211
|
+
self.icons = []
|
|
212
|
+
for base in self.svg_info.bases:
|
|
213
|
+
self.svgs.append(f'{base}-v{self.svg_info.version}.svg')
|
|
214
|
+
for resource in self.svgs + ['lockpaper.png']:
|
|
215
|
+
if not os.path.isfile(resource):
|
|
216
|
+
Utils.copy_to_folder(resource, ini_tool.folder)
|
|
217
|
+
if not os.path.isfile(resource):
|
|
218
|
+
prt(f'WARN: cannot find {repr(resource)}')
|
|
219
|
+
continue
|
|
220
|
+
self.icons.append(QIcon(os.path.join(self.ini_tool.folder, resource)))
|
|
221
|
+
|
|
222
|
+
# states are Awake, Locked, Blanked, Asleep
|
|
223
|
+
# when is idle time
|
|
224
|
+
self.tray_icon = QSystemTrayIcon(self.icons[0], self.app)
|
|
225
|
+
self.tray_icon.setToolTip("pwr-tray")
|
|
226
|
+
self.tray_icon.setVisible(True)
|
|
227
|
+
self.state = SimpleNamespace(name='Awake', when=0)
|
|
228
|
+
|
|
229
|
+
self.running_idle_s = 0.000
|
|
230
|
+
self.poll_s = 2.000
|
|
231
|
+
self.poll_100ms = False
|
|
232
|
+
self.lock_began_secs = None # TBD: remove
|
|
233
|
+
self.inh_lock_began_secs = False # TBD: refactor?
|
|
234
|
+
self.rebuild_menu = False
|
|
235
|
+
self.picks_file = ini_tool.picks_path
|
|
236
|
+
self.current_icon_num = -1 # triggers immediate icon update
|
|
237
|
+
self.enable_playerctl = True
|
|
238
|
+
|
|
239
|
+
self.restore_picks()
|
|
240
|
+
if quick:
|
|
241
|
+
for selector in self.ini_tool.get_selectors():
|
|
242
|
+
params = self.ini_tool.params_by_selector[selector]
|
|
243
|
+
params.lock_min_list = [1, 2, 4, 8, 32, 128]
|
|
244
|
+
params.sleep_min_list = [1, 2, 4, 8, 32, 128]
|
|
245
|
+
|
|
246
|
+
# self.down_state = self.opts.down_state
|
|
247
|
+
|
|
248
|
+
self.graphical, self.is_wayland = self.get_environment()
|
|
249
|
+
self.variables = self.default_variables
|
|
250
|
+
must_haves = self.default_variables['must_haves']
|
|
251
|
+
if self.graphical in ('i3', 'kde-x11'):
|
|
252
|
+
self.variables.update(self.overrides['x11'])
|
|
253
|
+
must_haves += self.default_variables['must_haves']
|
|
254
|
+
|
|
255
|
+
self.variables.update(self.overrides[self.graphical])
|
|
256
|
+
must_haves += self.variables['must_haves']
|
|
257
|
+
|
|
258
|
+
dont_haves = []
|
|
259
|
+
for must_have in set(must_haves):
|
|
260
|
+
if shutil.which(must_have) is None:
|
|
261
|
+
dont_haves.append(must_have)
|
|
262
|
+
assert not dont_haves, f'commands NOT on $PATH: {dont_haves}'
|
|
263
|
+
self.has_playerctl = bool(shutil.which('playerctl'))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
self.idle_manager = SwayIdleManager(self) if self.graphical == 'sway' else None
|
|
267
|
+
|
|
268
|
+
self.menu_items = []
|
|
269
|
+
self.menu = None
|
|
270
|
+
self.build_menu()
|
|
271
|
+
self.tray_icon.activated.connect(self.on_tray_icon_activated)
|
|
272
|
+
if self.idle_manager:
|
|
273
|
+
self.idle_manager_start()
|
|
274
|
+
self.timer = QTimer()
|
|
275
|
+
self.timer.setInterval(100) # 100 is initial ... gets recomputed
|
|
276
|
+
self.timer.timeout.connect(self.on_timeout)
|
|
277
|
+
self.timer.start()
|
|
278
|
+
|
|
279
|
+
def get_params(self, selector=None):
|
|
280
|
+
selector = self.battery.selector if selector is None else selector
|
|
281
|
+
return self.ini_tool.params_by_selector[selector]
|
|
282
|
+
|
|
283
|
+
def get_lock_min_list(self, selector=None):
|
|
284
|
+
"""TBD"""
|
|
285
|
+
selector = self.battery.selector if selector is None else selector
|
|
286
|
+
return self.ini_tool.get_current_vals(selector, 'lock_min_list')
|
|
287
|
+
|
|
288
|
+
def get_lock_rotated_list(self, selector=None, first=None):
|
|
289
|
+
"""TBD"""
|
|
290
|
+
selector = self.battery.selector if selector is None else selector
|
|
291
|
+
return self.ini_tool.get_rotated_vals(selector, 'lock_min_list', first)
|
|
292
|
+
|
|
293
|
+
def get_sleep_min_list(self, selector=None):
|
|
294
|
+
"""TBD"""
|
|
295
|
+
selector = self.battery.selector if selector is None else selector
|
|
296
|
+
return self.ini_tool.get_current_vals(selector, 'sleep_min_list')
|
|
297
|
+
|
|
298
|
+
def get_sleep_rotated_list(self, selector=None, first=None):
|
|
299
|
+
"""TBD"""
|
|
300
|
+
selector = self.battery.selector if selector is None else selector
|
|
301
|
+
return self.ini_tool.get_rotated_vals(selector, 'sleep_min_list', first)
|
|
302
|
+
|
|
303
|
+
def get_effective_mode(self):
|
|
304
|
+
"""TBD"""
|
|
305
|
+
return 'SleepAfterLock' if self.battery.selector == 'LoBattery' else self.mode
|
|
306
|
+
|
|
307
|
+
def reconfig(self):
|
|
308
|
+
""" update/fix config """
|
|
309
|
+
if self.ini_tool.update_config():
|
|
310
|
+
self.rebuild_menu = True
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
def save_picks():
|
|
314
|
+
this = PwrTray.singleton
|
|
315
|
+
if this and not this.quick:
|
|
316
|
+
picks = { 'mode': this.mode,
|
|
317
|
+
'enable_playerctl': this.enable_playerctl,
|
|
318
|
+
'lock_mins': {
|
|
319
|
+
'Settings': this.get_lock_min_list('Settings')[0],
|
|
320
|
+
'HiBattery': this.get_lock_min_list('HiBattery')[0],
|
|
321
|
+
'LoBattery': this.get_lock_min_list('LoBattery')[0],
|
|
322
|
+
|
|
323
|
+
}, 'sleep_mins': {
|
|
324
|
+
'Settings': this.get_sleep_min_list('Settings')[0],
|
|
325
|
+
'HiBattery': this.get_sleep_min_list('HiBattery')[0],
|
|
326
|
+
'LoBattery': this.get_sleep_min_list('LoBattery')[0],
|
|
327
|
+
},
|
|
328
|
+
}
|
|
329
|
+
try:
|
|
330
|
+
picks_str = json.dumps(picks)
|
|
331
|
+
with open(this.picks_file, 'w', encoding='utf-8') as f:
|
|
332
|
+
f.write(picks_str + '\n')
|
|
333
|
+
print("Picks saved:", picks_str)
|
|
334
|
+
this.poll_100ms = True
|
|
335
|
+
except Exception as e:
|
|
336
|
+
print(f"An error occurred while saving picks: {e}", file=sys.stderr)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def restore_picks(self):
|
|
340
|
+
try:
|
|
341
|
+
with open(self.picks_file, 'r', encoding='utf-8') as handle:
|
|
342
|
+
picks = json.load(handle)
|
|
343
|
+
self.mode = picks.get('mode', 'SleepAfterLock')
|
|
344
|
+
self.enable_playerctl = picks.get('enable_playerctl', True)
|
|
345
|
+
for attr in 'lock_mins sleep_mins'.split():
|
|
346
|
+
for selector in 'LoBattery HiBattery Settings'.split():
|
|
347
|
+
self.get_lock_rotated_list(selector, first=picks[attr][selector])
|
|
348
|
+
prt('restored Picks OK:', picks)
|
|
349
|
+
return True
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
prt(f'restored picks FAILED: {e}')
|
|
353
|
+
prt(f'mode=self.mode lock_mins={self.get_lock_min_list()}'
|
|
354
|
+
f' sleep_mins={self.get_sleep_min_list()}')
|
|
355
|
+
return True
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _get_down_state(self):
|
|
360
|
+
return 'PowerDown' if self.get_params().power_down else 'Suspend'
|
|
361
|
+
|
|
362
|
+
def update_running_idle_s(self):
|
|
363
|
+
""" Update the running idle seconds (called after each regular timeout) """
|
|
364
|
+
cmd = self.variables['get_idle_ms']
|
|
365
|
+
if cmd:
|
|
366
|
+
xidle_ms = int(subprocess.check_output(cmd.split()).strip())
|
|
367
|
+
xidle_ms *= 2 if self.quick else 1 # time warp
|
|
368
|
+
self.running_idle_s = round(xidle_ms/1000, 3)
|
|
369
|
+
|
|
370
|
+
def DB(self):
|
|
371
|
+
""" is debug on? """
|
|
372
|
+
rv = self.get_params().debug_mode
|
|
373
|
+
return rv
|
|
374
|
+
|
|
375
|
+
def effective_mode(self):
|
|
376
|
+
""" TBD """
|
|
377
|
+
return 'SleepAfterLock' if self.battery.selector == 'LoBattery' else self.mode
|
|
378
|
+
|
|
379
|
+
def show_icon(self, inhibited=''):
|
|
380
|
+
""" Display Icon if updated """
|
|
381
|
+
emode = self.get_effective_mode()
|
|
382
|
+
num = (3 if self.battery.selector == 'LoBattery'
|
|
383
|
+
else 1 if emode in ('Presentation', )
|
|
384
|
+
else 7 if inhibited == 'systemd'
|
|
385
|
+
else 4 if inhibited == 'player'
|
|
386
|
+
else 0 if emode in ('SleepAfterLock',)
|
|
387
|
+
else 2)
|
|
388
|
+
lock_secs = self.get_lock_min_list()[0]*60
|
|
389
|
+
# down_secs = self.get_sleep_min_list()[0]*60 + lock_secs
|
|
390
|
+
moon_when = lock_secs - min(60 , lock_secs/8)
|
|
391
|
+
if num == 0 and self.running_idle_s >= moon_when:
|
|
392
|
+
num = 5
|
|
393
|
+
elif num == 2 and self.running_idle_s >= moon_when:
|
|
394
|
+
num = 6
|
|
395
|
+
# prt(f'{num=} {self.running_idle_s=} {moon_when=}')
|
|
396
|
+
|
|
397
|
+
if num != self.current_icon_num:
|
|
398
|
+
self.tray_icon.setIcon(self.icons[num])
|
|
399
|
+
self.current_icon_num = num
|
|
400
|
+
return True # changed
|
|
401
|
+
return False # unchanged
|
|
402
|
+
|
|
403
|
+
def check_inhibited(self):
|
|
404
|
+
pipe = subprocess.Popen(
|
|
405
|
+
# ['systemd-inhibit', '--no-legend', '--no-pager', '--mode=block'],
|
|
406
|
+
['systemd-inhibit', '--no-pager', '--mode=block'],
|
|
407
|
+
stdout=subprocess.PIPE)
|
|
408
|
+
output, _ = pipe.communicate()
|
|
409
|
+
output = output.decode('utf-8')
|
|
410
|
+
lines = output.splitlines()
|
|
411
|
+
if self.DB() and 'No inhibitors' not in output:
|
|
412
|
+
prt('DB', 'systemd-inhibit:', output.strip())
|
|
413
|
+
rows = []
|
|
414
|
+
inhibited = ''
|
|
415
|
+
for count, line in enumerate(lines):
|
|
416
|
+
if count == 0:
|
|
417
|
+
rows.append(line.strip())
|
|
418
|
+
continue
|
|
419
|
+
if 'block' not in line:
|
|
420
|
+
continue
|
|
421
|
+
if count == 1:
|
|
422
|
+
if ('xfce4-power-man' not in line
|
|
423
|
+
and 'org_kde_powerde' not in line):
|
|
424
|
+
inhibited = 'systemd'
|
|
425
|
+
rows.append(line)
|
|
426
|
+
else:
|
|
427
|
+
inhibited = 'systemd'
|
|
428
|
+
rows.append(line)
|
|
429
|
+
if len(rows) == 1:
|
|
430
|
+
rows = []
|
|
431
|
+
if self.has_playerctl and self.enable_playerctl:
|
|
432
|
+
child = subprocess.run('playerctl status'.split(), check=False,
|
|
433
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
|
434
|
+
play_state = child.stdout.decode('utf-8').strip().lower()
|
|
435
|
+
if play_state == 'playing':
|
|
436
|
+
inhibited = 'player'
|
|
437
|
+
if self.was_play_state != play_state:
|
|
438
|
+
prt(f'{play_state=}')
|
|
439
|
+
self.was_play_state = play_state
|
|
440
|
+
|
|
441
|
+
emode = self.effective_mode()
|
|
442
|
+
|
|
443
|
+
if self.show_icon(inhibited=inhibited):
|
|
444
|
+
self.poll_100ms = True
|
|
445
|
+
self.idle_manager_start()
|
|
446
|
+
|
|
447
|
+
self.was_effective_mode = emode
|
|
448
|
+
self.was_selector = self.battery.selector
|
|
449
|
+
was_output = self.was_output
|
|
450
|
+
self.was_output = output
|
|
451
|
+
self.was_inhibited = inhibited
|
|
452
|
+
return rows, bool(was_output != output)
|
|
453
|
+
|
|
454
|
+
def update_battery_status(self):
|
|
455
|
+
if self.battery.present is False:
|
|
456
|
+
return
|
|
457
|
+
battery = psutil.sensors_battery()
|
|
458
|
+
if battery is None:
|
|
459
|
+
self.battery.present = False
|
|
460
|
+
return
|
|
461
|
+
was_plugged = self.battery.plugged
|
|
462
|
+
was_selector = self.battery.selector
|
|
463
|
+
|
|
464
|
+
self.battery.plugged = battery.power_plugged
|
|
465
|
+
self.battery.percent = round(battery.percent, 1)
|
|
466
|
+
if self.battery.plugged:
|
|
467
|
+
self.battery.selector = 'Settings'
|
|
468
|
+
elif self.battery.percent > self.get_params().lo_battery_pct:
|
|
469
|
+
self.battery.selector = 'HiBattery'
|
|
470
|
+
else:
|
|
471
|
+
self.battery.selector = 'LoBattery'
|
|
472
|
+
if was_plugged != self.battery.plugged or was_selector != self.battery.selector:
|
|
473
|
+
self.rebuild_menu = True
|
|
474
|
+
# IF WANTING TIME LEFT
|
|
475
|
+
# secsleft = battery.secsleft
|
|
476
|
+
# if secsleft == psutil.POWER_TIME_UNLIMITED:
|
|
477
|
+
# time_left = "Calculating..."
|
|
478
|
+
# elif secsleft == psutil.POWER_TIME_UNKNOWN:
|
|
479
|
+
# time_left = "Unknown"
|
|
480
|
+
# else:
|
|
481
|
+
# hours, remainder = divmod(secsleft, 3600)
|
|
482
|
+
# minutes, seconds = divmod(remainder, 60)
|
|
483
|
+
# time_left = f"{hours:02}:{minutes:02}"
|
|
484
|
+
|
|
485
|
+
def reset_xidle_ms(self):
|
|
486
|
+
""" TBD"""
|
|
487
|
+
self.run_command('reset_idle')
|
|
488
|
+
|
|
489
|
+
def set_state(self, name):
|
|
490
|
+
""" Set the state of the applet """
|
|
491
|
+
prt(f'set_state: {name},{self.running_idle_s}s',
|
|
492
|
+
f'was: {self.state.name},{self.state.when}s')
|
|
493
|
+
self.state.name = name
|
|
494
|
+
self.state.when = self.running_idle_s
|
|
495
|
+
|
|
496
|
+
def on_timeout(self):
|
|
497
|
+
"""TBD"""
|
|
498
|
+
self.loop += 1
|
|
499
|
+
if self.DB():
|
|
500
|
+
prt('DB', f'on_timeout() {self.loop=}/{self.loop_sample} ...')
|
|
501
|
+
if not QSystemTrayIcon.isSystemTrayAvailable():
|
|
502
|
+
prt('SystemTray is gone ... exiting')
|
|
503
|
+
self.exit_wm(None)
|
|
504
|
+
|
|
505
|
+
self.reconfig()
|
|
506
|
+
if self.idle_manager:
|
|
507
|
+
self.idle_manager.checkup()
|
|
508
|
+
self.update_battery_status()
|
|
509
|
+
|
|
510
|
+
rows, updated = self.check_inhibited()
|
|
511
|
+
if updated or self.rebuild_menu:
|
|
512
|
+
self.build_menu(rows)
|
|
513
|
+
self.rebuild_menu = False
|
|
514
|
+
prt('re-built menu')
|
|
515
|
+
|
|
516
|
+
if self.loop >= self.loop_sample:
|
|
517
|
+
self.update_running_idle_s()
|
|
518
|
+
lock_secs = self.get_lock_min_list()[0]*60
|
|
519
|
+
down_secs = self.get_sleep_min_list()[0]*60 + lock_secs
|
|
520
|
+
blank_secs = 5 if self.quick else 20
|
|
521
|
+
# if 0 <= int(self.get_params().dim_pct_brightness) < 100:
|
|
522
|
+
# dim_secs = int(round(lock_secs * int(self.get_params().dim_pct_lock_min) / 100, 0))
|
|
523
|
+
# else:
|
|
524
|
+
# dim_secs = 2000 + lock_secs * 2 # make it never happen
|
|
525
|
+
|
|
526
|
+
emit = f'idle_s={self.running_idle_s} state={self.state.name},{self.state.when}s'
|
|
527
|
+
emode = self.get_effective_mode()
|
|
528
|
+
if emode in ('LockOnly', 'SleepAfterLock'):
|
|
529
|
+
emit += f' @{self.get_lock_min_list()[0]}m'
|
|
530
|
+
if emode in ('SleepAfterLock', ):
|
|
531
|
+
emit += f'+{self.get_sleep_min_list()[0]}m'
|
|
532
|
+
if self.battery.selector != 'Settings':
|
|
533
|
+
emit += f' {self.battery.selector}'
|
|
534
|
+
prt(emit)
|
|
535
|
+
|
|
536
|
+
if emode in ('Presentation',) or self.was_inhibited:
|
|
537
|
+
if 'sway' in self.graphical:
|
|
538
|
+
self.reset_xidle_ms() # we don't know when
|
|
539
|
+
elif self.running_idle_s > min(50, lock_secs*0.40):
|
|
540
|
+
self.reset_xidle_ms() # we don't know when
|
|
541
|
+
|
|
542
|
+
elif (self.running_idle_s >= down_secs and emode not in ('LockOnly',)
|
|
543
|
+
and self.state.name in ('Awake', 'Locked', 'Blanked')):
|
|
544
|
+
if self._get_down_state() == 'PowerOff':
|
|
545
|
+
self.poweroff(None)
|
|
546
|
+
else:
|
|
547
|
+
self.suspend(None)
|
|
548
|
+
|
|
549
|
+
elif (self.running_idle_s >= lock_secs and emode not in ('Presentation',)
|
|
550
|
+
and self.state.name in ('Awake', 'Dim')):
|
|
551
|
+
self.lock_screen(None)
|
|
552
|
+
|
|
553
|
+
# elif (self.running_idle_s >= dim_secs and emode not in ('Presentation',)
|
|
554
|
+
# and self.state.name in ('Awake',)):
|
|
555
|
+
# self.dimmer(None)
|
|
556
|
+
|
|
557
|
+
elif (self.running_idle_s >= self.state.when + blank_secs
|
|
558
|
+
and self.get_params().turn_off_monitors
|
|
559
|
+
and emode not in ('Presentation',)
|
|
560
|
+
and self.state.name in ('Locked',)):
|
|
561
|
+
self.blank_primitive()
|
|
562
|
+
|
|
563
|
+
elif self.inh_lock_began_secs:
|
|
564
|
+
self.inh_lock_began_secs = False
|
|
565
|
+
|
|
566
|
+
elif self.running_idle_s < lock_secs and self.state.name not in ('Awake', ):
|
|
567
|
+
self.set_state('Awake')
|
|
568
|
+
|
|
569
|
+
self.loop = 0
|
|
570
|
+
|
|
571
|
+
if self.poll_100ms:
|
|
572
|
+
poll_ms = 100
|
|
573
|
+
self.poll_100ms = False
|
|
574
|
+
self.loop = self.loop_sample
|
|
575
|
+
prt(f'{poll_ms=}')
|
|
576
|
+
else:
|
|
577
|
+
poll_ms = int(self.poll_s * 1000)
|
|
578
|
+
self.timer.setInterval(poll_ms)
|
|
579
|
+
|
|
580
|
+
def _toggle_battery(self, _=None):
|
|
581
|
+
if self.battery.present is False:
|
|
582
|
+
# lets you either use the lo/hi battery setting for another
|
|
583
|
+
# purpose or test out your battery settings
|
|
584
|
+
selector = self.battery.selector
|
|
585
|
+
selector = ('LoBattery' if selector == 'HiBattery'
|
|
586
|
+
else 'Settings' if selector == 'LoBattery' else 'HiBattery')
|
|
587
|
+
self.battery.selector = selector
|
|
588
|
+
# self.ini_tool.set_effective_params(selector)
|
|
589
|
+
self.rebuild_menu = True
|
|
590
|
+
|
|
591
|
+
def _lock_rotate_next(self, advance=None):
|
|
592
|
+
advance = True if advance is None else advance
|
|
593
|
+
mins = self.get_lock_min_list()
|
|
594
|
+
if len(mins) < 1 or (len(mins) == 2 and mins[0] == mins[1]):
|
|
595
|
+
return mins[0]
|
|
596
|
+
next_mins = mins[1]
|
|
597
|
+
if advance:
|
|
598
|
+
mins0 = mins[0]
|
|
599
|
+
mins = self.get_lock_rotated_list(first=next_mins)
|
|
600
|
+
self.rebuild_menu = bool(mins0 != mins[0])
|
|
601
|
+
self.save_picks()
|
|
602
|
+
self.idle_manager_start()
|
|
603
|
+
return next_mins
|
|
604
|
+
|
|
605
|
+
def _lock_rotate_str(self):
|
|
606
|
+
mins = self.get_lock_min_list()
|
|
607
|
+
rv = f'{mins[0]}m' + ('' if mins[0] == mins[1] else f'->{mins[1]}m')
|
|
608
|
+
return rv
|
|
609
|
+
|
|
610
|
+
def toggle_playerctl(self):
|
|
611
|
+
if self.has_playerctl:
|
|
612
|
+
self.enable_playerctl = not bool(self.enable_playerctl)
|
|
613
|
+
self.save_picks()
|
|
614
|
+
self.rebuild_menu = True
|
|
615
|
+
|
|
616
|
+
def _sleep_rotate_next(self, advance=None):
|
|
617
|
+
advance = True if advance is None else advance
|
|
618
|
+
mins = self.get_sleep_min_list()
|
|
619
|
+
if len(mins) < 1 or (len(mins) == 2 and mins[0] == mins[1]):
|
|
620
|
+
return mins[0]
|
|
621
|
+
next_mins = mins[1]
|
|
622
|
+
if advance:
|
|
623
|
+
mins0 = mins[0]
|
|
624
|
+
mins = self.get_sleep_rotated_list(first=next_mins)
|
|
625
|
+
self.rebuild_menu = bool(mins0 != mins[0])
|
|
626
|
+
self.save_picks()
|
|
627
|
+
self.idle_manager_start()
|
|
628
|
+
return next_mins
|
|
629
|
+
|
|
630
|
+
def _sleep_rotate_str(self):
|
|
631
|
+
mins = self.get_sleep_min_list()
|
|
632
|
+
rv = f'{mins[0]}m' + ('' if mins[0] == mins[1] else f'->{mins[1]}m')
|
|
633
|
+
return rv
|
|
634
|
+
|
|
635
|
+
def build_menu(self, rows=None):
|
|
636
|
+
"""TBD"""
|
|
637
|
+
# pylint: disable=unnecessary-lambda
|
|
638
|
+
def has_cmd(label):
|
|
639
|
+
return bool(self.variables.get(label, None))
|
|
640
|
+
|
|
641
|
+
def add_item(text, callback):
|
|
642
|
+
nonlocal self
|
|
643
|
+
item = QAction(text)
|
|
644
|
+
item.triggered.connect(callback)
|
|
645
|
+
self.menu.addAction(item)
|
|
646
|
+
self.menu_items.append(item)
|
|
647
|
+
|
|
648
|
+
self.menu = QMenu()
|
|
649
|
+
self.menu_items = []
|
|
650
|
+
|
|
651
|
+
if rows:
|
|
652
|
+
for row in rows:
|
|
653
|
+
add_item(row, self.dummy)
|
|
654
|
+
|
|
655
|
+
if self.mode not in ('Presentation',):
|
|
656
|
+
add_item(f'🅟 Presentation ⮜ {self.mode} Mode', self.enable_presentation_mode)
|
|
657
|
+
|
|
658
|
+
if self.mode not in ('LockOnly',):
|
|
659
|
+
add_item(f'🅛 LockOnly ⮜ {self.mode} Mode', self.enable_nosleep_mode)
|
|
660
|
+
|
|
661
|
+
if self.mode not in ('SleepAfterLock',):
|
|
662
|
+
add_item(f'🅢 SleepAfterLock ⮜ {self.mode} Mode', self.enable_normal_mode)
|
|
663
|
+
|
|
664
|
+
add_item(f'{self.graphical}: ▷ Lock Screen', self.lock_screen)
|
|
665
|
+
|
|
666
|
+
if (self.get_params().turn_off_monitors and self.variables['monitors_off']):
|
|
667
|
+
add_item(' ▷ Blank Monitors', self.blank_quick)
|
|
668
|
+
|
|
669
|
+
if has_cmd('reload_wm'):
|
|
670
|
+
add_item(' ▷ Reload', self.reload_wm)
|
|
671
|
+
|
|
672
|
+
if has_cmd('restart_wm'):
|
|
673
|
+
add_item(' ▷ Restart', self.restart_wm)
|
|
674
|
+
|
|
675
|
+
add_item(' ▷ Log Off', self.exit_wm)
|
|
676
|
+
add_item('System: ▼ Suspend', self.suspend)
|
|
677
|
+
add_item(' ▼ Reboot', self.reboot)
|
|
678
|
+
add_item(' ▼ PowerOff', self.poweroff)
|
|
679
|
+
|
|
680
|
+
selector, percent = self.battery.selector, self.battery.percent
|
|
681
|
+
add_item('🗲 Plugged In' if selector == 'Settings'
|
|
682
|
+
else (('█' if selector == 'HiBattery' else '▃') + f' {selector}')
|
|
683
|
+
+ (f' {percent}%' if percent < 100 or selector != 'Settings' else '')
|
|
684
|
+
, self._toggle_battery)
|
|
685
|
+
|
|
686
|
+
# if self.mode not in ('Presentation',) and len(self.opts.lock_min_list) > 1:
|
|
687
|
+
add_item(f' ♺ Lock: {self._lock_rotate_str()}',
|
|
688
|
+
lambda: self._lock_rotate_next())
|
|
689
|
+
|
|
690
|
+
# if self.mode in ('SleepAfterLock',) and len(self.opts.sleep_min_list) > 1:
|
|
691
|
+
add_item(f' ♺ Sleep (after Lock): {self._sleep_rotate_str()}',
|
|
692
|
+
lambda: self._sleep_rotate_next())
|
|
693
|
+
|
|
694
|
+
label = '🎝 PlayerCtl: '
|
|
695
|
+
label += ('not installed' if not self.has_playerctl
|
|
696
|
+
else 'Enabled' if self.enable_playerctl
|
|
697
|
+
else 'Disabled')
|
|
698
|
+
add_item(label, self.toggle_playerctl)
|
|
699
|
+
if self.get_params().gui_editor:
|
|
700
|
+
add_item('🖹 Edit Applet Config', self.edit_config)
|
|
701
|
+
|
|
702
|
+
add_item('☓ Quit this Applet', self.quit_self)
|
|
703
|
+
|
|
704
|
+
add_item('↺ Restart this Applet', self.restart_self)
|
|
705
|
+
|
|
706
|
+
self.tray_icon.setContextMenu(self.menu)
|
|
707
|
+
|
|
708
|
+
if not self.tray_icon.isVisible():
|
|
709
|
+
prt('self.tray_icon.isVisible() is False ... restarting app')
|
|
710
|
+
time.sleep(0.5)
|
|
711
|
+
self.restart_self(None)
|
|
712
|
+
|
|
713
|
+
def on_tray_icon_activated(self, reason):
|
|
714
|
+
if reason == QSystemTrayIcon.Context: # Right click
|
|
715
|
+
self.tray_icon.contextMenu().exec_(QCursor.pos()) # Show the context menu
|
|
716
|
+
elif (reason == QSystemTrayIcon.Trigger # Left click
|
|
717
|
+
and not self.is_wayland): # wayland behaves badly
|
|
718
|
+
self.tray_icon.contextMenu().exec_(QCursor.pos()) # Show the context menu
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def idle_manager_start(self):
|
|
722
|
+
""" For any mode, get the idle manager started"""
|
|
723
|
+
if self.idle_manager:
|
|
724
|
+
self.idle_manager.start()
|
|
725
|
+
|
|
726
|
+
@staticmethod
|
|
727
|
+
def dummy(_):
|
|
728
|
+
"""TBD"""
|
|
729
|
+
|
|
730
|
+
@staticmethod
|
|
731
|
+
def run_command(key):
|
|
732
|
+
this = PwrTray.singleton
|
|
733
|
+
command = this.variables.get(key, None)
|
|
734
|
+
if command and key == 'locker' and this.graphical in ('i3', 'sway'):
|
|
735
|
+
if this.graphical == 'i3':
|
|
736
|
+
append = this.get_params().i3lock_args
|
|
737
|
+
elif this.graphical == 'sway':
|
|
738
|
+
append = this.get_params().swaylock_args
|
|
739
|
+
if not append:
|
|
740
|
+
append = '-t -i ./lockpaper.png'
|
|
741
|
+
command += ' ' + append
|
|
742
|
+
|
|
743
|
+
# elif key == 'dimmer':
|
|
744
|
+
# percent = int(round(int(thisget_params()params.dim_pct_brightness), 0))
|
|
745
|
+
# command = command.replace('{percent}', str(percent))
|
|
746
|
+
|
|
747
|
+
if command:
|
|
748
|
+
prt(f'+ {command}')
|
|
749
|
+
if re.match(r'^(\s\w\-)*$', command):
|
|
750
|
+
result = subprocess.run(command.split(), check=False)
|
|
751
|
+
else:
|
|
752
|
+
result = subprocess.run(command, check=False, shell=True)
|
|
753
|
+
if result.returncode != 0:
|
|
754
|
+
prt(f' NOTE: returncode={result.returncode}')
|
|
755
|
+
|
|
756
|
+
@staticmethod
|
|
757
|
+
def quit_self(_):
|
|
758
|
+
"""TBD"""
|
|
759
|
+
prt('+', 'quitting applet...')
|
|
760
|
+
this = PwrTray.singleton
|
|
761
|
+
if this:
|
|
762
|
+
this.tray_icon.hide()
|
|
763
|
+
sys.exit()
|
|
764
|
+
|
|
765
|
+
@staticmethod
|
|
766
|
+
def restart_self(_):
|
|
767
|
+
"""TBD"""
|
|
768
|
+
prt('+', 'restarting applet...')
|
|
769
|
+
this = PwrTray.singleton
|
|
770
|
+
if this:
|
|
771
|
+
this.tray_icon.hide()
|
|
772
|
+
PwrTray.save_picks()
|
|
773
|
+
subprocess.Popen([sys.executable] + sys.argv)
|
|
774
|
+
os._exit(0)
|
|
775
|
+
|
|
776
|
+
@staticmethod
|
|
777
|
+
def edit_config(_):
|
|
778
|
+
this = PwrTray.singleton
|
|
779
|
+
if this.get_params().gui_editor:
|
|
780
|
+
try:
|
|
781
|
+
ini_path = this.ini_tool.ini_path
|
|
782
|
+
arguments = this.get_params().gui_editor.split()
|
|
783
|
+
arguments.append(ini_path)
|
|
784
|
+
prt('+', 'running:', arguments)
|
|
785
|
+
subprocess.run(arguments, check=True)
|
|
786
|
+
except Exception as e:
|
|
787
|
+
prt(f"Edit Config ERR: {e}")
|
|
788
|
+
|
|
789
|
+
@staticmethod
|
|
790
|
+
def suspend(_):
|
|
791
|
+
"""TBD"""
|
|
792
|
+
this = PwrTray.singleton
|
|
793
|
+
this.set_state('Asleep')
|
|
794
|
+
this.reset_xidle_ms()
|
|
795
|
+
if this.graphical in ('i3', ):
|
|
796
|
+
PwrTray.run_command('locker')
|
|
797
|
+
PwrTray.run_command('suspend')
|
|
798
|
+
|
|
799
|
+
@staticmethod
|
|
800
|
+
def poweroff(_):
|
|
801
|
+
"""TBD"""
|
|
802
|
+
PwrTray.run_command('poweroff')
|
|
803
|
+
|
|
804
|
+
@staticmethod
|
|
805
|
+
def reboot(_):
|
|
806
|
+
"""TBD"""
|
|
807
|
+
PwrTray.run_command('reboot')
|
|
808
|
+
|
|
809
|
+
@staticmethod
|
|
810
|
+
def dimmer(_):
|
|
811
|
+
"""TBD"""
|
|
812
|
+
PwrTray.run_command('dimmer')
|
|
813
|
+
|
|
814
|
+
@staticmethod
|
|
815
|
+
def undim(_):
|
|
816
|
+
"""TBD"""
|
|
817
|
+
PwrTray.run_command('undim')
|
|
818
|
+
|
|
819
|
+
@staticmethod
|
|
820
|
+
def lock_screen(_):
|
|
821
|
+
this = PwrTray.singleton
|
|
822
|
+
PwrTray.run_command('locker')
|
|
823
|
+
this.update_running_idle_s()
|
|
824
|
+
# if 0 <= int(thisget_params()params.dim_pct_brightness) < 100:
|
|
825
|
+
# this.undim(None)
|
|
826
|
+
this.set_state('Locked')
|
|
827
|
+
|
|
828
|
+
def blank_primitive(self, lock_screen=False):
|
|
829
|
+
"""TBD"""
|
|
830
|
+
cmd = self.variables['monitors_off']
|
|
831
|
+
if cmd and self.get_params().turn_off_monitors:
|
|
832
|
+
if lock_screen:
|
|
833
|
+
# self.lock_screen(None, before='sleep 1.5; ')
|
|
834
|
+
self.lock_screen(None)
|
|
835
|
+
prt('+', cmd)
|
|
836
|
+
result = subprocess.run(cmd, shell=True, check=False)
|
|
837
|
+
if result.returncode != 0:
|
|
838
|
+
prt(f' NOTE: returncode={result.returncode}')
|
|
839
|
+
self.set_state('Blanked')
|
|
840
|
+
|
|
841
|
+
else:
|
|
842
|
+
prt('NOTE: blanking screen unsupported')
|
|
843
|
+
|
|
844
|
+
@staticmethod
|
|
845
|
+
def blank_quick(_):
|
|
846
|
+
this = PwrTray.singleton
|
|
847
|
+
this.blank_primitive(lock_screen=True)
|
|
848
|
+
|
|
849
|
+
@staticmethod
|
|
850
|
+
def reload_wm(_):
|
|
851
|
+
PwrTray.run_command('reload_wm')
|
|
852
|
+
|
|
853
|
+
@staticmethod
|
|
854
|
+
def restart_wm(_):
|
|
855
|
+
PwrTray.run_command('restart_wm')
|
|
856
|
+
|
|
857
|
+
@staticmethod
|
|
858
|
+
def exit_wm(_):
|
|
859
|
+
PwrTray.run_command('logoff')
|
|
860
|
+
|
|
861
|
+
@staticmethod
|
|
862
|
+
def enable_presentation_mode(_):
|
|
863
|
+
"""TBD"""
|
|
864
|
+
this = PwrTray.singleton
|
|
865
|
+
this.mode = 'Presentation'
|
|
866
|
+
this.was_output = 'Changed Mode'
|
|
867
|
+
prt('+', f'{this.mode=}')
|
|
868
|
+
this.save_picks()
|
|
869
|
+
this.idle_manager_start()
|
|
870
|
+
|
|
871
|
+
@staticmethod
|
|
872
|
+
def enable_nosleep_mode(_):
|
|
873
|
+
"""TBD"""
|
|
874
|
+
this = PwrTray.singleton
|
|
875
|
+
this.mode = 'LockOnly'
|
|
876
|
+
this.was_output = 'Changed Mode'
|
|
877
|
+
prt('+', f'{this.mode=}')
|
|
878
|
+
this.save_picks()
|
|
879
|
+
this.idle_manager_start()
|
|
880
|
+
|
|
881
|
+
@staticmethod
|
|
882
|
+
def enable_normal_mode(_):
|
|
883
|
+
"""TBD"""
|
|
884
|
+
this = PwrTray.singleton
|
|
885
|
+
this.mode = 'SleepAfterLock'
|
|
886
|
+
this.was_output = 'Changed Mode'
|
|
887
|
+
prt('+', f'{this.mode=}')
|
|
888
|
+
this.save_picks()
|
|
889
|
+
this.idle_manager_start()
|
|
890
|
+
|
|
891
|
+
# Function to check for ACPI events
|
|
892
|
+
@staticmethod
|
|
893
|
+
def acpi_event_listener():
|
|
894
|
+
acpi_pipe = subprocess.Popen(["acpi_listen"], stdout=subprocess.PIPE)
|
|
895
|
+
for line in acpi_pipe.stdout:
|
|
896
|
+
this = PwrTray.singleton
|
|
897
|
+
if this and b"resume" in line:
|
|
898
|
+
prt('acpi event:', line)
|
|
899
|
+
# Reset idle timer
|
|
900
|
+
this.reset_xidle_ms()
|
|
901
|
+
elif this:
|
|
902
|
+
prt('UNSELECTED acpi event:', line)
|
|
903
|
+
# Reset idle timer
|
|
904
|
+
this.reset_xidle_ms()
|
|
905
|
+
@staticmethod
|
|
906
|
+
def goodbye(message=''):
|
|
907
|
+
prt(f'ENDED {message}')
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def main():
|
|
911
|
+
# pylint: disable=import-outside-toplevel
|
|
912
|
+
import argparse
|
|
913
|
+
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
914
|
+
# os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
|
915
|
+
|
|
916
|
+
parser = argparse.ArgumentParser()
|
|
917
|
+
parser.add_argument('-D', '--debug', action='store_true',
|
|
918
|
+
help='override debug_mode from .ini initially')
|
|
919
|
+
parser.add_argument('-o', '--stdout', action='store_true',
|
|
920
|
+
help='log to stdout (if a tty)')
|
|
921
|
+
parser.add_argument('-f', '--follow-log', action='store_true',
|
|
922
|
+
help='exec tail -n50 -F on log file')
|
|
923
|
+
parser.add_argument('-e', '--edit-config', action='store_true',
|
|
924
|
+
help='exec ${EDITOR:-vim} on config.ini file')
|
|
925
|
+
parser.add_argument('-q', '--quick', action='store_true',
|
|
926
|
+
help='quick mode (1m lock + 1m sleep')
|
|
927
|
+
opts = parser.parse_args()
|
|
928
|
+
|
|
929
|
+
if opts.edit_config:
|
|
930
|
+
ini_tool = IniTool(paths_only=True)
|
|
931
|
+
editor = os.getenv('EDITOR', 'vim')
|
|
932
|
+
args = [editor, ini_tool.ini_path]
|
|
933
|
+
print(f'RUNNING: {args}')
|
|
934
|
+
os.execvp(editor, args)
|
|
935
|
+
sys.exit(1) # just in case ;-)
|
|
936
|
+
|
|
937
|
+
if opts.follow_log:
|
|
938
|
+
ini_tool = IniTool(paths_only=True)
|
|
939
|
+
args = ['tail', '-n50', '-F', ini_tool.log_path]
|
|
940
|
+
print(f'RUNNING: {args}')
|
|
941
|
+
os.execvp('tail', args)
|
|
942
|
+
sys.exit(1) # just in case ;-)
|
|
943
|
+
|
|
944
|
+
# os.environ['DISPLAY'] = ':0'
|
|
945
|
+
|
|
946
|
+
ini_tool = IniTool(paths_only=False)
|
|
947
|
+
Utils.prt_path = ini_tool.log_path
|
|
948
|
+
prt('START-UP', to_stdout=opts.stdout)
|
|
949
|
+
PyKill().kill_loop('pwr-tray')
|
|
950
|
+
atexit.register(PwrTray.goodbye)
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
if opts.debug:
|
|
954
|
+
for selector in ini_tool.get_selectors():
|
|
955
|
+
ini_tool.params_by_selector[selector].debug_mode = True # one-time override
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
tray = PwrTray(ini_tool=ini_tool, quick=opts.quick)
|
|
959
|
+
tray.app.exec_()
|
|
960
|
+
|
|
961
|
+
if __name__ == "__main__":
|
|
962
|
+
try:
|
|
963
|
+
main()
|
|
964
|
+
sys.exit(0)
|
|
965
|
+
|
|
966
|
+
###### AppIndicator3 is catching most of these so may not get many exceptions
|
|
967
|
+
except KeyboardInterrupt:
|
|
968
|
+
prt("Shutdown requested, so exiting ...")
|
|
969
|
+
sys.exit(1)
|
|
970
|
+
|
|
971
|
+
except Exception as exc:
|
|
972
|
+
prt("Caught exception running main(), so exiting ...\n",
|
|
973
|
+
traceback.format_exc(limit=24))
|
|
974
|
+
sys.exit(9)
|