from json import dumps as json_dumps
from datetime import datetime
from pathlib import Path
+from re import compile as re_compile
+from subprocess import run as subprocess_run
from time import sleep
METADATA = {'version': 1}
return int(stripped_text)
+def network_info(
+ ) -> tuple[str, ...]:
+ 'Produce one status block full_text per connected ethernet/wifi interface.'
+ def nmcli_fields(
+ args: tuple[str, ...]
+ ) -> tuple[tuple[str, ...], ...]:
+ # nmcli -t separates by ":" – except where escaped as "\:" …
+ return tuple(tuple(field.replace('\\:', ':').replace('\\\\', '\\')
+ for field in re_compile(r'(?<!\\):').split(line))
+ for line in subprocess_run(('nmcli', '-t', *args),
+ capture_output=True,
+ text=True,
+ check=True
+ ).stdout.splitlines())
+
+ def dev_info(
+ device: str,
+ dev_type: str
+ ) -> str:
+ def dev_ip(
+ ) -> str:
+ for family in ('IP4', 'IP6'):
+ addr_fields = nmcli_fields(
+ ('-g', f'{family}.ADDRESS', 'device', 'show', device))
+ if not addr_fields:
+ continue
+ for addr in (addr.split('/')[0]
+ for addr in addr_fields[0][0].split('|')):
+ # Every up interface connected or not has IPv6
+ # link-local addresses, only use if no better found.
+ if addr and (family == 'IP4'
+ or not addr.lower().startswith('fe80:')):
+ return addr
+ return '?'
+
+ info = f'{dev_ip()} {device}'
+ if dev_type == 'wifi':
+ for ssid, signal in (
+ row[1:] for row in nmcli_fields(
+ ('-f', 'ACTIVE,SSID,SIGNAL',
+ 'device', 'wifi', 'list', 'ifname', device,
+ '--rescan', 'no'))
+ if len(row) == 3
+ and row[0] == 'yes'):
+ info += f' {ssid} {signal}%'
+ return info
+ texts = tuple(dev_info(row[0], row[1])
+ for row in nmcli_fields(('-f', 'DEVICE,TYPE,STATE',
+ 'device', 'status'))
+ if row[2:3] == ('connected',)
+ and row[1] in ('ethernet', 'wifi'))
+ return texts if texts else ('offline', )
+
+
def battery_info(
) -> str:
'Produce battery charge, status, health status bar text.'
if __name__ == '__main__':
print(json_dumps(METADATA))
print('[', end='')
+ full_texts: list[str] = []
while True:
- print(json_dumps([
- {'full_text': battery_info()},
- {'full_text': (
+ full_texts.clear()
+ full_texts += list(network_info())
+ full_texts += [battery_info()]
+ full_texts += [
str(round(max(int_at_path(path)
for path in PATH_THERMAL.glob(GLOB_THERMAL)
) / 1000))
- + '°C')},
- {'full_text': str(datetime.now().isoformat(' ', 'seconds'))},
- ]), end=',\n', flush=True)
+ + '°C']
+ full_texts += [str(datetime.now().isoformat(' ', 'seconds'))]
+ print(json_dumps([{'full_text': text} for text in full_texts]),
+ end=',\n', flush=True)
sleep(1)