From 953aa13cb6fe7bf29538c54809225d4c3b222fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffrey=20=E2=80=9CFrogeye=E2=80=9D=20Preud=27homme?= Date: Fri, 3 Jan 2025 18:56:20 +0100 Subject: [PATCH] frobar-ng: Colors --- hm/desktop/frobar/.dev/new.py | 175 ++++++++++++++++++++++++++-------- hm/desktop/frobar/default.nix | 5 +- 2 files changed, 136 insertions(+), 44 deletions(-) diff --git a/hm/desktop/frobar/.dev/new.py b/hm/desktop/frobar/.dev/new.py index 1290ad7..d89de3b 100644 --- a/hm/desktop/frobar/.dev/new.py +++ b/hm/desktop/frobar/.dev/new.py @@ -5,18 +5,24 @@ import datetime import enum import ipaddress import logging -import random import signal import socket import typing -import coloredlogs import i3ipc import i3ipc.aio import psutil +import rich.color +import rich.logging +import rich.terminal_theme -coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s") -log = logging.getLogger() +logging.basicConfig( + level="DEBUG", + format="%(message)s", + datefmt="[%X]", + handlers=[rich.logging.RichHandler()], +) +log = logging.getLogger("frobar") T = typing.TypeVar("T", bound="ComposableText") P = typing.TypeVar("P", bound="ComposableText") @@ -85,12 +91,6 @@ class ComposableText(typing.Generic[P, C]): return self.generateMarkup() -def randomColor(seed: int | bytes | None = None) -> str: - if seed is not None: - random.seed(seed) - return "#" + "".join(f"{random.randint(0, 0xff):02x}" for _ in range(3)) - - class Button(enum.Enum): CLICK_LEFT = "1" CLICK_MIDDLE = "2" @@ -104,11 +104,16 @@ class Section(ComposableText): Colorable block separated by chevrons """ - def __init__(self, parent: "Module", sortKey: Sortable = 0) -> None: + def __init__( + self, + parent: "Module", + sortKey: Sortable = 0, + color: rich.color.Color = rich.color.Color.default(), + ) -> None: super().__init__(parent=parent, sortKey=sortKey) self.parent: "Module" + self.color = color - self.color = randomColor() self.desiredText: str | None = None self.text = "" self.targetSize = -1 @@ -224,6 +229,7 @@ class Side(ComposableText): self.children: typing.MutableSequence[Module] = [] self.alignment = alignment + self.bar = parent.getFirstParentOfType(Bar) def generateMarkup(self) -> str: if not self.children: @@ -234,22 +240,23 @@ class Side(ComposableText): for section in module.getSections(): if section.isHidden(): continue + hexa = section.color.get_truecolor(theme=self.bar.theme).hex if lastSection is None: if self.alignment == Alignment.LEFT: - text += "%{B" + section.color + "}%{F-}" + text += "%{B" + hexa + "}%{F-}" else: - text += "%{B-}%{F" + section.color + "}%{R}%{F-}" + text += "%{B-}%{F" + hexa + "}%{R}%{F-}" else: if self.alignment == Alignment.RIGHT: if lastSection.color == section.color: text += "" else: - text += "%{F" + section.color + "}%{R}" + text += "%{F" + hexa + "}%{R}" else: if lastSection.color == section.color: text += "" else: - text += "%{R}%{B" + section.color + "}" + text += "%{R}%{B" + hexa + "}" text += "%{F-}" text += section.getMarkup() lastSection = section @@ -280,11 +287,15 @@ class Bar(ComposableText): Top-level """ - def __init__(self) -> None: + def __init__( + self, + theme: rich.terminal_theme.TerminalTheme = rich.terminal_theme.DEFAULT_TERMINAL_THEME, + ) -> None: super().__init__() self.parent: None self.children: typing.MutableSequence[Screen] self.longRunningTasks: list[asyncio.Task] = list() + self.theme = theme self.refresh = asyncio.Event() self.taskGroup = asyncio.TaskGroup() @@ -312,6 +323,10 @@ class Bar(ComposableText): "64", "-f", "DejaVuSansM Nerd Font:size=10", + "-F", + self.theme.foreground_color.hex, + "-B", + self.theme.background_color.hex, ] proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE @@ -380,8 +395,9 @@ class Bar(ComposableText): class Provider: - def __init__(self) -> None: + def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None: self.modules: list[Module] = list() + self.color = color async def run(self) -> None: # Not a NotImplementedError, otherwise can't combine all classes @@ -389,8 +405,8 @@ class Provider: class MirrorProvider(Provider): - def __init__(self) -> None: - super().__init__() + def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None: + super().__init__(color=color) self.module: Module async def run(self) -> None: @@ -403,11 +419,14 @@ class MirrorProvider(Provider): class SingleSectionProvider(MirrorProvider): async def run(self) -> None: await super().run() - self.section = Section(parent=self.module) + self.section = Section(parent=self.module, color=self.color) class StaticProvider(SingleSectionProvider): - def __init__(self, text: str) -> None: + def __init__( + self, text: str, color: rich.color.Color = rich.color.Color.default() + ) -> None: + super().__init__(color=color) self.text = text async def run(self) -> None: @@ -417,8 +436,13 @@ class StaticProvider(SingleSectionProvider): class StatefulSection(Section): - def __init__(self, parent: Module, sortKey: Sortable = 0) -> None: - super().__init__(parent=parent, sortKey=sortKey) + def __init__( + self, + parent: Module, + sortKey: Sortable = 0, + color: rich.color.Color = rich.color.Color.default(), + ) -> None: + super().__init__(parent=parent, sortKey=sortKey, color=color) self.state = 0 self.numberStates: int @@ -444,7 +468,7 @@ class StatefulSection(Section): class SingleStatefulSectionProvider(MirrorProvider): async def run(self) -> None: await super().run() - self.section = StatefulSection(parent=self.module) + self.section = StatefulSection(parent=self.module, color=self.color) class PeriodicProvider(Provider): @@ -518,8 +542,10 @@ class I3WindowTitleProvider(SingleSectionProvider): class I3WorkspacesProvider(Provider): - # TODO Custom names - # TODO Colors + COLOR_URGENT = rich.color.Color.parse("red") + COLOR_FOCUSED = rich.color.Color.parse("yellow") + COLOR_VISIBLE = rich.color.Color.parse("blue") + COLOR_DEFAULT = rich.color.Color.parse("bright_black") async def updateWorkspaces(self, i3: i3ipc.Connection) -> None: """ @@ -554,11 +580,15 @@ class I3WorkspacesProvider(Provider): ) name = workspace.name if workspace.urgent: - name = f"{name} !" + section.color = self.COLOR_URGENT elif workspace.focused: - name = f"{name} +" + section.color = self.COLOR_FOCUSED elif workspace.visible: - name = f"{name} *" + section.color = self.COLOR_VISIBLE + else: + section.color = self.COLOR_DEFAULT + if workspace.focused or workspace.visible: + name = f"{name} X" # TODO Custom names section.setText(name) workspacesNums = set(workspace.num for workspace in workspaces) for num, section in self.sections.items(): @@ -595,8 +625,13 @@ class I3WorkspacesProvider(Provider): class NetworkProviderSection(StatefulSection): - def __init__(self, parent: Module, iface: str, provider: "NetworkProvider") -> None: - super().__init__(parent=parent, sortKey=iface) + def __init__( + self, + parent: Module, + iface: str, + provider: "NetworkProvider", + ) -> None: + super().__init__(parent=parent, sortKey=iface, color=provider.color) self.iface = iface self.provider = provider @@ -649,7 +684,7 @@ class NetworkProviderSection(StatefulSection): net = ipaddress.IPv4Network( (address.address, address.netmask), strict=False ) - text += f" {net.with_prefixlen}" + text += f" {address.address}/{net.prefixlen}" break if state >= 3: # Speed @@ -670,8 +705,11 @@ class NetworkProviderSection(StatefulSection): class NetworkProvider(MirrorProvider, PeriodicProvider): - def __init__(self) -> None: - super().__init__() + def __init__( + self, + color: rich.color.Color = rich.color.Color.default(), + ) -> None: + super().__init__(color=color) self.sections: dict[str, NetworkProviderSection] = dict() async def init(self) -> None: @@ -725,33 +763,86 @@ class TimeProvider(PeriodicStatefulProvider): async def main() -> None: - bar = Bar() + FROGARIZED = [ + "#092c0e", + "#143718", + "#5a7058", + "#677d64", + "#89947f", + "#99a08d", + "#fae2e3", + "#fff0f1", + "#e0332e", + "#cf4b15", + "#bb8801", + "#8d9800", + "#1fa198", + "#008dd1", + "#5c73c4", + "#d43982", + ] + # TODO Configurable + + def base16_color(color: int) -> tuple[int, int, int]: + hexa = FROGARIZED[color] + return tuple(rich.color.parse_rgb_hex(hexa[1:])) + + theme = rich.terminal_theme.TerminalTheme( + base16_color(0x0), + base16_color(0x0), # TODO should be 7, currently 0 so it's compatible with v2 + [ + base16_color(0x0), # black + base16_color(0x8), # red + base16_color(0xB), # green + base16_color(0xA), # yellow + base16_color(0xD), # blue + base16_color(0xE), # magenta + base16_color(0xC), # cyan + base16_color(0x5), # white + ], + [ + base16_color(0x3), # bright black + base16_color(0x8), # bright red + base16_color(0xB), # bright green + base16_color(0xA), # bright yellow + base16_color(0xD), # bright blue + base16_color(0xE), # bright magenta + base16_color(0xC), # bright cyan + base16_color(0x7), # bright white + ], + ) + + bar = Bar(theme=theme) dualScreen = len(bar.children) > 1 - bar.addProvider(I3ModeProvider(), alignment=Alignment.LEFT) + color = rich.color.Color.parse + + bar.addProvider(I3ModeProvider(color=color("red")), alignment=Alignment.LEFT) bar.addProvider(I3WorkspacesProvider(), alignment=Alignment.LEFT) if dualScreen: bar.addProvider( I3WindowTitleProvider(), screenNum=0, alignment=Alignment.CENTER ) bar.addProvider( - StaticProvider(text="mpris"), + StaticProvider(text="mpris", color=color("bright_white")), screenNum=1 if dualScreen else None, alignment=Alignment.CENTER, ) - bar.addProvider(StaticProvider("C L M T B"), alignment=Alignment.RIGHT) bar.addProvider( - StaticProvider("pulse"), + StaticProvider("C L M T B", color=color("green")), alignment=Alignment.RIGHT + ) + bar.addProvider( + StaticProvider("pulse", color=color("magenta")), screenNum=1 if dualScreen else None, alignment=Alignment.RIGHT, ) bar.addProvider( - NetworkProvider(), + NetworkProvider(color=color("blue")), screenNum=0 if dualScreen else None, alignment=Alignment.RIGHT, ) - bar.addProvider(TimeProvider(), alignment=Alignment.RIGHT) + bar.addProvider(TimeProvider(color=color("cyan")), alignment=Alignment.RIGHT) await bar.run() diff --git a/hm/desktop/frobar/default.nix b/hm/desktop/frobar/default.nix index b6c0020..a1193d7 100644 --- a/hm/desktop/frobar/default.nix +++ b/hm/desktop/frobar/default.nix @@ -25,13 +25,14 @@ pkgs.python3Packages.buildPythonApplication rec { version = "2.0"; propagatedBuildInputs = with pkgs.python3Packages; [ - coloredlogs - notmuch + coloredlogs # old only i3ipc mpd2 + notmuch psutil pulsectl pyinotify + rich ]; nativeBuildInputs = [ lemonbar ]