Skip to content

Latest commit

 

History

History
319 lines (230 loc) · 12.1 KB

File metadata and controls

319 lines (230 loc) · 12.1 KB

Launch Plugin Developer Guide

This guide explains how to build plugins for Launch using the D-Bus Plugin API. Any Linux application can integrate with Launch to register context-specific actions, interactive sliders, and touchpad gesture controls.

How It Works

Launch is a context-aware application launcher. When a user focuses your application, Launch can display custom action buttons in its right region. When the user clicks one of those buttons, your plugin receives a callback signal.

Your App / Plugin Script
        |
        | D-Bus (Session Bus)
        v
Launch (org.leechristophermurray.Launch.Plugin)
        |
        | User clicks your action
        v
ActionTriggered signal → Your App

Lifecycle:

  1. Your plugin calls SetContextActions to register actions for a specific app_id
  2. The user focuses your application
  3. Launch detects the focused app and shows your registered actions as buttons
  4. The user clicks a button (or presses Alt+1, Alt+2, etc.)
  5. Launch emits an ActionTriggered signal with the app_id and action_id
  6. Your plugin receives the signal and performs the corresponding action

D-Bus Interface Reference

Property Value
Bus Name org.leechristophermurray.Launch.Plugin
Object Path /org/leechristophermurray/Launch/Plugin
Interface org.leechristophermurray.Launch.Plugin

Methods

SetContextActions

Register context action buttons for your application. These appear in Launch's right region when your app is focused.

Parameter Type Description
app_id String (s) Your application's freedesktop ID (e.g., org.gimp.GIMP)
actions Array<(String, String)> (a(ss)) Array of (action_id, label) tuples
  • action_id is an internal identifier you define (e.g., "brightness", "new_layer")
  • label is the text displayed on the button (keep it short — buttons are small)
  • Calling SetContextActions again for the same app_id replaces all previous actions
  • Pass an empty array to remove all actions for your app_id

RequestSlider

Request that Launch display an interactive slider. The slider replaces the text entry and captures touchpad gestures.

Parameter Type Description
app_id String (s) Your application's freedesktop ID
action_id String (s) Which action this slider corresponds to
label String (s) Label displayed above the slider
initial_value Double (d) Starting value, normalized 0.0 to 1.0

Once the slider is active, Launch emits GestureData signals as the user swipes on their touchpad or drags the slider.

Signals

ActionTriggered

Emitted when the user clicks one of your registered action buttons.

Parameter Type Description
app_id String (s) The application ID the action belongs to
action_id String (s) The ID of the action that was triggered

GestureData

Emitted continuously while the user interacts with a slider (touchpad swipe or drag).

Parameter Type Description
app_id String (s) The application ID
action_id String (s) The action ID associated with the slider
value Double (d) Current slider value (0.0 to 1.0)
delta_x Double (d) Horizontal touchpad swipe delta
delta_y Double (d) Vertical touchpad swipe delta

Understanding app_id

The app_id is critical — it determines when your actions are visible. Launch shows your actions only when the window with a matching app_id is focused.

The app_id must match the .desktop file name (without the .desktop suffix) of your application. For example:

Application Desktop File app_id to Use
GIMP org.gimp.GIMP.desktop org.gimp.GIMP
Firefox org.mozilla.firefox.desktop org.mozilla.firefox
Nautilus org.gnome.Nautilus.desktop org.gnome.Nautilus
VS Code com.visualstudio.code.desktop com.visualstudio.code
Google Chrome google-chrome.desktop google-chrome

To find the app_id for any application, check its .desktop file in /usr/share/applications/ or ~/.local/share/applications/.

You can also use dbus-monitor to see what app_id Launch detects for a focused window:

dbus-monitor "interface='org.leechristophermurray.Launch.Shell',member='FocusChanged'"

Quick Start: Python Plugin

Prerequisites

pip install dbus-python PyGObject

These are pre-installed on most GNOME-based distributions (Fedora, Ubuntu, etc.).

Minimal Example

#!/usr/bin/env python3
"""Minimal Launch plugin — registers two actions and listens for callbacks."""

import dbus
import dbus.mainloop.glib
from gi.repository import GLib

BUS_NAME = "org.leechristophermurray.Launch.Plugin"
OBJECT_PATH = "/org/leechristophermurray/Launch/Plugin"
INTERFACE = "org.leechristophermurray.Launch.Plugin"

# Replace with your application's app_id
APP_ID = "org.example.MyApp"


def on_action_triggered(app_id, action_id):
    """Called when the user clicks one of our action buttons."""
    if app_id != APP_ID:
        return
    print(f"Action triggered: {action_id}")

    if action_id == "brightness":
        # Request a slider when brightness is clicked
        bus = dbus.SessionBus()
        proxy = bus.get_object(BUS_NAME, OBJECT_PATH)
        iface = dbus.Interface(proxy, INTERFACE)
        iface.RequestSlider(APP_ID, "brightness", "Brightness", 0.5)


def on_gesture_data(app_id, action_id, value, delta_x, delta_y):
    """Called continuously while the user interacts with a slider."""
    if app_id != APP_ID:
        return
    print(f"Slider: {action_id} = {value:.3f} (dx={delta_x:.3f}, dy={delta_y:.3f})")


