Compare commits

..

No commits in common. "812e5acf6c0f72c72d251704b1f4649c80785e74" and "01934563a531214d0be3bb98f04aaaa6f168ec30" have entirely different histories.

2 changed files with 95 additions and 196 deletions

View file

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

View file

@ -3,11 +3,9 @@
import asyncio
import datetime
import enum
import ipaddress
import logging
import random
import signal
import socket
import typing
import coloredlogs
@ -24,21 +22,6 @@ 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__(
@ -284,7 +267,6 @@ 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()
@ -292,18 +274,12 @@ class Bar(ComposableText):
self.actionIndex = 0
self.actions: dict[str, typing.Callable] = dict()
self.periodicProviderTask: typing.Coroutine | None = None
i3 = i3ipc.Connection()
for output in i3.get_outputs():
if not output.active:
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",
@ -323,6 +299,7 @@ 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:
@ -333,15 +310,21 @@ 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:
self.addLongRunningTask(refresher())
self.addLongRunningTask(actionHandler())
addLongRunningTask(refresher())
addLongRunningTask(actionHandler())
for provider in self.providers:
self.addLongRunningTask(provider.run())
addLongRunningTask(provider.run())
def exit() -> None:
log.info("Terminating")
for task in self.longRunningTasks:
for task in longRunningTasks:
task.cancel()
loop = asyncio.get_event_loop()
@ -384,8 +367,7 @@ class Provider:
self.modules: list[Module] = list()
async def run(self) -> None:
# Not a NotImplementedError, otherwise can't combine all classes
pass
raise NotImplementedError()
class MirrorProvider(Provider):
@ -394,16 +376,17 @@ class MirrorProvider(Provider):
self.module: Module
async def run(self) -> None:
await super().run()
self.module = self.modules[0]
for module in self.modules[1:]:
module.mirror(self.module)
class SingleSectionProvider(MirrorProvider):
SECTION_CLASS = Section
async def run(self) -> None:
await super().run()
self.section = Section(parent=self.module)
self.section = self.SECTION_CLASS(parent=self.module)
class StaticProvider(SingleSectionProvider):
@ -421,6 +404,7 @@ class StatefulSection(Section):
super().__init__(parent=parent, sortKey=sortKey)
self.state = 0
self.numberStates: int
self.stateChanged = asyncio.Event()
self.setAction(Button.CLICK_LEFT, self.incrementState)
self.setAction(Button.CLICK_RIGHT, self.decrementState)
@ -433,61 +417,14 @@ class StatefulSection(Section):
self.state -= 1
self.changeState()
def setChangedState(self, callback: typing.Callable) -> None:
self.callback = callback
def changeState(self) -> None:
self.state %= self.numberStates
self.bar.taskGroup.create_task(self.callback())
self.stateChanged.set()
self.stateChanged.clear()
class SingleStatefulSectionProvider(MirrorProvider):
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)
class StatefulProvider(SingleSectionProvider):
SECTION_CLASS = StatefulSection
# Providers
@ -518,8 +455,8 @@ class I3WindowTitleProvider(SingleSectionProvider):
class I3WorkspacesProvider(Provider):
# TODO Custom names
# TODO Colors
# FIXME Custom names
# FIXME Colors
async def updateWorkspaces(self, i3: i3ipc.Connection) -> None:
"""
@ -626,102 +563,64 @@ class NetworkProviderSection(StatefulSection):
self.numberStates = 5 if self.wifi else 4
self.state = 1 if self.wifi else 0
self.setChangedState(self.update)
async def update(self) -> None:
async def getText(self) -> str | None:
if self.ignore or not self.provider.if_stats[self.iface].isup:
self.setText(None)
return
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
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)
return text
class NetworkProvider(MirrorProvider, PeriodicProvider):
class NetworkProvider(MirrorProvider):
def __init__(self) -> None:
super().__init__()
self.sections: dict[str, NetworkProviderSection] = dict()
async def init(self) -> None:
loop = asyncio.get_running_loop()
self.time = loop.time()
self.io_counters = psutil.net_io_counters(pernic=True)
async def updateIface(self, iface: str) -> None:
section = self.sections[iface]
section.setText(await section.getText())
async def loop(self) -> None:
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)
async def run(self) -> None:
await super().run()
for iface in self.if_stats:
section = self.sections.get(iface)
if not section:
section = NetworkProviderSection(
parent=self.module, iface=iface, provider=self
)
self.sections[iface] = section
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)
tg.create_task(section.update())
for iface, section in self.sections.items():
if iface not in self.if_stats:
section.setText(None)
async with asyncio.TaskGroup() as tg:
self.if_stats = psutil.net_if_stats()
for iface in self.if_stats:
if iface not in self.sections:
section = NetworkProviderSection(
parent=self.module, iface=iface, provider=self
)
self.sections[iface] = section
async def onStateChange(self, section: StatefulSection) -> None:
assert isinstance(section, NetworkProviderSection)
await section.update()
tg.create_task(self.updateIface(iface))
for iface, section in self.sections.items():
if iface not in self.if_stats:
section.setText(None)
tg.create_task(asyncio.sleep(1))
class TimeProvider(PeriodicStatefulProvider):
class TimeProvider(StatefulProvider):
FORMATS = ["%H:%M", "%m-%d %H:%M:%S", "%a %y-%m-%d %H:%M:%S"]
async def init(self) -> None:
async def run(self) -> None:
await super().run()
assert isinstance(self.section, StatefulSection)
self.section.state = 1
self.section.numberStates = len(self.FORMATS)
async def loop(self) -> None:
now = datetime.datetime.now()
format = self.FORMATS[self.section.state]
self.section.setText(now.strftime(format))
while True:
now = datetime.datetime.now()
format = self.FORMATS[self.section.state]
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: