#!/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())