def main():
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SessionBus()

    # Listen for signals from Launch
    bus.add_signal_receiver(
        on_action_triggered,
        signal_name="ActionTriggered",
        dbus_interface=INTERFACE,
        path=OBJECT_PATH,
    )
    bus.add_signal_receiver(
        on_gesture_data,
        signal_name="GestureData",
        dbus_interface=INTERFACE,
        path=OBJECT_PATH,
    )

    # Register our context actions
    try:
        proxy = bus.get_object(BUS_NAME, OBJECT_PATH)
        iface = dbus.Interface(proxy, INTERFACE)
        iface.SetContextActions(APP_ID, [
            ("brightness", "Brightness"),
            ("contrast", "Contrast"),
        ])
        print(f"Registered actions for {APP_ID}")
    except dbus.exceptions.DBusException as e:
        print(f"Could not register (is Launch running?): {e}")

    print("Listening for signals. Press Ctrl+C to quit.")
    loop = GLib.MainLoop()
    try:
        loop.run()
    except KeyboardInterrupt:
        print("\nShutting down.")


if __name__ == "__main__":
    main()

A more complete example is available at examples/sample_plugin.py in the Launch repository.

Quick Start: Command-Line Testing

You can test the Plugin API without writing any code using dbus-send:

Register Actions

dbus-send --session --type=method_call \
  --dest=org.leechristophermurray.Launch.Plugin \
  /org/leechristophermurray/Launch/Plugin \
  org.leechristophermurray.Launch.Plugin.SetContextActions \
  string:"org.example.MyApp" \
  array:struct:string:string:"brightness","Brightness","contrast","Contrast"

Introspect the API

dbus-send --session --print-reply \
  --dest=org.leechristophermurray.Launch.Plugin \
  /org/leechristophermurray/Launch/Plugin \
  org.freedesktop.DBus.Introspectable.Introspect

Monitor Signals

dbus-monitor "interface='org.leechristophermurray.Launch.Plugin'"

Quick Start: Other Languages

Any language with D-Bus bindings can interact with the Plugin API. Here are pointers for common languages:

Language Library Notes
Python dbus-python + PyGObject See example above
Rust dbus crate Same crate Launch itself uses
C libdbus or GDBus Low-level but widely available
Go godbus/dbus Pure Go implementation
Node.js dbus-next Pure JS, no native modules (used by the VS Code extension)
GJS Built-in Gio.DBus Used by GNOME Shell extensions

The D-Bus protocol is language-agnostic. As long as you can call methods and listen for signals on the session bus, your plugin will work.

Built-in Integrations as Reference

Launch ships with several integrations that use the same Plugin API patterns. These serve as real-world reference implementations:

VS Code Extension (vscode-extension/)

  • Language: TypeScript
  • D-Bus Library: dbus-next
  • What it does: Registers 5 context actions (New File, Terminal, Git Commit, Search, Format) for VS Code, VS Code Insiders, and Google Antigravity
  • Key file: vscode-extension/src/plugin-client.ts
  • Pattern: Detects editor type via vscode.env.appName, registers actions on activation, listens for ActionTriggered to execute VS Code commands

Chromium Extension (chromium-extension/ + browser-bridge/)

  • Language: JavaScript (Chrome extension) + Python (native messaging bridge)
  • What it does: Provides tab search, history search, and navigation controls for Chrome/Chromium
  • Key files: chromium-extension/background.js, browser-bridge/launch_browser_bridge.py
  • Pattern: Uses Chrome's Native Messaging API to bridge between the Chrome extension and D-Bus, since Chrome extensions cannot access D-Bus directly

GNOME Shell Extension (extension/)

  • Language: GJS (JavaScript)
  • What it does: Tracks window focus changes and registers a global hotkey to toggle Launch
  • Key file: extension/extension.js
  • Note: This is not a plugin in the Plugin API sense — it provides the org.leechristophermurray.Launch.Shell service that Launch consumes for focus tracking

Best Practices

  1. Use the correct app_id: It must match your .desktop file name (minus the .desktop suffix). If the ID doesn't match, your actions will never appear.

  2. Keep labels short: Action buttons render in a compact right region. Labels like "Brightness" or "New Tab" work well. Labels like "Adjust the brightness level of the current image" will be clipped.

  3. Clean up on exit: Call SetContextActions with an empty array when your plugin shuts down to remove stale actions.

  4. Slider values are normalized: The initial_value and value in GestureData range from 0.0 to 1.0. Map this to your application's actual range (e.g., 0.0–1.00–255 for brightness).

  5. Debounce gesture data: GestureData fires continuously during touchpad swipes. If your action is expensive (e.g., applying an image filter), debounce or throttle the incoming values.

  6. Filter by app_id: Signal receivers get all signals, not just yours. Always check app_id matches your application before processing.

  7. Handle Launch not running: Wrap your SetContextActions call in a try/except. Your plugin should degrade gracefully if Launch isn't running.

Testing Your Plugin

  1. Start Launch: cargo run or launch toggle
  2. Run your plugin script: python3 my_plugin.py
  3. Focus your application: Switch to the window whose app_id matches your plugin's
  4. Verify actions appear: Your action buttons should show up in Launch's right region
  5. Click an action: Your script should print the ActionTriggered callback
  6. Test sliders: If your action requests a slider, verify GestureData signals arrive when you swipe on your touchpad

Debugging

Use dbus-monitor to see all Plugin API traffic:

dbus-monitor "interface='org.leechristophermurray.Launch.Plugin'"

Verify Launch's Plugin service is running:

dbus-send --session --print-reply \
  --dest=org.freedesktop.DBus \
  /org/freedesktop/DBus \
  org.freedesktop.DBus.NameHasOwner \
  string:"org.leechristophermurray.Launch.Plugin"

This should return boolean true if Launch is running.