frobar-ng: Colors

This commit is contained in:
Geoffrey Frogeye 2025-01-03 18:56:20 +01:00
parent 7b54ad0578
commit 953aa13cb6
Signed by: geoffrey
GPG key ID: C72403E7F82E6AD8
2 changed files with 136 additions and 44 deletions

View file

@ -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()

View file

@ -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 ]