frobar: Mutli-display support

Freaking finally
This commit is contained in:
Geoffrey Frogeye 2024-06-18 04:09:59 +02:00
parent e09774c4ca
commit 445c2b8a99
Signed by: geoffrey
GPG key ID: C72403E7F82E6AD8
6 changed files with 108 additions and 103 deletions

View file

@ -1,11 +1,21 @@
{ pkgs ? import <nixpkgs> { config = { }; overlays = [ ]; }, ... }: { pkgs ? import <nixpkgs> { 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, # 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 { pkgs.python3Packages.buildPythonApplication {
pname = "frobar"; pname = "frobar";
version = "2.0"; version = "2.0";
runtimeInputs = with pkgs; [ lemonbar-xft wirelesstools ];
propagatedBuildInputs = with pkgs.python3Packages; [ propagatedBuildInputs = with pkgs.python3Packages; [
coloredlogs coloredlogs
notmuch notmuch
@ -15,7 +25,7 @@ pkgs.python3Packages.buildPythonApplication {
pulsectl pulsectl
pyinotify pyinotify
]; ];
makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath (with pkgs; [ lemonbar-xft wirelesstools ])}" ]; makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath ([ lemonbar ] ++ (with pkgs; [ wirelesstools ]))}" ];
src = ./.; src = ./.;
} }

View file

@ -12,6 +12,9 @@ def run() -> None:
Bar.init() Bar.init()
Updater.init() Updater.init()
# Bar.addSectionAll(fp.CpuProvider(), BarGroupType.RIGHT)
# Bar.addSectionAll(fp.NetworkProvider(theme=2), BarGroupType.RIGHT)
WORKSPACE_THEME = 8 WORKSPACE_THEME = 8
FOCUS_THEME = 2 FOCUS_THEME = 2
URGENT_THEME = 0 URGENT_THEME = 0

View file

