Skip to content

Commit 4607ee5

Browse files
committed
Merge pull request #147 from dschep/locked
WIP of #125: screen locking detection * dschep/locked: yapf-ify WIP of #125: screen locking detection
2 parents bd06e7f + fa49894 commit 4607ee5

4 files changed

Lines changed: 158 additions & 0 deletions

File tree

ntfy/backends/multi.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from importlib import import_module
2+
try:
3+
from ..terminal import is_focused
4+
except ImportError:
5+
6+
def is_focused():
7+
return True
8+
9+
10+
from ..screensaver import is_locked
11+
12+
13+
def notify(title,
14+
message,
15+
locked=None,
16+
focused=None,
17+
unfocused=None,
18+
retcode=None):
19+
for condition, options in ((is_locked, locked), (is_focused, focused),
20+
(lambda: not is_focused(), unfocused)):
21+
for backend_name, backend_options in options.items():
22+
if not condition():
23+
continue
24+
backend = import_module('ntfy.backends.{}'.format(
25+
backend_options.get('backend', backend_name)))
26+
backend_options.pop('backend', None)
27+
backend.notify(title, message, retcode=retcode, **backend_options)

ntfy/cli.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ def is_focused():
3434
return True
3535

3636

37+
from .screensaver import is_locked
38+
39+
3740
def run_cmd(args):
3841
if getattr(args, 'pid', False):
3942
return watch_pid(args)
@@ -64,6 +67,8 @@ def run_cmd(args):
6467
retcode = process.returncode
6568
if args.longer_than is not None and duration <= args.longer_than:
6669
return None, None
70+
if args.locked_only and not is_locked():
71+
return None, None
6772
if args.unfocused_only and is_focused():
6873
return None, None
6974
message = _result_message(args.command if not args.hide_command else None,
@@ -230,6 +235,12 @@ def default_sender(args):
230235
type=int,
231236
metavar='N',
232237
help="Only notify if the command runs longer than N seconds")
238+
done_parser.add_argument(
239+
'--locked-only',
240+
action='store_true',
241+
default=False,
242+
dest='locked_only',
243+
help='Only notify if the screen is locked')
233244
done_parser.add_argument(
234245
'-b',
235246
'--background-only',
@@ -283,6 +294,12 @@ def default_sender(args):
283294
type=int,
284295
metavar='N',
285296
help="Only notify if the command runs longer than N seconds")
297+
shell_integration_parser.add_argument(
298+
'--locked-only',
299+
action='store_true',
300+
default=False,
301+
dest='locked_only',
302+
help='Only notify if the screen is locked')
286303
shell_integration_parser.add_argument(
287304
'-f',
288305
'--foreground-too',

ntfy/screensaver.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from shlex import split
2+
from subprocess import check_output, check_call, CalledProcessError, PIPE
3+
4+
# some adapted from
5+
# https://github.com/mtorromeo/xdg-utils/blob/master/scripts/xdg-screensaver.in#L540
6+
7+
8+
def xscreensaver_detect():
9+
try:
10+
check_call(split('pgrep xscreensaver'), stdout=PIPE)
11+
except (CalledProcessError, OSError):
12+
return False
13+
else:
14+
return True
15+
16+
17+
def xscreensaver_is_locked():
18+
return 'screen locked' in check_output(split('xscreensaver-command -time'))
19+
20+
21+
def lightlocker_detect():
22+
try:
23+
check_call(split('pgrep light-locker'), stdout=PIPE)
24+
except (CalledProcessError, OSError):
25+
return False
26+
else:
27+
return True
28+
29+
30+
def lightlocker_is_active():
31+
return 'The screensaver is active' in check_output(
32+
split('light-locker-command -q'))
33+
34+
35+
def gnomescreensaver_detect():
36+
try:
37+
import dbus
38+
except ImportError:
39+
return False
40+
bus = dbus.SessionBus()
41+
dbus_obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
42+
dbus_iface = dbus.Interface(
43+
dbus_obj, dbus_interface='org.freedesktop.DBus')
44+
try:
45+
dbus_iface.GetNameOwner('org.gnome.ScreenSaver')
46+
except dbus.DBusException as e:
47+
if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NameHasNoOwner':
48+
return False
49+
else:
50+
raise e
51+
else:
52+
return True
53+
54+
55+
def gnomescreensaver_is_locked():
56+
import dbus
57+
bus = dbus.SessionBus()
58+
dbus_obj = bus.get_object('org.gnome.ScreenSaver',
59+
'/org/gnome/ScreenSaver')
60+
dbus_iface = dbus.Interface(
61+
dbus_obj, dbus_interface='org.gnome.ScreenSaver')
62+
return bool(dbus_iface.GetActive())
63+
64+
65+
def matescreensaver_detect():
66+
try:
67+
import dbus
68+
except ImportError:
69+
return False
70+
bus = dbus.SessionBus()
71+
dbus_obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
72+
dbus_iface = dbus.Interface(
73+
dbus_obj, dbus_interface='org.freedesktop.DBus')
74+
try:
75+
dbus_iface.GetNameOwner('org.mate.ScreenSaver')
76+
except dbus.DBusException as e:
77+
if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NameHasNoOwner':
78+
return False
79+
else:
80+
raise e
81+
else:
82+
return True
83+
84+
85+
def matescreensaver_is_locked():
86+
import dbus
87+
bus = dbus.SessionBus()
88+
dbus_obj = bus.get_object('org.mate.ScreenSaver', '/org/mate/ScreenSaver')
89+
dbus_iface = dbus.Interface(
90+
dbus_obj, dbus_interface='org.mate.ScreenSaver')
91+
return bool(dbus_iface.GetActive())
92+
93+
94+
def is_locked():
95+
if xscreensaver_detect():
96+
return xscreensaver_is_locked()
97+
if lightlocker_detect():
98+
return lightlocker_is_active()
99+
if gnomescreensaver_detect():
100+
return gnomescreensaver_is_locked()
101+
if matescreensaver_detect():
102+
return matescreensaver_is_locked()
103+
return True

tests/test_cli.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def test_default(self, mock_Popen):
2626
args.pid = None
2727
args.unfocused_only = False
2828
args.hide_command = False
29+
args.locked_only = False
2930
self.assertEqual(('"true" succeeded in 0:00 minutes', 0),
3031
run_cmd(args))
3132

@@ -39,6 +40,7 @@ def test_emoji(self, mock_Popen):
3940
args.no_emoji = False
4041
args.unfocused_only = False
4142
args.hide_command = False
43+
args.locked_only = False
4244
self.assertEqual(
4345
(':white_check_mark: "true" succeeded in 0:00 minutes', 0),
4446
run_cmd(args))
@@ -70,6 +72,7 @@ def test_failure(self, mock_Popen):
7072
args.pid = None
7173
args.unfocused_only = False
7274
args.hide_command = False
75+
args.locked_only = False
7376
self.assertEqual(('"false" failed (code 42) in 0:00 minutes', 42),
7477
run_cmd(args))
7578

@@ -82,6 +85,7 @@ def test_stdout(self, mock_Popen):
8285
args.pid = None
8386
args.unfocused_only = False
8487
args.hide_command = False
88+
args.locked_only = False
8589
# not actually used
8690
args.stdout = True
8791
args.stderr = False
@@ -97,6 +101,7 @@ def test_stderr(self, mock_Popen):
97101
args.pid = None
98102
args.unfocused_only = False
99103
args.hide_command = False
104+
args.locked_only = False
100105
# not actually used
101106
args.stdout = False
102107
args.stderr = True
@@ -112,6 +117,7 @@ def test_stdout_and_stderr(self, mock_Popen):
112117
args.pid = None
113118
args.unfocused_only = False
114119
args.hide_command = False
120+
args.locked_only = False
115121
# not actually used
116122
args.stdout = True
117123
args.stderr = True
@@ -128,6 +134,7 @@ def test_failure_stdout_and_stderr(self, mock_Popen):
128134
args.pid = None
129135
args.unfocused_only = False
130136
args.hide_command = False
137+
args.locked_only = False
131138
# not actually used
132139
args.stdout = True
133140
args.stderr = True
@@ -144,6 +151,7 @@ def test_hide_command(self, mock_Popen):
144151
args.pid = None
145152
args.unfocused_only = False
146153
args.hide_command = True
154+
args.locked_only = False
147155
self.assertEqual(('Your command succeeded in 0:00 minutes', 0),
148156
run_cmd(args))
149157

@@ -155,6 +163,7 @@ def test_formatter(self):
155163
args.longer_than = -1
156164
args.unfocused_only = False
157165
args.hide_command = False
166+
args.locked_only = False
158167
self.assertEqual(('"true" succeeded in 1:05 minutes', 0),
159168
run_cmd(args))
160169

@@ -166,6 +175,7 @@ def test_formatter_failure(self):
166175
args.longer_than = -1
167176
args.unfocused_only = False
168177
args.hide_command = False
178+
args.locked_only = False
169179
self.assertEqual(('"false" failed (code 1) in 0:10 minutes', 1),
170180
run_cmd(args))
171181

@@ -199,6 +209,7 @@ def test_watch_pid(self, mock_process):
199209
args = MagicMock()
200210
args.pid = 1
201211
args.unfocused_only = False
212+
args.locked_only = False
202213
self.assertEqual('PID[1]: "cmd" finished in 0:00 minutes',
203214
run_cmd(args)[0])
204215

0 commit comments

Comments
 (0)