frobar-ng: Colors
This commit is contained in:
parent
7b54ad0578
commit
953aa13cb6
|
@ -5,18 +5,24 @@ import datetime
|
||||||
import enum
|
import enum
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
import random
|
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
import coloredlogs
|
|
||||||
import i3ipc
|
import i3ipc
|
||||||
import i3ipc.aio
|
import i3ipc.aio
|
||||||
import psutil
|
import psutil
|
||||||
|
import rich.color
|
||||||
|
import rich.logging
|
||||||
|
import rich.terminal_theme
|
||||||
|
|
||||||
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
logging.basicConfig(
|
||||||
log = logging.getLogger()
|
level="DEBUG",
|
||||||
|
format="%(message)s",
|
||||||
|
datefmt="[%X]",
|
||||||
|
handlers=[rich.logging.RichHandler()],
|
||||||
|
)
|
||||||
|
log = logging.getLogger("frobar")
|
||||||
|
|
||||||
T = typing.TypeVar("T", bound="ComposableText")
|
T = typing.TypeVar("T", bound="ComposableText")
|
||||||
P = typing.TypeVar("P", bound="ComposableText")
|
P = typing.TypeVar("P", bound="ComposableText")
|
||||||
|
@ -85,12 +91,6 @@ class ComposableText(typing.Generic[P, C]):
|
||||||
return self.generateMarkup()
|
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):
|
class Button(enum.Enum):
|
||||||
CLICK_LEFT = "1"
|
CLICK_LEFT = "1"
|
||||||
CLICK_MIDDLE = "2"
|
CLICK_MIDDLE = "2"
|
||||||
|
@ -104,11 +104,16 @@ class Section(ComposableText):
|
||||||
Colorable block separated by chevrons
|
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)
|
super().__init__(parent=parent, sortKey=sortKey)
|
||||||
self.parent: "Module"
|
self.parent: "Module"
|
||||||
|
self.color = color
|
||||||
|
|
||||||
self.color = randomColor()
|
|
||||||
self.desiredText: str | None = None
|
self.desiredText: str | None = None
|
||||||
self.text = ""
|
self.text = ""
|
||||||
self.targetSize = -1
|
self.targetSize = -1
|
||||||
|
@ -224,6 +229,7 @@ class Side(ComposableText):
|
||||||
self.children: typing.MutableSequence[Module] = []
|
self.children: typing.MutableSequence[Module] = []
|
||||||
|
|
||||||
self.alignment = alignment
|
self.alignment = alignment
|
||||||
|
self.bar = parent.getFirstParentOfType(Bar)
|
||||||
|
|
||||||
def generateMarkup(self) -> str:
|
def generateMarkup(self) -> str:
|
||||||
if not self.children:
|
if not self.children:
|
||||||
|
@ -234,22 +240,23 @@ class Side(ComposableText):
|
||||||
for section in module.getSections():
|
for section in module.getSections():
|
||||||
if section.isHidden():
|
if section.isHidden():
|
||||||
continue
|
continue
|
||||||
|
hexa = section.color.get_truecolor(theme=self.bar.theme).hex
|
||||||
if lastSection is None:
|
if lastSection is None:
|
||||||
if self.alignment == Alignment.LEFT:
|
if self.alignment == Alignment.LEFT:
|
||||||
text += "%{B" + section.color + "}%{F-}"
|
text += "%{B" + hexa + "}%{F-}"
|
||||||
else:
|
else:
|
||||||
text += "%{B-}%{F" + section.color + "}%{R}%{F-}"
|
text += "%{B-}%{F" + hexa + "}%{R}%{F-}"
|
||||||
else:
|
else:
|
||||||
if self.alignment == Alignment.RIGHT:
|
if self.alignment == Alignment.RIGHT:
|
||||||
if lastSection.color == section.color:
|
if lastSection.color == section.color:
|
||||||
text += ""
|
text += ""
|
||||||
else:
|
else:
|
||||||
text += "%{F" + section.color + "}%{R}"
|
text += "%{F" + hexa + "}%{R}"
|
||||||
else:
|
else:
|
||||||
if lastSection.color == section.color:
|
if lastSection.color == section.color:
|
||||||
text += ""
|
text += ""
|
||||||
else:
|
else:
|
||||||
text += "%{R}%{B" + section.color + "}"
|
text += "%{R}%{B" + hexa + "}"
|
||||||
text += "%{F-}"
|
text += "%{F-}"
|
||||||
text += section.getMarkup()
|
text += section.getMarkup()
|
||||||
lastSection = section
|
lastSection = section
|
||||||
|
@ -280,11 +287,15 @@ class Bar(ComposableText):
|
||||||
Top-level
|
Top-level
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
theme: rich.terminal_theme.TerminalTheme = rich.terminal_theme.DEFAULT_TERMINAL_THEME,
|
||||||
|
) -> None:
|
||||||
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.longRunningTasks: list[asyncio.Task] = list()
|
||||||
|
self.theme = theme
|
||||||
|
|
||||||
self.refresh = asyncio.Event()
|
self.refresh = asyncio.Event()
|
||||||
self.taskGroup = asyncio.TaskGroup()
|
self.taskGroup = asyncio.TaskGroup()
|
||||||
|
@ -312,6 +323,10 @@ class Bar(ComposableText):
|
||||||
"64",
|
"64",
|
||||||
"-f",
|
"-f",
|
||||||
"DejaVuSansM Nerd Font:size=10",
|
"DejaVuSansM Nerd Font:size=10",
|
||||||
|
"-F",
|
||||||
|
self.theme.foreground_color.hex,
|
||||||
|
"-B",
|
||||||
|
self.theme.background_color.hex,
|
||||||
]
|
]
|
||||||
proc = await asyncio.create_subprocess_exec(
|
proc = await asyncio.create_subprocess_exec(
|
||||||
*cmd, stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
|
*cmd, stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
|
||||||
|
@ -380,8 +395,9 @@ class Bar(ComposableText):
|
||||||
|
|
||||||
|
|
||||||
class Provider:
|
class Provider:
|
||||||
def __init__(self) -> None:
|
def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None:
|
||||||
self.modules: list[Module] = list()
|
self.modules: list[Module] = list()
|
||||||
|
self.color = color
|
||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
# Not a NotImplementedError, otherwise can't combine all classes
|
# Not a NotImplementedError, otherwise can't combine all classes
|
||||||
|
@ -389,8 +405,8 @@ class Provider:
|
||||||
|
|
||||||
|
|
||||||
class MirrorProvider(Provider):
|
class MirrorProvider(Provider):
|
||||||
def __init__(self) -> None:
|
def __init__(self, color: rich.color.Color = rich.color.Color.default()) -> None:
|
||||||
super().__init__()
|
super().__init__(color=color)
|
||||||
self.module: Module
|
self.module: Module
|
||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
|
@ -403,11 +419,14 @@ class MirrorProvider(Provider):
|
||||||
class SingleSectionProvider(MirrorProvider):
|
class SingleSectionProvider(MirrorProvider):
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
await super().run()
|
await super().run()
|
||||||
self.section = Section(parent=self.module)
|
self.section = Section(parent=self.module, color=self.color)
|
||||||
|
|
||||||
|
|
||||||
class StaticProvider(SingleSectionProvider):
|
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
|
self.text = text
|
||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
|
@ -417,8 +436,13 @@ class StaticProvider(SingleSectionProvider):
|
||||||
|
|
||||||
class StatefulSection(Section):
|
class StatefulSection(Section):
|
||||||
|
|
||||||
def __init__(self, parent: Module, sortKey: Sortable = 0) -> None:
|
def __init__(
|
||||||
super().__init__(parent=parent, sortKey=sortKey)
|
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.state = 0
|
||||||
self.numberStates: int
|
self.numberStates: int
|
||||||
|
|
||||||
|
@ -444,7 +468,7 @@ class StatefulSection(Section):
|
||||||
class SingleStatefulSectionProvider(MirrorProvider):
|
class SingleStatefulSectionProvider(MirrorProvider):
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
await super().run()
|
await super().run()
|
||||||
self.section = StatefulSection(parent=self.module)
|
self.section = StatefulSection(parent=self.module, color=self.color)
|
||||||
|
|
||||||
|
|
||||||
class PeriodicProvider(Provider):
|
class PeriodicProvider(Provider):
|
||||||
|
@ -518,8 +542,10 @@ class I3WindowTitleProvider(SingleSectionProvider):
|
||||||
|
|
||||||
|
|
||||||
class I3WorkspacesProvider(Provider):
|
class I3WorkspacesProvider(Provider):
|
||||||
# TODO Custom names
|
COLOR_URGENT = rich.color.Color.parse("red")
|
||||||
# TODO Colors
|
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:
|
async def updateWorkspaces(self, i3: i3ipc.Connection) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -554,11 +580,15 @@ class I3WorkspacesProvider(Provider):
|
||||||
)
|
)
|
||||||
name = workspace.name
|
name = workspace.name
|
||||||
if workspace.urgent:
|
if workspace.urgent:
|
||||||
name = f"{name} !"
|
section.color = self.COLOR_URGENT
|
||||||
elif workspace.focused:
|
elif workspace.focused:
|
||||||
name = f"{name} +"
|
section.color = self.COLOR_FOCUSED
|
||||||
elif workspace.visible:
|
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)
|
section.setText(name)
|
||||||
workspacesNums = set(workspace.num for workspace in workspaces)
|
workspacesNums = set(workspace.num for workspace in workspaces)
|
||||||
for num, section in self.sections.items():
|
for num, section in self.sections.items():
|
||||||
|
@ -595,8 +625,13 @@ class I3WorkspacesProvider(Provider):
|
||||||
|
|
||||||
|
|
||||||
class NetworkProviderSection(StatefulSection):
|
class NetworkProviderSection(StatefulSection):
|
||||||
def __init__(self, parent: Module, iface: str, provider: "NetworkProvider") -> None:
|
def __init__(
|
||||||
super().__init__(parent=parent, sortKey=iface)
|
self,
|
||||||
|
parent: Module,
|
||||||
|
iface: str,
|
||||||
|
provider: "NetworkProvider",
|
||||||
|
) -> None:
|
||||||
|
super().__init__(parent=parent, sortKey=iface, color=provider.color)
|
||||||
self.iface = iface
|
self.iface = iface
|
||||||
self.provider = provider
|
self.provider = provider
|
||||||
|
|
||||||
|
@ -649,7 +684,7 @@ class NetworkProviderSection(StatefulSection):
|
||||||
net = ipaddress.IPv4Network(
|
net = ipaddress.IPv4Network(
|
||||||
(address.address, address.netmask), strict=False
|
(address.address, address.netmask), strict=False
|
||||||
)
|
)
|
||||||
text += f" {net.with_prefixlen}"
|
text += f" {address.address}/{net.prefixlen}"
|
||||||
break
|
break
|
||||||
|
|
||||||
if state >= 3: # Speed
|
if state >= 3: # Speed
|
||||||
|
@ -670,8 +705,11 @@ class NetworkProviderSection(StatefulSection):
|
||||||
|
|
||||||
|
|
||||||
class NetworkProvider(MirrorProvider, PeriodicProvider):
|
class NetworkProvider(MirrorProvider, PeriodicProvider):
|
||||||
def __init__(self) -> None:
|
def __init__(
|
||||||
super().__init__()
|
self,
|
||||||
|
color: rich.color.Color = rich.color.Color.default(),
|
||||||
|
) -> None:
|
||||||
|
super().__init__(color=color)
|
||||||
self.sections: dict[str, NetworkProviderSection] = dict()
|
self.sections: dict[str, NetworkProviderSection] = dict()
|
||||||
|
|
||||||
async def init(self) -> None:
|
async def init(self) -> None:
|
||||||
|
@ -725,33 +763,86 @@ class TimeProvider(PeriodicStatefulProvider):
|
||||||
|
|
||||||
|
|
||||||
async def main() -> None:
|
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
|
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)
|
bar.addProvider(I3WorkspacesProvider(), alignment=Alignment.LEFT)
|
||||||
if dualScreen:
|
if dualScreen:
|
||||||
bar.addProvider(
|
bar.addProvider(
|
||||||
I3WindowTitleProvider(), screenNum=0, alignment=Alignment.CENTER
|
I3WindowTitleProvider(), screenNum=0, alignment=Alignment.CENTER
|
||||||
)
|
)
|
||||||
bar.addProvider(
|
bar.addProvider(
|
||||||
StaticProvider(text="mpris"),
|
StaticProvider(text="mpris", color=color("bright_white")),
|
||||||
screenNum=1 if dualScreen else None,
|
screenNum=1 if dualScreen else None,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
)
|
)
|
||||||
|
|
||||||
bar.addProvider(StaticProvider("C L M T B"), alignment=Alignment.RIGHT)
|
|
||||||
bar.addProvider(
|
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,
|
screenNum=1 if dualScreen else None,
|
||||||
alignment=Alignment.RIGHT,
|
alignment=Alignment.RIGHT,
|
||||||
)
|
)
|
||||||
bar.addProvider(
|
bar.addProvider(
|
||||||
NetworkProvider(),
|
NetworkProvider(color=color("blue")),
|
||||||
screenNum=0 if dualScreen else None,
|
screenNum=0 if dualScreen else None,
|
||||||
alignment=Alignment.RIGHT,
|
alignment=Alignment.RIGHT,
|
||||||
)
|
)
|
||||||
bar.addProvider(TimeProvider(), alignment=Alignment.RIGHT)
|
bar.addProvider(TimeProvider(color=color("cyan")), alignment=Alignment.RIGHT)
|
||||||
|
|
||||||
await bar.run()
|
await bar.run()
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,14 @@ pkgs.python3Packages.buildPythonApplication rec {
|
||||||
version = "2.0";
|
version = "2.0";
|
||||||
|
|
||||||
propagatedBuildInputs = with pkgs.python3Packages; [
|
propagatedBuildInputs = with pkgs.python3Packages; [
|
||||||
coloredlogs
|
coloredlogs # old only
|
||||||
notmuch
|
|
||||||
i3ipc
|
i3ipc
|
||||||
mpd2
|
mpd2
|
||||||
|
notmuch
|
||||||
psutil
|
psutil
|
||||||
pulsectl
|
pulsectl
|
||||||
pyinotify
|
pyinotify
|
||||||
|
rich
|
||||||
];
|
];
|
||||||
nativeBuildInputs =
|
nativeBuildInputs =
|
||||||
[ lemonbar ]
|
[ lemonbar ]
|
||||||
|
|
Loading…
Reference in a new issue