@ -12,7 +12,7 @@ import typing
import coloredlogs import coloredlogs
import i3ipc import i3ipc
from frobar.notbusy import notBusy from frobar.common import notBusy
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s") coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
log = logging.getLogger() log = logging.getLogger()
@ -70,9 +70,19 @@ class Bar:
@staticmethod @staticmethod
def init() -> None: def init() -> None:
Bar.running = True Bar.running = True
Bar.everyone = set()
Section.init() Section.init()
cmd = ["lemonbar", "-b", "-a", "64"] cmd = [
"lemonbar",
"-b",
"-a",
"64",
"-F",
Section.FGCOLOR,
"-B",
Section.BGCOLOR,
]
for font in Bar.FONTS: for font in Bar.FONTS:
cmd += ["-f", "{}:size={}".format(font, Bar.FONTSIZE)] cmd += ["-f", "{}:size={}".format(font, Bar.FONTSIZE)]
Bar.process = subprocess.Popen( Bar.process = subprocess.Popen(
@ -80,9 +90,11 @@ class Bar:
) )
BarStdoutThread().start() BarStdoutThread().start()
# Debug i3 = i3ipc.Connection()
Bar(0) for output in i3.get_outputs():
# Bar(1) if not output.active:
continue
Bar(output.name)
@staticmethod @staticmethod
def stop() -> None: def stop() -> None:
@ -99,17 +111,15 @@ class Bar:
def doStop(*args: list) -> None: def doStop(*args: list) -> None:
Bar.stop() Bar.stop()
print(88)
try: try:
i3.on("ipc_shutdown", doStop) i3.on("ipc_shutdown", doStop)
i3.main() i3.main()
except BaseException: except BaseException:
print(93)
Bar.stop() Bar.stop()
# Class globals # Class globals
everyone: set["Bar"] = set() everyone: set["Bar"]
string = "" string = ""
process: subprocess.Popen process: subprocess.Popen
running = False running = False
@ -137,8 +147,8 @@ class Bar:
Bar.process.wait() Bar.process.wait()
Bar.stop() Bar.stop()
def __init__(self, screen: int) -> None: def __init__(self, output: str) -> None:
self.screen = "%{S" + str(screen) + "}" self.output = output
self.groups = dict() self.groups = dict()
for groupType in BarGroupType: for groupType in BarGroupType:
@ -146,27 +156,26 @@ class Bar:
self.groups[groupType] = group self.groups[groupType] = group
self.childsChanged = False self.childsChanged = False
Bar.everyone.add(self)
self.everyone.add(self)
@staticmethod @staticmethod
def addSectionAll( def addSectionAll(
section: "Section", group: "BarGroupType", screens: None = None section: "Section", group: "BarGroupType"
) -> None: ) -> None:
""" """
.. note:: .. note::
Add the section before updating it for the first time. Add the section before updating it for the first time.
""" """
# TODO screens selection
for bar in Bar.everyone: for bar in Bar.everyone:
bar.addSection(section, group=group) bar.addSection(section, group=group)
section.added()
def addSection(self, section: "Section", group: "BarGroupType") -> None: def addSection(self, section: "Section", group: "BarGroupType") -> None:
self.groups[group].addSection(section) self.groups[group].addSection(section)
def update(self) -> None: def update(self) -> None:
if self.childsChanged: if self.childsChanged:
self.string = self.screen self.string = "%{Sn" + self.output + "}"
self.string += self.groups[BarGroupType.LEFT].string self.string += self.groups[BarGroupType.LEFT].string
self.string += self.groups[BarGroupType.RIGHT].string self.string += self.groups[BarGroupType.RIGHT].string
@ -182,9 +191,10 @@ class Bar:
# Color for empty sections # Color for empty sections
Bar.string += BarGroup.color(*Section.EMPTY) Bar.string += BarGroup.color(*Section.EMPTY)
# print(Bar.string) string = Bar.string + "\n"
# print(string)
assert Bar.process.stdin assert Bar.process.stdin
Bar.process.stdin.write(bytes(Bar.string + "\n", "utf-8")) Bar.process.stdin.write(string.encode())
Bar.process.stdin.flush() Bar.process.stdin.flush()
@ -410,6 +420,9 @@ class Section:
for parent in self.parents: for parent in self.parents:
parent.addSectionAfter(self, section) parent.addSectionAfter(self, section)
def added(self) -> None:
pass
def informParentsThemeChanged(self) -> None: def informParentsThemeChanged(self) -> None:
for parent in self.parents: for parent in self.parents:
parent.childsThemeChanged = True parent.childsThemeChanged = True

View file

