added warning capabilities
This commit is contained in:
parent
396e5916a2
commit
2ca8ad5217
4 changed files with 106 additions and 7 deletions
14
config.yaml
14
config.yaml
|
|
@ -2,12 +2,20 @@ check-interval: 1
|
||||||
timers:
|
timers:
|
||||||
gaming:
|
gaming:
|
||||||
apps:
|
apps:
|
||||||
- retroarch
|
|
||||||
- minecraft
|
- minecraft
|
||||||
|
- xmcl
|
||||||
|
- mcpelauncher-ui
|
||||||
# how many minutes of usage
|
# how many minutes of usage
|
||||||
time-limit: 40
|
time-limit: 5
|
||||||
# within how many hours of interval
|
# within how many hours of interval
|
||||||
limit-interval: 12
|
limit-interval: 4
|
||||||
|
# warn n minutes before reaching the limit
|
||||||
|
warn-threshold: 4
|
||||||
|
# commands run on GNOME to pre-warn/final-warn
|
||||||
|
warn-command: >
|
||||||
|
runuser -l randogoth -c 'DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send "App Timer" "Gaming time is almost up ({time_left_int} min left)" -u critical && canberra-gtk-play -i complete'
|
||||||
|
final-warn-command: >
|
||||||
|
runuser -l randogoth -c 'DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send "App Timer" "Final minute before shutdown ({time_left_seconds}s)" -u critical && canberra-gtk-play -i alarm-clock-elapsed'
|
||||||
websurf:
|
websurf:
|
||||||
apps:
|
apps:
|
||||||
- chrome
|
- chrome
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import math
|
||||||
import os, time
|
import os, time
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
@ -27,6 +28,7 @@ class Usage(object):
|
||||||
super(Usage, self).__init__()
|
super(Usage, self).__init__()
|
||||||
self.timer = timer
|
self.timer = timer
|
||||||
self.file = '%s/usage/%s' % (CONFIG_PATH, self.timer.name)
|
self.file = '%s/usage/%s' % (CONFIG_PATH, self.timer.name)
|
||||||
|
self.final_warning_sent = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current(self):
|
def current(self):
|
||||||
|
|
@ -45,7 +47,9 @@ class Usage(object):
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
'''Remove the timer usage to restart the counter'''
|
'''Remove the timer usage to restart the counter'''
|
||||||
os.remove(self.file)
|
if os.path.exists(self.file):
|
||||||
|
os.remove(self.file)
|
||||||
|
self.final_warning_sent = False
|
||||||
|
|
||||||
def isOffLimit(self):
|
def isOffLimit(self):
|
||||||
'''Is the timer usage off its limits'''
|
'''Is the timer usage off its limits'''
|
||||||
|
|
@ -99,18 +103,89 @@ class Timer(object):
|
||||||
apps = [app.strip() for app in item_apps]
|
apps = [app.strip() for app in item_apps]
|
||||||
return apps
|
return apps
|
||||||
|
|
||||||
|
@property
|
||||||
|
def warnThreshold(self):
|
||||||
|
threshold = self.item.get('warn-threshold', None)
|
||||||
|
if threshold is None:
|
||||||
|
return None
|
||||||
|
return float(threshold)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def warnCommand(self):
|
||||||
|
return self.item.get('warn-command')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def finalWarnCommand(self):
|
||||||
|
return self.item.get('final-warn-command')
|
||||||
|
|
||||||
|
def _command_context(self, time_left):
|
||||||
|
minutes = max(time_left, 0)
|
||||||
|
seconds = minutes * 60
|
||||||
|
return {
|
||||||
|
'timer_name': self.name,
|
||||||
|
'time_left': minutes,
|
||||||
|
'time_left_int': int(math.ceil(minutes)),
|
||||||
|
'time_left_floor': int(math.floor(minutes)),
|
||||||
|
'time_left_seconds': int(math.ceil(seconds)),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _prepareCommand(self, command, context):
|
||||||
|
if isinstance(command, (list, tuple)):
|
||||||
|
command = ' '.join([str(part) for part in command])
|
||||||
|
if not command:
|
||||||
|
return None
|
||||||
|
context = context or {}
|
||||||
|
try:
|
||||||
|
return command.format(**context)
|
||||||
|
except (KeyError, IndexError, ValueError) as exc:
|
||||||
|
print('Timer %s warning command format error (%s), using raw command' % (self.name, exc))
|
||||||
|
return command
|
||||||
|
|
||||||
|
def _runCommand(self, command, label, context=None):
|
||||||
|
cmd = self._prepareCommand(command, context)
|
||||||
|
if not cmd:
|
||||||
|
return
|
||||||
|
print('Timer %s running %s command: %s' % (self.name, label, cmd))
|
||||||
|
shell(cmd)
|
||||||
|
|
||||||
|
def maybeWarn(self, check_interval):
|
||||||
|
if self.timeLimit < 0:
|
||||||
|
return
|
||||||
|
current_usage = self.usage.current
|
||||||
|
time_left = self.timeLimit - current_usage
|
||||||
|
if time_left <= 0:
|
||||||
|
if self.usage.final_warning_sent:
|
||||||
|
self.usage.final_warning_sent = False
|
||||||
|
return
|
||||||
|
context = self._command_context(time_left)
|
||||||
|
if (self.warnThreshold is not None and self.warnCommand and
|
||||||
|
time_left <= self.warnThreshold):
|
||||||
|
self._runCommand(self.warnCommand, 'warning', context)
|
||||||
|
final_command = self.finalWarnCommand
|
||||||
|
if not final_command:
|
||||||
|
return
|
||||||
|
interval = float(check_interval)
|
||||||
|
if time_left <= interval:
|
||||||
|
if not self.usage.final_warning_sent:
|
||||||
|
self._runCommand(final_command, 'final warning', context)
|
||||||
|
self.usage.final_warning_sent = True
|
||||||
|
else:
|
||||||
|
# reset flag if the next loop is no longer the final one
|
||||||
|
if self.usage.final_warning_sent:
|
||||||
|
self.usage.final_warning_sent = False
|
||||||
|
|
||||||
def isRunning(self):
|
def isRunning(self):
|
||||||
running = False
|
running = False
|
||||||
for app in self.apps:
|
for app in self.apps:
|
||||||
cmd = 'ps aux | grep -v grep | grep "%s"' % app
|
cmd = 'pgrep -f "%s"' % app
|
||||||
res = shell(cmd)
|
res = shell(cmd)
|
||||||
if len(res):
|
if res.strip():
|
||||||
running = True
|
running = True
|
||||||
return running
|
return running
|
||||||
|
|
||||||
def block(self):
|
def block(self):
|
||||||
for app in self.apps:
|
for app in self.apps:
|
||||||
cmd = 'killall "%s"' % app
|
cmd = 'pkill -f "%s"' % app
|
||||||
shell(cmd)
|
shell(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
15
readme.md
15
readme.md
|
|
@ -23,6 +23,21 @@ timers:
|
||||||
time-limit: 60
|
time-limit: 60
|
||||||
# within how many hours of interval
|
# within how many hours of interval
|
||||||
limit-interval: 12
|
limit-interval: 12
|
||||||
|
# optional: warn N minutes before the limit is reached
|
||||||
|
warn-threshold: 5
|
||||||
|
# run on every loop inside the warning window (anything executable)
|
||||||
|
warn-command: "notify-send 'App Timer' 'Only {time_left_int} minutes left'"
|
||||||
|
# run only on the final loop before apps are blocked
|
||||||
|
final-warn-command: "notify-send 'App Timer' 'Last minute before shutdown'"
|
||||||
|
```
|
||||||
|
|
||||||
|
`warn-threshold` is measured in minutes and triggers the `warn-command` every loop while there is still time remaining. `final-warn-command` fires once on the last loop before the timer exceeds its limit (based on `check-interval`). All commands are executed through the shell, so they can show desktop notifications, play sounds, etc. You can interpolate values in the command string using `{timer_name}`, `{time_left}`, `{time_left_int}`, `{time_left_floor}`, and `{time_left_seconds}` (any Python format specifiers are supported, e.g. `{time_left:.1f}`).
|
||||||
|
|
||||||
|
If the systemd service runs as `root`, wrap desktop commands so they execute inside your session:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
warn-command: >
|
||||||
|
runuser -l randogoth -c 'DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send "App Timer" "{timer_name}: {time_left_int} minutes left"'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
|
||||||
1
timer.py
1
timer.py
|
|
@ -12,6 +12,7 @@ def check_timers(config):
|
||||||
if not timer.isRunning():
|
if not timer.isRunning():
|
||||||
continue
|
continue
|
||||||
print('Timer %s is running' % timer.name)
|
print('Timer %s is running' % timer.name)
|
||||||
|
timer.maybeWarn(config.checkInterval)
|
||||||
# check for off limit apps
|
# check for off limit apps
|
||||||
if timer.usage.isOffLimit():
|
if timer.usage.isOffLimit():
|
||||||
print('Timer %s is off limit' % timer.name)
|
print('Timer %s is off limit' % timer.name)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue