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 = [ ]; }, ... }:
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.
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 = ./.;
}

View file

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

View file

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

View file

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

View file

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