From 445c2b8a99dd4b3d0e9b157941d10fce734aa1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffrey=20=E2=80=9CFrogeye=E2=80=9D=20Preud=27homme?= Date: Tue, 18 Jun 2024 04:09:59 +0200 Subject: [PATCH] frobar: Mutli-display support Freaking finally --- hm/desktop/frobar/default.nix | 16 +- hm/desktop/frobar/frobar/__init__.py | 3 + .../frobar/frobar/{notbusy.py => common.py} | 0 hm/desktop/frobar/frobar/display.py | 47 +++--- hm/desktop/frobar/frobar/providers.py | 143 ++++++++---------- hm/desktop/frobar/frobar/updaters.py | 2 +- 6 files changed, 108 insertions(+), 103 deletions(-) rename hm/desktop/frobar/frobar/{notbusy.py => common.py} (100%) diff --git a/hm/desktop/frobar/default.nix b/hm/desktop/frobar/default.nix index a5df2bb..e28bb45 100644 --- a/hm/desktop/frobar/default.nix +++ b/hm/desktop/frobar/default.nix @@ -1,11 +1,21 @@ { pkgs ? import { config = { }; overlays = [ ]; }, ... }: +let + lemonbar = (pkgs.lemonbar-xft.overrideAttrs (old: { + src = pkgs.fetchFromGitHub { + owner = "drscream"; + repo = "lemonbar-xft"; + rev = "a64a2a6a6d643f4d92f9d7600722710eebce7bdb"; + sha256 = "sha256-T5FhEPIiDt/9paJwL9Sj84CBtA0YFi1hZz0+87Hd6jU="; + # https://github.com/drscream/lemonbar-xft/pull/2 + }; + })); +in # Tried using pyproject.nix but mpd2 dependency wouldn't resolve, -# is called pyton-mpd2 on PyPi but mpd2 in nixpkgs. + # is called pyton-mpd2 on PyPi but mpd2 in nixpkgs. pkgs.python3Packages.buildPythonApplication { pname = "frobar"; version = "2.0"; - runtimeInputs = with pkgs; [ lemonbar-xft wirelesstools ]; propagatedBuildInputs = with pkgs.python3Packages; [ coloredlogs notmuch @@ -15,7 +25,7 @@ pkgs.python3Packages.buildPythonApplication { pulsectl pyinotify ]; - makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath (with pkgs; [ lemonbar-xft wirelesstools ])}" ]; + makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath ([ lemonbar ] ++ (with pkgs; [ wirelesstools ]))}" ]; src = ./.; } diff --git a/hm/desktop/frobar/frobar/__init__.py b/hm/desktop/frobar/frobar/__init__.py index 8a76774..4a3e58c 100644 --- a/hm/desktop/frobar/frobar/__init__.py +++ b/hm/desktop/frobar/frobar/__init__.py @@ -12,6 +12,9 @@ def run() -> None: Bar.init() Updater.init() + # Bar.addSectionAll(fp.CpuProvider(), BarGroupType.RIGHT) + # Bar.addSectionAll(fp.NetworkProvider(theme=2), BarGroupType.RIGHT) + WORKSPACE_THEME = 8 FOCUS_THEME = 2 URGENT_THEME = 0 diff --git a/hm/desktop/frobar/frobar/notbusy.py b/hm/desktop/frobar/frobar/common.py similarity index 100% rename from hm/desktop/frobar/frobar/notbusy.py rename to hm/desktop/frobar/frobar/common.py diff --git a/hm/desktop/frobar/frobar/display.py b/hm/desktop/frobar/frobar/display.py index 0896a71..c24b891 100644 --- a/hm/desktop/frobar/frobar/display.py +++ b/hm/desktop/frobar/frobar/display.py @@ -12,7 +12,7 @@ import typing import coloredlogs import i3ipc -from frobar.notbusy import notBusy +from frobar.common import notBusy coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s") log = logging.getLogger() @@ -70,9 +70,19 @@ class Bar: @staticmethod def init() -> None: Bar.running = True + Bar.everyone = set() Section.init() - cmd = ["lemonbar", "-b", "-a", "64"] + cmd = [ + "lemonbar", + "-b", + "-a", + "64", + "-F", + Section.FGCOLOR, + "-B", + Section.BGCOLOR, + ] for font in Bar.FONTS: cmd += ["-f", "{}:size={}".format(font, Bar.FONTSIZE)] Bar.process = subprocess.Popen( @@ -80,9 +90,11 @@ class Bar: ) BarStdoutThread().start() - # Debug - Bar(0) - # Bar(1) + i3 = i3ipc.Connection() + for output in i3.get_outputs(): + if not output.active: + continue + Bar(output.name) @staticmethod def stop() -> None: @@ -99,17 +111,15 @@ class Bar: def doStop(*args: list) -> None: Bar.stop() - print(88) try: i3.on("ipc_shutdown", doStop) i3.main() except BaseException: - print(93) Bar.stop() # Class globals - everyone: set["Bar"] = set() + everyone: set["Bar"] string = "" process: subprocess.Popen running = False @@ -137,8 +147,8 @@ class Bar: Bar.process.wait() Bar.stop() - def __init__(self, screen: int) -> None: - self.screen = "%{S" + str(screen) + "}" + def __init__(self, output: str) -> None: + self.output = output self.groups = dict() for groupType in BarGroupType: @@ -146,27 +156,26 @@ class Bar: self.groups[groupType] = group self.childsChanged = False - - self.everyone.add(self) + Bar.everyone.add(self) @staticmethod def addSectionAll( - section: "Section", group: "BarGroupType", screens: None = None + section: "Section", group: "BarGroupType" ) -> None: """ .. note:: Add the section before updating it for the first time. """ - # TODO screens selection for bar in Bar.everyone: bar.addSection(section, group=group) + section.added() def addSection(self, section: "Section", group: "BarGroupType") -> None: self.groups[group].addSection(section) def update(self) -> None: if self.childsChanged: - self.string = self.screen + self.string = "%{Sn" + self.output + "}" self.string += self.groups[BarGroupType.LEFT].string self.string += self.groups[BarGroupType.RIGHT].string @@ -182,9 +191,10 @@ class Bar: # Color for empty sections Bar.string += BarGroup.color(*Section.EMPTY) - # print(Bar.string) + string = Bar.string + "\n" + # print(string) assert Bar.process.stdin - Bar.process.stdin.write(bytes(Bar.string + "\n", "utf-8")) + Bar.process.stdin.write(string.encode()) Bar.process.stdin.flush() @@ -410,6 +420,9 @@ class Section: for parent in self.parents: parent.addSectionAfter(self, section) + def added(self) -> None: + pass + def informParentsThemeChanged(self) -> None: for parent in self.parents: parent.childsThemeChanged = True diff --git a/hm/desktop/frobar/frobar/providers.py b/hm/desktop/frobar/frobar/providers.py index d2a040b..20f496d 100644 --- a/hm/desktop/frobar/frobar/providers.py +++ b/hm/desktop/frobar/frobar/providers.py @@ -18,7 +18,7 @@ import notmuch import psutil import pulsectl -from frobar.display import (BarGroup, ColorCountsSection, Element, Section, +from frobar.display import (ColorCountsSection, Element, Section, StatefulSection, Text) from frobar.updaters import (I3Updater, InotifyUpdater, MergedUpdater, PeriodicUpdater, ThreadedUpdater, Updater) @@ -448,10 +448,6 @@ class NetworkProvider(Section, PeriodicUpdater): return None - def addParent(self, parent: BarGroup) -> None: - self.parents.add(parent) - self.refreshData() - def __init__(self, theme: int | None = None): PeriodicUpdater.__init__(self) Section.__init__(self, theme) @@ -670,10 +666,12 @@ class I3WindowTitleProvider(Section, I3Updater): class I3WorkspacesProviderSection(Section): def selectTheme(self) -> int: - if self.urgent: + if self.workspace.urgent: return self.parent.themeUrgent - elif self.focused: + elif self.workspace.focused: return self.parent.themeFocus + elif self.workspace.visible: + return self.parent.themeVisible else: return self.parent.themeNormal @@ -682,26 +680,21 @@ class I3WorkspacesProviderSection(Section): def show(self) -> None: self.updateTheme(self.selectTheme()) - self.updateText(self.fullName if self.focused else self.shortName) - - def changeState(self, focused: bool, urgent: bool) -> None: - self.focused = focused - self.urgent = urgent - self.show() - - def setName(self, name: str) -> None: - self.shortName = name - self.fullName: str = ( - self.parent.customNames[name] if name in self.parent.customNames else name + self.updateText( + self.fullName if self.workspace.focused else self.workspace.name ) def switchTo(self) -> None: - self.parent.i3.command("workspace {}".format(self.shortName)) + self.parent.i3.command("workspace {}".format(self.workspace.name)) - def __init__(self, name: str, parent: "I3WorkspacesProvider"): + def updateWorkspace(self, workspace: i3ipc.WorkspaceReply) -> None: + self.workspace = workspace + self.fullName: str = self.parent.customNames.get(workspace.name, workspace.name) + self.show() + + def __init__(self, parent: "I3WorkspacesProvider"): Section.__init__(self) self.parent = parent - self.setName(name) self.setDecorators(clickLeft=self.switchTo) self.tempText: Element = None @@ -718,64 +711,50 @@ class I3WorkspacesProviderSection(Section): class I3WorkspacesProvider(Section, I3Updater): - # TODO FEAT Multi-screen - def initialPopulation(self, parent: BarGroup) -> None: - """ - Called on init - Can't reuse addWorkspace since i3.get_workspaces() gives dict and not - ConObjects - """ - workspaces = self.i3.get_workspaces() - lastSection = self.modeSection - for workspace in workspaces: - # if parent.display != workspace["display"]: - # continue - - section = I3WorkspacesProviderSection(workspace.name, self) - section.focused = workspace.focused - section.urgent = workspace.urgent - section.show() - parent.addSectionAfter(lastSection, section) - self.sections[workspace.num] = section - - lastSection = section - - def on_workspace_init(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None: - workspace = e.current - i = workspace.num - if i in self.sections: - section = self.sections[i] + def updateWorkspace(self, workspace: i3ipc.WorkspaceReply) -> None: + section: Section | None = None + lastSectionOnOutput = self.modeSection + highestNumOnOutput = -1 + for sect in self.sections.values(): + if sect.workspace.num == workspace.num: + section = sect + break + elif ( + sect.workspace.num > highestNumOnOutput + and sect.workspace.num < workspace.num + and sect.workspace.output == workspace.output + ): + lastSectionOnOutput = sect + highestNumOnOutput = sect.workspace.num else: - # Find the section just before - while i not in self.sections.keys() and i > 0: - i -= 1 - prevSection = self.sections[i] if i != 0 else self.modeSection - - section = I3WorkspacesProviderSection(workspace.name, self) - prevSection.appendAfter(section) + section = I3WorkspacesProviderSection(self) self.sections[workspace.num] = section - section.focused = workspace.focused - section.urgent = workspace.urgent - section.show() + + for bargroup in self.parents: + if bargroup.parent.output == workspace.output: + break + else: + bargroup = list(self.parents)[0] + bargroup.addSectionAfter(lastSectionOnOutput, section) + section.updateWorkspace(workspace) + + def updateWorkspaces(self) -> None: + workspaces = self.i3.get_workspaces() + for workspace in workspaces: + self.updateWorkspace(workspace) + + def added(self) -> None: + super().added() + self.appendAfter(self.modeSection) + self.updateWorkspaces() + + def on_workspace_change(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None: + self.updateWorkspaces() def on_workspace_empty(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None: self.sections[e.current.num].empty() - def on_workspace_focus(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None: - self.sections[e.old.num].focused = False - self.sections[e.old.num].show() - self.sections[e.current.num].focused = True - self.sections[e.current.num].show() - - def on_workspace_urgent(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None: - self.sections[e.current.num].urgent = e.current.urgent - self.sections[e.current.num].show() - - def on_workspace_rename(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None: - self.sections[e.current.num].setName(e.name) - self.sections[e.current.num].show() - def on_mode(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None: if e.change == "default": self.modeSection.updateText(None) @@ -789,6 +768,7 @@ class I3WorkspacesProvider(Section, I3Updater): def __init__( self, theme: int = 0, + themeVisible: int = 4, themeFocus: int = 3, themeUrgent: int = 1, themeMode: int = 2, @@ -799,24 +779,23 @@ class I3WorkspacesProvider(Section, I3Updater): self.themeNormal = theme self.themeFocus = themeFocus self.themeUrgent = themeUrgent + self.themeVisible = themeVisible self.customNames = customNames - self.sections: dict[str, I3WorkspacesProviderSection] = dict() - self.on("workspace::init", self.on_workspace_init) - self.on("workspace::focus", self.on_workspace_focus) + self.sections: dict[int, I3WorkspacesProviderSection] = dict() + # The event object doesn't have the visible property, + # so we have to fetch the list of workspaces anyways. + # This sacrifices a bit of performance for code simplicity. + self.on("workspace::init", self.on_workspace_change) + self.on("workspace::focus", self.on_workspace_change) self.on("workspace::empty", self.on_workspace_empty) - self.on("workspace::urgent", self.on_workspace_urgent) - self.on("workspace::rename", self.on_workspace_rename) + self.on("workspace::urgent", self.on_workspace_change) + self.on("workspace::rename", self.on_workspace_change) # TODO Un-handled/tested: reload, rename, restored, move self.on("mode", self.on_mode) self.modeSection = Section(theme=themeMode) - def addParent(self, parent: BarGroup) -> None: - self.parents.add(parent) - parent.addSection(self.modeSection) - self.initialPopulation(parent) - class MpdProvider(Section, ThreadedUpdater): # TODO FEAT More informations and controls diff --git a/hm/desktop/frobar/frobar/updaters.py b/hm/desktop/frobar/frobar/updaters.py index e2a735f..1afc016 100644 --- a/hm/desktop/frobar/frobar/updaters.py +++ b/hm/desktop/frobar/frobar/updaters.py @@ -12,7 +12,7 @@ import i3ipc import pyinotify from frobar.display import Element -from frobar.notbusy import notBusy +from frobar.common import notBusy coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s") log = logging.getLogger()