@ -18,7 +18,7 @@ import notmuch
import psutil import psutil
import pulsectl import pulsectl
from frobar.display import (BarGroup, ColorCountsSection, Element, Section, from frobar.display import (ColorCountsSection, Element, Section,
StatefulSection, Text) StatefulSection, Text)
from frobar.updaters import (I3Updater, InotifyUpdater, MergedUpdater, from frobar.updaters import (I3Updater, InotifyUpdater, MergedUpdater,
PeriodicUpdater, ThreadedUpdater, Updater) PeriodicUpdater, ThreadedUpdater, Updater)
@ -448,10 +448,6 @@ class NetworkProvider(Section, PeriodicUpdater):
return None return None
def addParent(self, parent: BarGroup) -> None:
self.parents.add(parent)
self.refreshData()
def __init__(self, theme: int | None = None): def __init__(self, theme: int | None = None):
PeriodicUpdater.__init__(self) PeriodicUpdater.__init__(self)
Section.__init__(self, theme) Section.__init__(self, theme)
@ -670,10 +666,12 @@ class I3WindowTitleProvider(Section, I3Updater):
class I3WorkspacesProviderSection(Section): class I3WorkspacesProviderSection(Section):
def selectTheme(self) -> int: def selectTheme(self) -> int:
if self.urgent: if self.workspace.urgent:
return self.parent.themeUrgent return self.parent.themeUrgent
elif self.focused: elif self.workspace.focused:
return self.parent.themeFocus return self.parent.themeFocus
elif self.workspace.visible:
return self.parent.themeVisible
else: else:
return self.parent.themeNormal return self.parent.themeNormal
@ -682,26 +680,21 @@ class I3WorkspacesProviderSection(Section):
def show(self) -> None: def show(self) -> None:
self.updateTheme(self.selectTheme()) self.updateTheme(self.selectTheme())
self.updateText(self.fullName if self.focused else self.shortName) self.updateText(
self.fullName if self.workspace.focused else self.workspace.name
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
) )
def switchTo(self) -> None: 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) Section.__init__(self)
self.parent = parent self.parent = parent
self.setName(name)
self.setDecorators(clickLeft=self.switchTo) self.setDecorators(clickLeft=self.switchTo)
self.tempText: Element = None self.tempText: Element = None
@ -718,64 +711,50 @@ class I3WorkspacesProviderSection(Section):
class I3WorkspacesProvider(Section, I3Updater): class I3WorkspacesProvider(Section, I3Updater):
# TODO FEAT Multi-screen
def initialPopulation(self, parent: BarGroup) -> None: def updateWorkspace(self, workspace: i3ipc.WorkspaceReply) -> None:
""" section: Section | None = None
Called on init lastSectionOnOutput = self.modeSection
Can't reuse addWorkspace since i3.get_workspaces() gives dict and not highestNumOnOutput = -1
ConObjects for sect in self.sections.values():
""" if sect.workspace.num == workspace.num:
workspaces = self.i3.get_workspaces() section = sect
lastSection = self.modeSection break
for workspace in workspaces: elif (
# if parent.display != workspace["display"]: sect.workspace.num > highestNumOnOutput
# continue and sect.workspace.num < workspace.num
and sect.workspace.output == workspace.output
section = I3WorkspacesProviderSection(workspace.name, self) ):
section.focused = workspace.focused lastSectionOnOutput = sect
section.urgent = workspace.urgent highestNumOnOutput = sect.workspace.num
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]
else: else:
# Find the section just before section = I3WorkspacesProviderSection(self)
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)
self.sections[workspace.num] = section self.sections[workspace.num] = section
section.focused = workspace.focused
section.urgent = workspace.urgent for bargroup in self.parents:
section.show() 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: def on_workspace_empty(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
self.sections[e.current.num].empty() 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: def on_mode(self, i3: i3ipc.Connection, e: i3ipc.Event) -> None:
if e.change == "default": if e.change == "default":
self.modeSection.updateText(None) self.modeSection.updateText(None)
@ -789,6 +768,7 @@ class I3WorkspacesProvider(Section, I3Updater):
def __init__( def __init__(
self, self,
theme: int = 0, theme: int = 0,
themeVisible: int = 4,
themeFocus: int = 3, themeFocus: int = 3,
themeUrgent: int = 1, themeUrgent: int = 1,
themeMode: int = 2, themeMode: int = 2,
@ -799,24 +779,23 @@ class I3WorkspacesProvider(Section, I3Updater):
self.themeNormal = theme self.themeNormal = theme
self.themeFocus = themeFocus self.themeFocus = themeFocus
self.themeUrgent = themeUrgent self.themeUrgent = themeUrgent
self.themeVisible = themeVisible
self.customNames = customNames self.customNames = customNames
self.sections: dict[str, I3WorkspacesProviderSection] = dict() self.sections: dict[int, I3WorkspacesProviderSection] = dict()
self.on("workspace::init", self.on_workspace_init) # The event object doesn't have the visible property,
self.on("workspace::focus", self.on_workspace_focus) # 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::empty", self.on_workspace_empty)
self.on("workspace::urgent", self.on_workspace_urgent) self.on("workspace::urgent", self.on_workspace_change)
self.on("workspace::rename", self.on_workspace_rename) self.on("workspace::rename", self.on_workspace_change)
# TODO Un-handled/tested: reload, rename, restored, move # TODO Un-handled/tested: reload, rename, restored, move
self.on("mode", self.on_mode) self.on("mode", self.on_mode)
self.modeSection = Section(theme=themeMode) 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): class MpdProvider(Section, ThreadedUpdater):
# TODO FEAT More informations and controls # TODO FEAT More informations and controls

View file

@ -12,7 +12,7 @@ import i3ipc
import pyinotify import pyinotify
from frobar.display import Element from frobar.display import Element
from frobar.notbusy import notBusy from frobar.common import notBusy
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s") coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
log = logging.getLogger() log = logging.getLogger()