home · contact · privacy
To sway status bar add network status. master
authorPlom Heller <plom@plomlompom.com>
Tue, 15 Sep 2026 16:43:51 +0000 (18:43 +0200)
committerPlom Heller <plom@plomlompom.com>
Tue, 15 Sep 2026 16:43:51 +0000 (18:43 +0200)
home/user/.config/sway/status.py

index 295b03f49391c4e1a6f5aaafadaf1d406dd1dc1a..7abe81f1520a324ec8516c46c8683a07b276a27a 100755 (executable)
@@ -3,6 +3,8 @@
 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}
@@ -28,6 +30,60 @@ def int_at_path(
     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.'
@@ -51,14 +107,17 @@ def battery_info(
 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)