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.
This commit is contained in:
1 file changed
+147
@@ -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"""<node>
|
||||
<interface name='{INTERFACE}'>
|
||||
<method name='Toggle'>
|
||||
<arg type='s' name='command' direction='in'/>
|
||||
<arg type='s' name='text' direction='in'/>
|
||||
<arg type='b' name='external' direction='in'/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>"""
|
||||
|
||||
|
||||
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()
|
||||
Reference in new issue
Block a user