From 9ff5ebc95c55b24f8a71fe7fbbb49f9fea1d44c6 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Tue, 14 Jul 2026 13:02:26 +0545 Subject: [PATCH] refactor(spotlight): replace raw socket IPC with Fabric Service over DBus SpotlightWindow now exposed via SpotlightService (fabric Service) on the session bus (dev.modus.Spotlight /Toggle). On-demand process forwards to the running instance over DBus instead of a unix socket; still quits on dismiss. Behaviour unchanged. --- src/window/spotlight/app.py | 147 ++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/window/spotlight/app.py diff --git a/src/window/spotlight/app.py b/src/window/spotlight/app.py new file mode 100644 index 0000000..bcf1b4d --- /dev/null +++ b/src/window/spotlight/app.py @@ -0,0 +1,147 @@ +from fabric import Application +from fabric.core.service import Service +from fabric.utils import GLib, get_relative_path, logger +from gi.repository import Gio + +from shared.data import load_config +from utils.functions import set_process_name +from window.spotlight.main import SpotlightWindow + +BUS_NAME = "dev.modus.Spotlight" +OBJECT_PATH = "/dev/modus/Spotlight" +INTERFACE = "dev.modus.Spotlight" + +_INTROSPECTION = f""" + + + + + + + +""" + + +class SpotlightService(Service): + """DBus-exposed controller for the running spotlight instance. + + Owns no state of its own; it forwards IPC calls to the live window. + """ + + def __init__(self, window: SpotlightWindow, **kwargs): + super().__init__(**kwargs) + self._window = window + self._connection = Gio.bus_get_sync(Gio.BusType.SESSION) + self._reg_id = self._connection.register_object( + OBJECT_PATH, + Gio.DBusNodeInfo.new_for_xml(_INTROSPECTION).interfaces[0], + self._on_method_call, + ) + + def _on_method_call( + self, + _conn, + _sender, + _path, + _iface, + method, + params, + invocation, + ): + if method == "Toggle": + command, text, external = params.unpack() + GLib.idle_add(lambda: self._window.toggle(command, text, external=external)) + invocation.return_value(None) + + def release(self): + if self._reg_id: + self._connection.unregister_object(self._reg_id) + self._reg_id = 0 + + +def _request_name() -> int: + """Try to own the spotlight bus name. Returns the RequestName reply code + (1 == primary owner).""" + conn = Gio.bus_get_sync(Gio.BusType.SESSION) + reply = conn.call_sync( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "RequestName", + GLib.Variant("(su)", (BUS_NAME, 0)), + GLib.VariantType.new("(u)"), + Gio.DBusCallFlags.NONE, + -1, + None, + ) + return reply.get_child_value(0).get_uint32() + + +def _forward_to_running(command: str, text: str, external: bool) -> bool: + """Hand the command to an already-running instance. Return True on success.""" + try: + conn = Gio.bus_get_sync(Gio.BusType.SESSION) + conn.call_sync( + BUS_NAME, + OBJECT_PATH, + INTERFACE, + "Toggle", + GLib.Variant("(ssb)", (command, text, external)), + None, + Gio.DBusCallFlags.NONE, + -1, + None, + ) + return True + except GLib.Error as e: + logger.warning( + f"[Spotlight] failed to forward to running instance: {e.message}" + ) + return False + + +def main(command: str = "", text: str = "", external: bool = False): + # If an instance already owns the bus name, let it handle the toggle and + # exit. This is the same on-demand forwarding the old socket did. + if _request_name() != 1: + _forward_to_running(command, text, external) + return + + set_process_name("modus-spotlight") + load_config() + + spotlight = SpotlightWindow() + + global app + app = Application("modus-spotlight", spotlight) + + def set_css(): + app.set_stylesheet_from_file(get_relative_path("../../styles/main.css")) + + app.set_css = set_css + app.set_css() + + # Kill the process on every dismissal so nothing lingers in memory. + # close_spotlight is the single close chokepoint (Escape, result launch, + # and toggle-when-visible all route through it). + _orig_close = spotlight.close_spotlight + + def _close_and_quit(): + _orig_close() + app.quit() + + spotlight.close_spotlight = _close_and_quit + + service = SpotlightService(spotlight) + + spotlight.toggle(command, text, external=external) + + # External one-shot commands never show the window; give any async action a + # moment to fire, then exit so we do not stay resident. + if external and not spotlight.get_visible(): + GLib.timeout_add(2000, app.quit) + + try: + app.run() + finally: + service.release()