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

View file

@ -3,11 +3,9 @@
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
@ -24,21 +22,6 @@ 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__(
@ -284,7 +267,6 @@ 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()
@ -292,18 +274,12 @@ 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",
@ -323,6 +299,7 @@ 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:
@ -333,15 +310,21 @@ 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:
self.addLongRunningTask(refresher()) addLongRunningTask(refresher())
self.addLongRunningTask(actionHandler()) addLongRunningTask(actionHandler())
for provider in self.providers: for provider in self.providers:
self.addLongRunningTask(provider.run()) addLongRunningTask(provider.run())
def exit() -> None: def exit() -> None:
log.info("Terminating") log.info("Terminating")
for task in self.longRunningTasks: for task in longRunningTasks:
task.cancel() task.cancel()
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -384,8 +367,7 @@ class Provider:
self.modules: list[Module] = list() self.modules: list[Module] = list()
async def run(self) -> None: async def run(self) -> None:
# Not a NotImplementedError, otherwise can't combine all classes raise NotImplementedError()
pass
class MirrorProvider(Provider): class MirrorProvider(Provider):
@ -394,16 +376,17 @@ 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 = Section(parent=self.module) self.section = self.SECTION_CLASS(parent=self.module)
class StaticProvider(SingleSectionProvider): class StaticProvider(SingleSectionProvider):
@ -421,6 +404,7 @@ 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)
@ -433,61 +417,14 @@ 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.bar.taskGroup.create_task(self.callback()) self.stateChanged.set()
self.stateChanged.clear()
class SingleStatefulSectionProvider(MirrorProvider): class StatefulProvider(SingleSectionProvider):
async def run(self) -> None: SECTION_CLASS = StatefulSection
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
@ -518,8 +455,8 @@ class I3WindowTitleProvider(SingleSectionProvider):
class I3WorkspacesProvider(Provider): class I3WorkspacesProvider(Provider):
# TODO Custom names # FIXME Custom names
# TODO Colors # FIXME Colors
async def updateWorkspaces(self, i3: i3ipc.Connection) -> None: async def updateWorkspaces(self, i3: i3ipc.Connection) -> None:
""" """
@ -626,102 +563,64 @@ 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
self.setChangedState(self.update) async def getText(self) -> str | None:
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:
self.setText(None) return 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, PeriodicProvider): class NetworkProvider(MirrorProvider):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__()
self.sections: dict[str, NetworkProviderSection] = dict() self.sections: dict[str, NetworkProviderSection] = dict()
async def init(self) -> None: async def updateIface(self, iface: str) -> None:
loop = asyncio.get_running_loop() section = self.sections[iface]
self.time = loop.time() section.setText(await section.getText())
self.io_counters = psutil.net_io_counters(pernic=True)
async def loop(self) -> None: async def run(self) -> None:
loop = asyncio.get_running_loop() await super().run()
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: while True:
section = self.sections.get(iface) # if_addrs: dict[str, list[psutil._common.snicaddr]] = psutil.net_if_addrs()
if not section: # io_counters: dict[str, psutil._common.snetio] = psutil.net_io_counters(pernic=True)
section = NetworkProviderSection(
parent=self.module, iface=iface, provider=self
)
self.sections[iface] = section
tg.create_task(section.update()) async with asyncio.TaskGroup() as tg:
for iface, section in self.sections.items(): self.if_stats = psutil.net_if_stats()
if iface not in self.if_stats: for iface in self.if_stats:
section.setText(None) 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: tg.create_task(self.updateIface(iface))
assert isinstance(section, NetworkProviderSection) for iface, section in self.sections.items():
await section.update() 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"] 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.state = 1
self.section.numberStates = len(self.FORMATS) self.section.numberStates = len(self.FORMATS)
async def loop(self) -> None: while True:
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: