146 lines
5.7 KiB
Python
146 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""station-bar — the EWW top bar's replacement for hyprdrive (codename:
|
|
Voidstation Status Bar). Single-instance, same pattern as the rest of the
|
|
Cosmonaut Shell suite: the first launch builds the (visible, reserved-space)
|
|
bar and holds; later invocations forward their verb over D-Bus via
|
|
scripts/station-bar.sh instead of spawning a second python3+GTK4 process.
|
|
|
|
main.py run the resident instance (bar shown by default)
|
|
main.py --show show the bar (restores its exclusive zone)
|
|
main.py --hide hide the bar (releases its exclusive zone)
|
|
main.py --toggle whichever of the above applies
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# station-bar-start.sh LD_PRELOADs libgtk4-layer-shell (load-ordering
|
|
# requirement ahead of libwayland-client). Drop it once resident so it isn't
|
|
# inherited by anything this process launches (see orbit-menu/horizon-dock/
|
|
# astro-menu's main.py for the same rationale).
|
|
os.environ.pop("LD_PRELOAD", None)
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
import gi # noqa: E402
|
|
|
|
gi.require_version("Gtk", "4.0")
|
|
gi.require_version("Gdk", "4.0")
|
|
from gi.repository import Gdk, Gio, GLib, Gtk # noqa: E402
|
|
|
|
import config # noqa: E402
|
|
import theme # noqa: E402
|
|
from bar import StationBar # noqa: E402
|
|
from paths import APP_ID # noqa: E402
|
|
from sni_watcher import SniWatcher # noqa: E402
|
|
|
|
|
|
class StationBarApp(Gtk.Application):
|
|
def __init__(self) -> None:
|
|
super().__init__(application_id=APP_ID,
|
|
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
|
|
# One bar per monitor, keyed by connector name so hotplugged monitors can
|
|
# be matched on the "items-changed" signal.
|
|
self.bars: dict[str, StationBar] = {}
|
|
self._hologram = True
|
|
self._visible = True
|
|
self._sni_watcher: SniWatcher | None = None
|
|
|
|
def do_startup(self) -> None:
|
|
Gtk.Application.do_startup(self)
|
|
theme.load_css()
|
|
# Provide the StatusNotifierWatcher ourselves (no DE does it here), so tray
|
|
# apps have something to register with and the bars' tray fills up.
|
|
self._sni_watcher = SniWatcher()
|
|
self._hologram = config.hologram_enabled()
|
|
display = Gdk.Display.get_default()
|
|
if display is not None:
|
|
monitors = display.get_monitors()
|
|
monitors.connect("items-changed", lambda *_a: self._sync_bars())
|
|
self._sync_bars()
|
|
self._register_actions()
|
|
self.hold() # stay alive even while the bars are briefly hidden
|
|
|
|
def _sync_bars(self) -> None:
|
|
"""Build a bar for every current monitor (and drop bars for monitors that
|
|
went away) — a single layer-shell window only ever lands on one output, so
|
|
a per-monitor bar is the only way to get one on every screen."""
|
|
display = Gdk.Display.get_default()
|
|
if display is None:
|
|
return
|
|
monitors = display.get_monitors()
|
|
seen: set[str] = set()
|
|
for i in range(monitors.get_n_items()):
|
|
mon = monitors.get_item(i)
|
|
conn = (mon.get_connector() if mon is not None else None) or f"mon{i}"
|
|
seen.add(conn)
|
|
if conn not in self.bars:
|
|
bar = StationBar(hologram_enabled=self._hologram, monitor=mon)
|
|
if not self._visible:
|
|
bar.hide_bar()
|
|
self.bars[conn] = bar
|
|
for conn in list(self.bars):
|
|
if conn not in seen:
|
|
self.bars.pop(conn).destroy()
|
|
|
|
def _apply(self, verb: str, target: str = "") -> None:
|
|
"""target="" acts on every monitor's bar (global); a connector name acts
|
|
only on that monitor's bar (Super+Z toggles the bar you're looking at)."""
|
|
if target:
|
|
bars = [self.bars[target]] if target in self.bars else []
|
|
else:
|
|
bars = list(self.bars.values())
|
|
if verb == "show":
|
|
self._visible = True
|
|
elif verb == "hide":
|
|
self._visible = False
|
|
elif verb == "toggle":
|
|
self._visible = not self._visible
|
|
for bar in bars:
|
|
if verb == "show":
|
|
bar.show_bar()
|
|
elif verb == "hide":
|
|
bar.hide_bar()
|
|
elif verb == "toggle":
|
|
# per-monitor toggle keys off that bar's own state; the global
|
|
# toggle drives every bar from the shared _visible flag.
|
|
bar.toggle() if target else (bar.show_bar() if self._visible else bar.hide_bar())
|
|
|
|
def _register_actions(self) -> None:
|
|
stype = GLib.VariantType.new("s")
|
|
for name in ("show", "hide", "toggle"):
|
|
action = Gio.SimpleAction.new(name, stype)
|
|
action.connect(
|
|
"activate",
|
|
lambda _a, p, n=name: self._apply(n, p.get_string() if p is not None else ""),
|
|
)
|
|
self.add_action(action)
|
|
|
|
def do_command_line(self, cmdline: Gio.ApplicationCommandLine) -> int:
|
|
args = list(cmdline.get_arguments()[1:])
|
|
verb = args[0] if args else "--daemon"
|
|
target = args[1] if len(args) > 1 else ""
|
|
if verb == "--show":
|
|
self._apply("show", target)
|
|
elif verb == "--hide":
|
|
self._apply("hide", target)
|
|
elif verb == "--toggle":
|
|
self._apply("toggle", target)
|
|
# --daemon and anything else: no-op (bars already shown by do_startup)
|
|
return 0
|
|
|
|
def do_activate(self) -> None:
|
|
pass # resident instance: nothing to do on plain activate
|
|
|
|
|
|
def main() -> int:
|
|
GLib.set_prgname("station-bar")
|
|
return StationBarApp().run(sys.argv)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|