#!/usr/bin/env python3 # # Usage: # ./nohang_notify_helper "title" "body" from sys import argv, stdout from os import listdir, path from subprocess import Popen, TimeoutExpired if len(argv) < 2 or argv[1] == "-h" or argv[1] == "--help": print('Usage: ./nohang_notify_helper "title" "body"') exit(1) wait_time = 15 display_env = 'DISPLAY=' dbus_env = 'DBUS_SESSION_BUS_ADDRESS=' user_env = 'USER=' def rline1(path): """read 1st line from path.""" with open(path) as f: for line in f: return line def re_pid_environ(pid): """ read environ of 1 process returns tuple with USER, DBUS, DISPLAY like follow: ('user', 'DISPLAY=:0', 'DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus') returns None if these vars is not in /proc/[pid]/environ """ try: env = str(rline1('/proc/' + pid + '/environ')) if display_env in env and dbus_env in env and user_env in env: env_list = env.split('\x00') # iterating over a list of process environment variables for i in env_list: if i.startswith(user_env): user = i continue if i.startswith(display_env): display = i[:10] continue if i.startswith(dbus_env): dbus = i continue if i.startswith('HOME='): # exclude Display Manager's user if i.startswith('HOME=/var'): return None try: env = user.partition('USER=')[2], display, dbus except UnboundLocalError: # print('notify helper: UnboundLocalError') return None return env except FileNotFoundError: # print('notify helper: FileNotFoundError') return None except ProcessLookupError: # print('notify helper: ProcessLookupError') return None def root_notify_env(): """return set(user, display, dbus)""" unsorted_envs_list = [] # iterates over processes, find processes with suitable env for pid in listdir('/proc'): if path.exists('/proc/' + pid + '/exe') is True: one_env = re_pid_environ(pid) unsorted_envs_list.append(one_env) env = set(unsorted_envs_list) env.discard(None) # deduplicate dbus new_env = [] end = [] for i in env: key = i[0] + i[1] if key not in end: end.append(key) new_env.append(i) else: continue return new_env list_with_envs = root_notify_env() list_len = len(list_with_envs) # if somebody logged in with GUI if list_len > 0: for i in list_with_envs: print('Send GUI notification:', argv[1], argv[2], i) stdout.flush() # iterating over logged-in users for i in list_with_envs: username, display_env, dbus_env = i[0], i[1], i[2] display_tuple = display_env.partition('=') dbus_tuple = dbus_env.partition('=') display_value = display_tuple[2] dbus_value = dbus_tuple[2] with Popen([ 'sudo', '-u', username, 'env', 'DISPLAY=' + display_value, 'DBUS_SESSION_BUS_ADDRESS=' + dbus_value, 'notify-send', '--icon=dialog-warning', argv[1], argv[2] ]) as proc: try: proc.wait(timeout=wait_time) except TimeoutExpired: proc.kill() print('TimeoutExpired: notify user:' + username) else: print( 'Not send GUI notification: [', argv[1], argv[2], ']. Nobody logged-in with GUI. Nothing to do.')