├── README.md ├── README.rst └── networkd-notify /README.md: -------------------------------------------------------------------------------- 1 | **WARNING:** This repository has been migrated to GitLab: 2 | 3 | https://gitlab.com/wavexx/networkd-notify 4 | 5 | This GitHub repository is no longer updated. 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | networkd-notify: desktop notification integration for networkd 2 | ============================================================== 3 | 4 | `networkd-notify` generates desktop notifications for `systemd-networkd` 5 | events, such as interface connection/disconnection. 6 | 7 | The generated notifications look like:: 8 | 9 | wlan0: configuring ... 10 | wlan0: online @ ESSID 11 | wlan0: disconnected 12 | ... 13 | 14 | 15 | Usage 16 | ----- 17 | 18 | Simply run ``networkd-notify`` at the start of your session: 19 | 20 | ./networkd-notify & 21 | 22 | You'll need ``networkd`` to be running of course, and you'll also need a 23 | notification daemon such as dunst_. 24 | 25 | 26 | Dependencies 27 | ------------ 28 | 29 | The following software is currently required for `networkd-notify`: 30 | 31 | - Python (3.x or 2.7) 32 | - PyGObject/PyGI (``python3-gi``) 33 | - Python D-Bus (``python3-dbus``) 34 | - ``wireless-tools`` 35 | 36 | On Debian/Ubuntu, you can install all the required dependencies with:: 37 | 38 | sudo apt-get install python3 python3-gi python3-dbus 39 | 40 | 41 | Authors and Copyright 42 | --------------------- 43 | 44 | | `networkd-notify` is distributed under GPLv3+ (see COPYING) WITHOUT ANY WARRANTY. 45 | | Copyright(c) 2016-2017 by wave++ "Yuri D'Elia" . 46 | 47 | .. _dunst: http://www.knopwob.org/dunst/ 48 | -------------------------------------------------------------------------------- /networkd-notify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # networkd-notify: desktop notification integration for systemd-networkd 3 | # Copyright(c) 2016-2017 by wave++ "Yuri D'Elia" 4 | # Distributed under GPLv3+ (see COPYING) WITHOUT ANY WARRANTY. 5 | from __future__ import print_function, division, generators, unicode_literals 6 | 7 | import argparse 8 | import subprocess 9 | import sys 10 | import os 11 | 12 | import gi 13 | from gi.repository import GLib as glib 14 | 15 | import dbus 16 | import dbus.mainloop.glib 17 | 18 | 19 | # Constants 20 | NETWORKCTL = ['/usr/bin/networkctl', '/bin/networkctl'] 21 | IWCONFIG = ['/usr/bin/iwconfig', '/sbin/iwconfig'] 22 | APP_NAME = 'networkd' 23 | 24 | STATE_IGN = {'carrier', 'degraded'} 25 | STATE_MAP = {'off': 'offline', 26 | 'no-carrier': 'disconnected', 27 | 'dormant': 'configuring ...', 28 | 'routable': 'online'} 29 | 30 | # Nifty globals 31 | IFACE_MAP = {} 32 | NOTIFY_IF = None 33 | 34 | 35 | def refresh_notify_if(bus): 36 | global NOTIFY_IF 37 | NOTIFY_IF = dbus.Interface(bus.get_object('org.freedesktop.Notifications', 38 | '/org/freedesktop/Notifications'), 39 | 'org.freedesktop.Notifications') 40 | 41 | 42 | def notify(title, text, time=1000): 43 | if NOTIFY_IF: 44 | NOTIFY_IF.Notify(APP_NAME, 0, '', title, text, '', '', time) 45 | 46 | 47 | def resolve_path(path_list): 48 | for path in path_list: 49 | if os.path.exists(path): 50 | return path 51 | return None 52 | 53 | 54 | def update_iface_map(): 55 | out = subprocess.check_output([NETWORKCTL, 'list', '--no-pager', '--no-legend']) 56 | IFACE_MAP.clear() 57 | for line in out.split(b'\n')[:-1]: 58 | fields = line.decode('ascii').split() 59 | IFACE_MAP[int(fields[0])] = fields[1] 60 | 61 | 62 | def get_iface_data(iface): 63 | out = subprocess.check_output([NETWORKCTL, 'status', '--no-pager', '--no-legend', '--', iface]) 64 | data = {} 65 | oldk = None 66 | for line in out.split(b'\n')[1:-1]: 67 | line = line.decode('ascii') 68 | k = line[:16].strip() or oldk 69 | oldk = k 70 | v = line[18:].strip() 71 | if k not in data: 72 | data[k] = v 73 | elif type(data[k]) == list: 74 | data[k].append(v) 75 | else: 76 | data[k] = [data[k], v] 77 | return data 78 | 79 | 80 | def unquote(buf, char='\\'): 81 | idx = 0 82 | while True: 83 | idx = buf.find(char, idx) 84 | if idx < 0: break 85 | buf = buf[:idx] + buf[idx+1:] 86 | idx += 1 87 | return buf 88 | 89 | 90 | def get_wlan_essid(iface): 91 | out = subprocess.check_output([IWCONFIG, '--', iface]) 92 | line = out.split(b'\n')[0].decode('ascii') 93 | essid = line[line.find('ESSID:')+7:-3] 94 | return unquote(essid) 95 | 96 | 97 | def property_changed(typ, data, _, path): 98 | if typ != 'org.freedesktop.network1.Link': 99 | return 100 | if not path.startswith('/org/freedesktop/network1/link/_'): 101 | return 102 | if 'OperationalState' not in data: 103 | return 104 | state = data['OperationalState'] 105 | if state in STATE_IGN: 106 | return 107 | 108 | # http://thread.gmane.org/gmane.comp.sysutils.systemd.devel/36460 109 | idx = path[32:] 110 | idx = int(chr(int(idx[:2], 16)) + idx[2:]) 111 | 112 | if idx not in IFACE_MAP: 113 | update_iface_map() 114 | iface = IFACE_MAP[idx] 115 | 116 | hstate = STATE_MAP.get(state, state) 117 | if state != 'routable': 118 | notify(iface, hstate) 119 | else: 120 | data = get_iface_data(iface) 121 | 122 | # append ESSID to the online state 123 | if data['Type'] == 'wlan': 124 | data['ESSID'] = get_wlan_essid(iface) 125 | if 'ESSID' in data and data['ESSID']: 126 | hstate += ' @ ' + data['ESSID'] 127 | 128 | # filter out uninteresting addresses 129 | addrs = [] 130 | if type(data['Address']) != list: 131 | addrs = [data['Address']] 132 | else: 133 | for addr in data['Address']: 134 | if addr.startswith('127.') or \ 135 | addr.startswith('fe80:'): 136 | continue 137 | addrs.append(addr) 138 | 139 | notify(iface, '{}\n{}'.format(hstate, ', '.join(addrs)), 3000) 140 | 141 | 142 | def name_owner_changed(name, old_owner, new_owner, path): 143 | if name == 'org.freedesktop.Notifications': 144 | refresh_notify_if(dbus.SessionBus()) 145 | 146 | 147 | if __name__ == '__main__': 148 | ap = argparse.ArgumentParser(description='networkd notification daemon') 149 | args = ap.parse_args() 150 | 151 | NETWORKCTL = resolve_path(NETWORKCTL) 152 | if NETWORKCTL is None: 153 | sys.exit("networkctl binary not found") 154 | 155 | IWCONFIG = resolve_path(IWCONFIG) 156 | if IWCONFIG is None: 157 | sys.exit("iwconfig binary not found") 158 | 159 | # interfaces never change at runtime, right?? 160 | update_iface_map() 161 | 162 | # listen on system-wide bus for networkd events 163 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 164 | bus = dbus.SystemBus() 165 | bus.add_signal_receiver(property_changed, 166 | bus_name='org.freedesktop.network1', 167 | signal_name='PropertiesChanged', 168 | path_keyword='path') 169 | 170 | # register on session bus for notification daemon changes 171 | sessionbus = dbus.SessionBus() 172 | sessionbus.add_signal_receiver(name_owner_changed, 173 | bus_name='org.freedesktop.DBus', 174 | signal_name='NameOwnerChanged', 175 | path_keyword='path') 176 | refresh_notify_if(sessionbus) 177 | 178 | # main loop 179 | mainloop = glib.MainLoop() 180 | mainloop.run() 181 | --------------------------------------------------------------------------------