-
Notifications
You must be signed in to change notification settings - Fork 34
/
exec
executable file
·158 lines (141 loc) · 5.79 KB
/
exec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python
import os, sys, traceback, subprocess as sp
def get_dbus_notify_func(**defaults):
import ctypes as ct
class sd_bus(ct.Structure): pass
class sd_bus_error(ct.Structure):
_fields_ = [('name', ct.c_char_p), ('message', ct.c_char_p), ('need_free', ct.c_int)]
class sd_bus_msg(ct.Structure): pass
lib = ct.CDLL('libsystemd.so')
def run(call, *args, sig=None, check=True):
func = getattr(lib, call)
if sig: func.argtypes = sig
res = func(*args)
if check and res < 0: raise OSError(-res, os.strerror(-res))
return res
bus, error, reply = (
ct.POINTER(sd_bus)(), sd_bus_error(), ct.POINTER(sd_bus_msg)() )
kws, defaults = defaults, dict(
app='', replaces_id=0, icon='',
summary='', body='', actions=None, hints=None, timeout=-1 )
for k in defaults:
if k in kws: defaults[k] = kws.pop(k)
assert not kws, kws
bb = lambda v: v.encode() if isinstance(v, str) else v
def encode_array(k, v):
if not v: sig, args = [ct.c_void_p], [None]
elif k == 'actions':
sig, args = [ct.c_int, [ct.c_char_p] * len(v)], [len(v), *map(bb, v)]
elif k == 'hints':
sig, args = [ct.c_int], [len(v)]
for ak, av in v.items():
sig.extend([ct.c_char_p, ct.c_char_p]) # key, type
args.append(bb(ak))
if isinstance(av, (str, bytes)):
av_sig, av_args = [ct.c_char_p], [b's', bb(av)]
elif isinstance(av, int): av_sig, av_args = [ct.c_int32], [b'i', av]
else: av_sig, av_args = av
args.extend(av_args)
sig.extend(av_sig)
else: raise ValueError(k)
return sig, args
def notify_func(
summary=None, body=None, app=None, icon=None,
replaces_id=None, actions=None, hints=None, timeout=None ):
args, kws, sig_arrays = list(), locals(), list()
for k, default in defaults.items():
v = kws.get(k)
if v is None: v = default
if k in ['actions', 'hints']:
arr_sig, arr_args = encode_array(k, v)
sig_arrays.extend(arr_sig)
args.extend(arr_args)
else: args.append(bb(v))
run( 'sd_bus_open_user', ct.byref(bus),
sig=[ct.POINTER(ct.POINTER(sd_bus))] )
try:
run( 'sd_bus_call_method',
bus,
b'org.freedesktop.Notifications', # dst
b'/org/freedesktop/Notifications', # path
b'org.freedesktop.Notifications', # iface
b'Notify', # method
ct.byref(error),
ct.byref(reply),
b'susssasa{sv}i', *args,
sig=[
ct.POINTER(sd_bus),
ct.c_char_p, ct.c_char_p, ct.c_char_p, ct.c_char_p,
ct.POINTER(sd_bus_error),
ct.POINTER(ct.POINTER(sd_bus_msg)),
ct.c_char_p,
ct.c_char_p, ct.c_uint32,
ct.c_char_p, ct.c_char_p, ct.c_char_p,
*sig_arrays, ct.c_int32 ] )
note_id = ct.c_uint32()
n = run( 'sd_bus_message_read', reply, b'u', ct.byref(note_id),
sig=[ct.POINTER(sd_bus_msg), ct.c_char_p, ct.POINTER(ct.c_uint32)] )
assert n > 0, n
finally: run('sd_bus_flush_close_unref', bus, check=False)
return note_id.value
return notify_func
def main():
if os.environ.get('NOTIFICATION_EXEC_CHECK'):
print('Detected recursive call in notification.exec, aborting', file=sys.stderr)
sys.exit(1)
opts, cmd = list(), sys.argv[1:]
if '--' in sys.argv:
pos = sys.argv.index('--')
opts, cmd = sys.argv[1:pos], sys.argv[pos+1:]
# In case this binary is symlinked to something like "notify_X.wrapper" - just run notify_X
# Only use that if names are in "notify.*" form to avoid accidental recursion
cmd_base = os.path.basename(sys.argv[0])
if cmd_base.startswith('notify.') and cmd_base.rsplit('.')[-1] != 'exec':
if cmd_base.endswith('.wrapper'): cmd_base = cmd_base[:-8]
cmd = [cmd_base] + cmd
import argparse
parser = argparse.ArgumentParser(
usage='%(prog)s: [ options... -- ] command [ arguments... ]',
description='Wrapper for command execution results notification.')
parser.add_argument('-e', '--exit-code-only', action='store_true',
help='Issue notification only if exit code not equals zero, despite stderr.')
parser.add_argument('-v', '--notify-on-success', action='store_true',
help='Issue notification upon successful execution as well.')
parser.add_argument('-d', '--dump', action='store_true',
help='Include stdou/stderr for all notifications.')
parser.add_argument('-i', '--notify-icon', metavar='icon-name',
help='Icon name to pass to notification daemon.')
parser.add_argument('-t', '--timeout',
type=float, metavar='seconds', default=10,
help='Timeout for specified command to finish'
' (default=%(default)ss). It does not get killed afterwards!')
parser.add_argument('--err-note-timeout',
type=float, metavar='seconds', default=0,
help='Notification timeout setting for any kind of errors. Default: %(default)ss')
opts = parser.parse_args(opts)
notify = lambda *a,**kw: get_dbus_notify_func(icon=opts.notify_icon)(*a, **kw)
proc_name = cmd[0] if cmd else '(generic error)'
try:
env = os.environ.copy()
env['NOTIFICATION_EXEC_CHECK'] = 't' # to block recursion
proc = sp.run(cmd, env=env, timeout=opts.timeout, capture_output=True)
code, cmd = proc.returncode, ' '.join(cmd)
stdout, stderr = ( # indented and compressed for notification boxes
'\n '.join(s.decode(errors='replace').splitlines())
for s in [proc.stdout, proc.stderr] )
except Exception as err: # likely OSError or TimeoutExpired
notify( f'{proc_name}: failed to run command',
f'Error: {err}\n\n{traceback.format_exc().strip()}',
timeout=opts.err_note_timeout, hints=dict(urgency=2) )
raise
if code != 0 or (not opts.exit_code_only and proc.stderr):
notify( f'{proc_name}: command finished with errors',
f'Command (err={code}): {cmd}\n'
f'\nStdout:\n {stdout}\nStderr:\n {stderr}',
timeout=opts.err_note_timeout, hints=dict(urgency=2) )
return code
elif opts.notify_on_success:
notify( f'{proc_name}: command was executed successfully',
'Cmdline: {}{}'.format( cmd,
f'\nStdout:\n {stdout}\nStderr:\n {stderr}' if opts.dump else '' ))
if __name__ == '__main__': sys.exit(main())