Compare commits

...

4 commits

Author SHA1 Message Date
Geoffrey Frogeye 812e5acf6c
Update
Cups or something (I do print from time to time ><")
2024-10-01 14:24:16 +02:00
Geoffrey Frogeye 768b38b87f
Merge curacao and cranberry work 2024-10-01 14:22:16 +02:00
Geoffrey Frogeye 6644e85c30
frobarng: Some refactor 2024-10-01 14:12:24 +02:00
Geoffrey Frogeye 1ae7d6b447
frobarng: NetworkProvider done 2024-09-27 23:06:06 +02:00
2 changed files with 196 additions and 95 deletions

View file

@ -142,11 +142,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1724895876, "lastModified": 1727531434,
"narHash": "sha256-GSqAwa00+vRuHbq9O/yRv7Ov7W/pcMLis3HmeHv8a+Q=", "narHash": "sha256-b+GBgCWd2N6pkiTkRZaMFOPztPO4IVTaclYPrQl2uLk=",
"owner": "nix-community", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "511388d837178979de66d14ca4a2ebd5f7991cd3", "rev": "b709e1cc33fcde71c7db43850a55ebe6449d0959",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -208,11 +208,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1725024810, "lastModified": 1725234343,
"narHash": "sha256-ODYRm8zHfLTH3soTFWE452ydPYz2iTvr9T8ftDMUQ3E=", "narHash": "sha256-+ebgonl3NbiKD2UD0x4BszCZQ6sTfL4xioaM49o5B3Y=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "af510d4a62d071ea13925ce41c95e3dec816c01d", "rev": "567b938d64d4b4112ee253b9274472dc3a346eb6",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -226,11 +226,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1710146030, "lastModified": 1726560853,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -328,11 +328,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1720042825, "lastModified": 1726989464,
"narHash": "sha256-A0vrUB6x82/jvf17qPCpxaM+ulJnD8YZwH9Ci0BsAzE=", "narHash": "sha256-Vl+WVTJwutXkimwGprnEtXc/s/s8sMuXzqXaspIGlwM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "e1391fb22e18a36f57e6999c7a9f966dc80ac073", "rev": "2f23fa308a7c067e52dfcc30a0758f47043ec176",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -392,11 +392,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1724994893, "lastModified": 1725189302,
"narHash": "sha256-yutISDGg6HUaZqCaa54EcsfTwew3vhNtt/FNXBBo44g=", "narHash": "sha256-IhXok/kwQqtusPsoguQLCHA+h6gKvgdCrkhIaN+kByA=",
"owner": "lnl7", "owner": "lnl7",
"repo": "nix-darwin", "repo": "nix-darwin",
"rev": "c8d3157d1f768e382de5526bb38e74d2245cad04", "rev": "7c4b53a7d9f3a3df902b3fddf2ae245ef20ebcda",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -445,11 +445,11 @@
"nmd": "nmd" "nmd": "nmd"
}, },
"locked": { "locked": {
"lastModified": 1721670745, "lastModified": 1725658585,
"narHash": "sha256-rjTQ14dqQ90EaHQy4g/mGylrJ1aZJYc3wCXc4A3GHJg=", "narHash": "sha256-P29z4Gt89n5ps1U7+qmIrj0BuRXGZQSIaOe2+tsPgfw=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nix-on-droid", "repo": "nix-on-droid",
"rev": "248cc0806120fac9214f503dee0eaf0f47740dd0", "rev": "5d88ff2519e4952f8d22472b52c531bb5f1635fc",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -460,11 +460,11 @@
}, },
"nixos-hardware": { "nixos-hardware": {
"locked": { "locked": {
"lastModified": 1724878143, "lastModified": 1727665282,
"narHash": "sha256-UjpKo92iZ25M05kgSOw/Ti6VZwpgdlOa73zHj8OcaDk=", "narHash": "sha256-oKtfbQB1MBypqIyzkC8QCQcVGOa1soaXaGgcBIoh14o=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "95c3dfe6ef2e96ddc1ccdd7194e3cda02ca9a8ef", "rev": "11c43c830e533dad1be527ecce379fcf994fbbb5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -474,11 +474,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1725001927, "lastModified": 1727672256,
"narHash": "sha256-eV+63gK0Mp7ygCR0Oy4yIYSNcum2VQwnZamHxYTNi+M=", "narHash": "sha256-9/79hjQc9+xyH+QxeMcRsA6hDyw6Z9Eo1/oxjvwirLk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "6e99f2a27d600612004fbd2c3282d614bfee6421", "rev": "1719f27dd95fd4206afb9cec9f415b539978827e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -533,11 +533,11 @@
"treefmt-nix": "treefmt-nix" "treefmt-nix": "treefmt-nix"
}, },
"locked": { "locked": {
"lastModified": 1725107436, "lastModified": 1725350106,
"narHash": "sha256-84Rz+GeFifzaJHnyMlkz4TdnrWxQdryTQKU3XVFQR1Q=", "narHash": "sha256-TaMMlI2KPJ3wCyxJk6AShOLhNuTeabHCnvYRkLBlEFs=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixvim", "repo": "nixvim",
"rev": "7cae6d0202140ec322e18b65b63d03b423d595f7", "rev": "0f2c31e6a57a83ed4e6fa3adc76749620231055d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -587,11 +587,11 @@
}, },
"nur": { "nur": {
"locked": { "locked": {
"lastModified": 1725177927, "lastModified": 1727779756,
"narHash": "sha256-l6Wu5dnme8LpkdLYe+/WxKzK5Pgi96Iiuge9wfnzb4E=", "narHash": "sha256-KLeROOi6VYct8lP1TIAPABlKOsisecKZLOozD5W54PE=",
"owner": "nix-community", "owner": "nix-community",
"repo": "NUR", "repo": "NUR",
"rev": "90b3a926d1c4d52c2d3851702be75cbde4e13a0f", "rev": "01c22fb0d5c07a4a2f9ffc4b132cfa4ee4e1cce2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -699,11 +699,11 @@
}, },
"unixpkgs": { "unixpkgs": {
"locked": { "locked": {
"lastModified": 1725183711, "lastModified": 1727785308,
"narHash": "sha256-gkjg8FfjL92azt3gzZUm1+v+U4y+wbQE630uIf4Aybo=", "narHash": "sha256-t2PqANZPqbtiqOjiQFaHEXuMeG1laa1g+4OcQuo+MjE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "a2c345850e5e1d96c62e7fa8ca6c9d77ebad1c37", "rev": "1c9c0eabb80d35c24eb4e7968c9ee15641a3e0fd",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -3,9 +3,11 @@
import asyncio import asyncio
import datetime import datetime
import enum import enum
import ipaddress
import logging import logging
import random import random
import signal import signal
import socket
import typing import typing
import coloredlogs import coloredlogs
@ -22,6 +24,21 @@ C = typing.TypeVar("C", bound="ComposableText")
Sortable = str | int 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]): class ComposableText(typing.Generic[P, C]):
def __init__( def __init__(
@ -267,6 +284,7 @@ class Bar(ComposableText):
super().__init__() super().__init__()
self.parent: None self.parent: None
self.children: typing.MutableSequence[Screen] self.children: typing.MutableSequence[Screen]
self.longRunningTasks: list[asyncio.Task] = list()
self.refresh = asyncio.Event() self.refresh = asyncio.Event()
self.taskGroup = asyncio.TaskGroup() self.taskGroup = asyncio.TaskGroup()
@ -274,12 +292,18 @@ class Bar(ComposableText):
self.actionIndex = 0 self.actionIndex = 0
self.actions: dict[str, typing.Callable] = dict() self.actions: dict[str, typing.Callable] = dict()
self.periodicProviderTask: typing.Coroutine | None = None
i3 = i3ipc.Connection() i3 = i3ipc.Connection()
for output in i3.get_outputs(): for output in i3.get_outputs():
if not output.active: if not output.active:
continue continue
Screen(parent=self, output=output.name) 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: async def run(self) -> None:
cmd = [ cmd = [
"lemonbar", "lemonbar",
@ -299,7 +323,6 @@ class Bar(ComposableText):
await self.refresh.wait() await self.refresh.wait()
self.refresh.clear() self.refresh.clear()
markup = self.getMarkup() markup = self.getMarkup()
# log.debug(markup)
proc.stdin.write(markup.encode()) proc.stdin.write(markup.encode())
async def actionHandler() -> None: async def actionHandler() -> None:
@ -310,21 +333,15 @@ class Bar(ComposableText):
callback = self.actions[command] callback = self.actions[command]
callback() callback()
longRunningTasks = list()
def addLongRunningTask(coro: typing.Coroutine) -> None:
task = self.taskGroup.create_task(coro)
longRunningTasks.append(task)
async with self.taskGroup: async with self.taskGroup:
addLongRunningTask(refresher()) self.addLongRunningTask(refresher())
addLongRunningTask(actionHandler()) self.addLongRunningTask(actionHandler())
for provider in self.providers: for provider in self.providers:
addLongRunningTask(provider.run()) self.addLongRunningTask(provider.run())
def exit() -> None: def exit() -> None:
log.info("Terminating") log.info("Terminating")
for task in longRunningTasks: for task in self.longRunningTasks:
task.cancel() task.cancel()
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -367,7 +384,8 @@ class Provider:
self.modules: list[Module] = list() self.modules: list[Module] = list()
async def run(self) -> None: async def run(self) -> None:
raise NotImplementedError() # Not a NotImplementedError, otherwise can't combine all classes
pass
class MirrorProvider(Provider): class MirrorProvider(Provider):
@ -376,17 +394,16 @@ class MirrorProvider(Provider):
self.module: Module self.module: Module
async def run(self) -> None: async def run(self) -> None:
await super().run()
self.module = self.modules[0] self.module = self.modules[0]
for module in self.modules[1:]: for module in self.modules[1:]:
module.mirror(self.module) module.mirror(self.module)
class SingleSectionProvider(MirrorProvider): class SingleSectionProvider(MirrorProvider):
SECTION_CLASS = Section
async def run(self) -> None: async def run(self) -> None:
await super().run() await super().run()
self.section = self.SECTION_CLASS(parent=self.module) self.section = Section(parent=self.module)
class StaticProvider(SingleSectionProvider): class StaticProvider(SingleSectionProvider):
@ -404,7 +421,6 @@ class StatefulSection(Section):
super().__init__(parent=parent, sortKey=sortKey) super().__init__(parent=parent, sortKey=sortKey)
self.state = 0 self.state = 0
self.numberStates: int self.numberStates: int
self.stateChanged = asyncio.Event()
self.setAction(Button.CLICK_LEFT, self.incrementState) self.setAction(Button.CLICK_LEFT, self.incrementState)
self.setAction(Button.CLICK_RIGHT, self.decrementState) self.setAction(Button.CLICK_RIGHT, self.decrementState)
@ -417,14 +433,61 @@ class StatefulSection(Section):
self.state -= 1 self.state -= 1
self.changeState() self.changeState()
def setChangedState(self, callback: typing.Callable) -> None:
self.callback = callback
def changeState(self) -> None: def changeState(self) -> None:
self.state %= self.numberStates self.state %= self.numberStates
self.stateChanged.set() self.bar.taskGroup.create_task(self.callback())
self.stateChanged.clear()
class StatefulProvider(SingleSectionProvider): class SingleStatefulSectionProvider(MirrorProvider):
SECTION_CLASS = StatefulSection async def run(self) -> None:
await super().run()
self.section = StatefulSection(parent=self.module)
class PeriodicProvider(Provider):
async def init(self) -> None:
pass
async def loop(self) -> None:
raise NotImplementedError()
@classmethod
async def task(cls, bar: Bar) -> None:
providers = list()
for provider in bar.providers:
if isinstance(provider, PeriodicProvider):
providers.append(provider)
await provider.init()
while True:
# TODO Block bar update during the periodic update of the loops
loops = [provider.loop() for provider in providers]
asyncio.gather(*loops)
now = datetime.datetime.now()
# Hardcoded to 1 second... not sure if we want some more than that,
# and if the logic to check if a task should run would be a win
# compared to the task itself
remaining = 1 - now.microsecond / 1000000
await asyncio.sleep(remaining)
async def run(self) -> None:
await super().run()
for module in self.modules:
bar = module.getFirstParentOfType(Bar)
assert bar
if not bar.periodicProviderTask:
bar.periodicProviderTask = PeriodicProvider.task(bar)
bar.addLongRunningTask(bar.periodicProviderTask)
class PeriodicStatefulProvider(SingleStatefulSectionProvider, PeriodicProvider):
async def run(self) -> None:
await super().run()
self.section.setChangedState(self.loop)
# Providers # Providers
@ -455,8 +518,8 @@ class I3WindowTitleProvider(SingleSectionProvider):
class I3WorkspacesProvider(Provider): class I3WorkspacesProvider(Provider):
# FIXME Custom names # TODO Custom names
# FIXME Colors # TODO Colors
async def updateWorkspaces(self, i3: i3ipc.Connection) -> None: async def updateWorkspaces(self, i3: i3ipc.Connection) -> None:
""" """
@ -563,64 +626,102 @@ class NetworkProviderSection(StatefulSection):
self.numberStates = 5 if self.wifi else 4 self.numberStates = 5 if self.wifi else 4
self.state = 1 if self.wifi else 0 self.state = 1 if self.wifi else 0
async def getText(self) -> str | None: self.setChangedState(self.update)
async def update(self) -> None:
if self.ignore or not self.provider.if_stats[self.iface].isup: if self.ignore or not self.provider.if_stats[self.iface].isup:
return None self.setText(None)
return
text = self.icon text = self.icon
return text
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
dt = self.provider.time - self.provider.prev_time
recvDiff = (recv - prevRecv) / dt
sentDiff = (sent - prevSent) / dt
text += f"{humanSize(recvDiff)}{humanSize(sentDiff)}"
if state >= 4: # Counter
text += f"{humanSize(recv)}{humanSize(sent)}"
self.setText(text)
class NetworkProvider(MirrorProvider): class NetworkProvider(MirrorProvider, PeriodicProvider):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__()
self.sections: dict[str, NetworkProviderSection] = dict() self.sections: dict[str, NetworkProviderSection] = dict()
async def updateIface(self, iface: str) -> None: async def init(self) -> None:
section = self.sections[iface] loop = asyncio.get_running_loop()
section.setText(await section.getText()) self.time = loop.time()
self.io_counters = psutil.net_io_counters(pernic=True)
async def run(self) -> None: async def loop(self) -> None:
await super().run() loop = asyncio.get_running_loop()
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)
while True: for iface in self.if_stats:
# if_addrs: dict[str, list[psutil._common.snicaddr]] = psutil.net_if_addrs() section = self.sections.get(iface)
# io_counters: dict[str, psutil._common.snetio] = psutil.net_io_counters(pernic=True) if not section:
section = NetworkProviderSection(
parent=self.module, iface=iface, provider=self
)
self.sections[iface] = section
async with asyncio.TaskGroup() as tg: tg.create_task(section.update())
self.if_stats = psutil.net_if_stats() for iface, section in self.sections.items():
for iface in self.if_stats: if iface not in self.if_stats:
if iface not in self.sections: section.setText(None)
section = NetworkProviderSection(
parent=self.module, iface=iface, provider=self
)
self.sections[iface] = section
tg.create_task(self.updateIface(iface)) async def onStateChange(self, section: StatefulSection) -> None:
for iface, section in self.sections.items(): assert isinstance(section, NetworkProviderSection)
if iface not in self.if_stats: await section.update()
section.setText(None)
tg.create_task(asyncio.sleep(1))
class TimeProvider(StatefulProvider): class TimeProvider(PeriodicStatefulProvider):
FORMATS = ["%H:%M", "%m-%d %H:%M:%S", "%a %y-%m-%d %H:%M:%S"] FORMATS = ["%H:%M", "%m-%d %H:%M:%S", "%a %y-%m-%d %H:%M:%S"]
async def run(self) -> None: async def init(self) -> None:
await super().run()
assert isinstance(self.section, StatefulSection)
self.section.state = 1 self.section.state = 1
self.section.numberStates = len(self.FORMATS) self.section.numberStates = len(self.FORMATS)
while True: async def loop(self) -> None:
now = datetime.datetime.now() now = datetime.datetime.now()
format = self.FORMATS[self.section.state] format = self.FORMATS[self.section.state]
self.section.setText(now.strftime(format)) self.section.setText(now.strftime(format))
remaining = 1 - now.microsecond / 1000000
try:
await asyncio.wait_for(self.section.stateChanged.wait(), remaining)
except TimeoutError:
pass
async def main() -> None: async def main() -> None: