From 1ae7d6b447eeee6d576bc0910e468a59a9f011c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffrey=20=E2=80=9CFrogeye=E2=80=9D=20Preud=27homme?= Date: Thu, 26 Sep 2024 23:04:11 +0200 Subject: [PATCH] frobarng: NetworkProvider done --- hm/desktop/frobar/.dev/new.py | 98 +++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/hm/desktop/frobar/.dev/new.py b/hm/desktop/frobar/.dev/new.py index 36ee2ec..65e6cbe 100644 --- a/hm/desktop/frobar/.dev/new.py +++ b/hm/desktop/frobar/.dev/new.py @@ -3,9 +3,11 @@ import asyncio import datetime import enum +import ipaddress import logging import random import signal +import socket import typing import coloredlogs @@ -22,6 +24,21 @@ C = typing.TypeVar("C", bound="ComposableText") Sortable = str | int +def humanSize(numi: int) -> str: + """ + Returns a string of width 3+3 + """ + num = float(numi) + for unit in ("B ", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"): + if abs(num) < 1000: + if num >= 10: + return "{:3d}{}".format(int(num), unit) + else: + return "{:.1f}{}".format(num, unit) + num /= 1024 + return "{:d}YiB".format(numi) + + class ComposableText(typing.Generic[P, C]): def __init__( @@ -267,6 +284,7 @@ class Bar(ComposableText): super().__init__() self.parent: None self.children: typing.MutableSequence[Screen] + self.longRunningTasks: list[asyncio.Task] = list() self.refresh = asyncio.Event() self.taskGroup = asyncio.TaskGroup() @@ -280,6 +298,10 @@ class Bar(ComposableText): continue Screen(parent=self, output=output.name) + def addLongRunningTask(self, coro: typing.Coroutine) -> None: + task = self.taskGroup.create_task(coro) + self.longRunningTasks.append(task) + async def run(self) -> None: cmd = [ "lemonbar", @@ -299,7 +321,6 @@ class Bar(ComposableText): await self.refresh.wait() self.refresh.clear() markup = self.getMarkup() - # log.debug(markup) proc.stdin.write(markup.encode()) async def actionHandler() -> None: @@ -310,21 +331,15 @@ class Bar(ComposableText): callback = self.actions[command] callback() - longRunningTasks = list() - - def addLongRunningTask(coro: typing.Coroutine) -> None: - task = self.taskGroup.create_task(coro) - longRunningTasks.append(task) - async with self.taskGroup: - addLongRunningTask(refresher()) - addLongRunningTask(actionHandler()) + self.addLongRunningTask(refresher()) + self.addLongRunningTask(actionHandler()) for provider in self.providers: - addLongRunningTask(provider.run()) + self.addLongRunningTask(provider.run()) def exit() -> None: log.info("Terminating") - for task in longRunningTasks: + for task in self.longRunningTasks: task.cancel() loop = asyncio.get_event_loop() @@ -563,11 +578,50 @@ class NetworkProviderSection(StatefulSection): self.numberStates = 5 if self.wifi else 4 self.state = 1 if self.wifi else 0 + async def updateOnStateChange(self) -> None: + while True: + await self.stateChanged.wait() + await self.provider.updateIface(self.iface) + async def getText(self) -> str | None: if self.ignore or not self.provider.if_stats[self.iface].isup: return None - text = self.icon + + state = self.state + (0 if self.wifi else 1) # SSID + if self.wifi and state >= 1: + cmd = ["iwgetid", self.iface, "--raw"] + proc = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE + ) + stdout, stderr = await proc.communicate() + text += f" {stdout.decode().strip()}" + + if state >= 2: # Address + for address in self.provider.if_addrs[self.iface]: + if address.family == socket.AF_INET: + net = ipaddress.IPv4Network( + (address.address, address.netmask), strict=False + ) + text += f" {net.with_prefixlen}" + break + + if state >= 3: # Speed + prevRecv = self.provider.prev_io_counters[self.iface].bytes_recv + recv = self.provider.io_counters[self.iface].bytes_recv + prevSent = self.provider.prev_io_counters[self.iface].bytes_sent + sent = self.provider.io_counters[self.iface].bytes_sent + recvDiff = recv - prevRecv + sentDiff = sent - prevSent + dt = self.provider.time - self.provider.prev_time + recvDiff /= dt + sentDiff /= dt + + text += f" ↓{humanSize(recvDiff)}↑{humanSize(sentDiff)}" + + if state >= 4: # Counter + text += f" ⇓{humanSize(recv)}⇑{humanSize(sent)}" + return text @@ -582,12 +636,25 @@ class NetworkProvider(MirrorProvider): async def run(self) -> None: await super().run() - while True: - # if_addrs: dict[str, list[psutil._common.snicaddr]] = psutil.net_if_addrs() - # io_counters: dict[str, psutil._common.snetio] = psutil.net_io_counters(pernic=True) + loop = asyncio.get_running_loop() + self.time = loop.time() + self.io_counters = psutil.net_io_counters(pernic=True) + while True: + # Separate TaskGroup in case it takes longer than one second, + # it doesn't fill the main TaskGroup async with asyncio.TaskGroup() as tg: + self.prev_io_counters = self.io_counters + self.prev_time = self.time + # On-demand would only benefit if_addrs: + # stats are used to determine display, + # and we want to keep previous io_counters + # so displaying stats is ~instant. + self.time = loop.time() self.if_stats = psutil.net_if_stats() + self.if_addrs = psutil.net_if_addrs() + self.io_counters = psutil.net_io_counters(pernic=True) + for iface in self.if_stats: if iface not in self.sections: section = NetworkProviderSection( @@ -595,6 +662,7 @@ class NetworkProvider(MirrorProvider): ) self.sections[iface] = section + self.module.bar.addLongRunningTask(section.updateOnStateChange()) tg.create_task(self.updateIface(iface)) for iface, section in self.sections.items(): if iface not in self.if_stats: