#!/usr/bin/env python3 """orbit-menu — a radial power/utility menu for hyprlua. Two entry points into the same tree: Super+Shift+O -> --power opens straight into the Power ring (sleep / reboot / power off / soft reboot, via hyprshutdown) Super+Ctrl+O -> --menu opens the 5-category root (Power, Tools, Scripts, Management, Windows) Single-instance, same pattern as astal-menu/main.py: the first launch builds the (hidden) window and holds; later invocations forward their verb over D-Bus via scripts/orbit-menu.sh instead of spawning a second python3+GTK4 process. main.py run the resident instance (stays hidden until toggled) main.py --power show, rooted at Power main.py --menu show, rooted at the full category menu main.py --hide hide """ from __future__ import annotations import os import sys from pathlib import Path # orbit-menu-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 astal-menu/main.py for the same rationale — # some GTK4 apps abort at startup with the layer-shell lib preloaded). 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") from gi.repository import Gio, GLib, Gtk # noqa: E402 import menu_tree # noqa: E402 import theme # noqa: E402 from orbit_menu import OrbitMenu # noqa: E402 from paths import APP_ID # noqa: E402 class OrbitMenuApp(Gtk.Application): def __init__(self) -> None: super().__init__(application_id=APP_ID, flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) self.window: OrbitMenu | None = None self._current_mode: str | None = None # "power" | "menu" def do_startup(self) -> None: Gtk.Application.do_startup(self) theme.load_css() self.window = OrbitMenu(menu_tree.full_menu_root(), on_close=self._on_closed) self._current_mode = "menu" self._register_actions() self.hold() # stay alive with no visible window def _on_closed(self) -> None: pass # nothing to do; the window just hides itself def _open(self, mode: str) -> None: assert self.window is not None if self.window.get_visible() and self._current_mode == mode: self.window.set_visible(False) return if self._current_mode != mode: root = menu_tree.power_only_root() if mode == "power" else menu_tree.full_menu_root() self.window.set_root(root) self._current_mode = mode self.window.open_at_root() def _register_actions(self) -> None: def add(name: str, callback) -> None: action = Gio.SimpleAction.new(name, None) action.connect("activate", callback) self.add_action(action) def on_hide(*_a) -> None: assert self.window is not None self.window.set_visible(False) add("power", lambda *_a: self._open("power")) add("menu", lambda *_a: self._open("menu")) add("hide", on_hide) def do_command_line(self, cmdline: Gio.ApplicationCommandLine) -> int: args = list(cmdline.get_arguments()[1:]) verb = args[0] if args else "--daemon" if self.window is None: return 0 if verb == "--power": self._open("power") elif verb == "--menu": self._open("menu") elif verb == "--hide": self.window.set_visible(False) # --daemon and anything else: no-op (stay resident, hidden) return 0 def do_activate(self) -> None: pass # resident instance: nothing to do on plain activate def main() -> int: GLib.set_prgname("orbit-menu") return OrbitMenuApp().run(sys.argv) if __name__ == "__main__": raise SystemExit(main())