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.
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:
- Your plugin calls
SetContextActionsto register actions for a specificapp_id - The user focuses your application
- Launch detects the focused app and shows your registered actions as buttons
- The user clicks a button (or presses
Alt+1,Alt+2, etc.) - Launch emits an
ActionTriggeredsignal with theapp_idandaction_id - Your plugin receives the signal and performs the corresponding action
| Property | Value |
|---|---|
| Bus Name | org.leechristophermurray.Launch.Plugin |
| Object Path | /org/leechristophermurray/Launch/Plugin |
| Interface | org.leechristophermurray.Launch.Plugin |
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_idis an internal identifier you define (e.g.,"brightness","new_layer")labelis the text displayed on the button (keep it short — buttons are small)- Calling
SetContextActionsagain for the sameapp_idreplaces all previous actions - Pass an empty array to remove all actions for your
app_id
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.
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 |
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 |
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'"pip install dbus-python PyGObjectThese are pre-installed on most GNOME-based distributions (Fedora, Ubuntu, etc.).
#!/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.
You can test the Plugin API without writing any code using dbus-send:
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"dbus-send --session --print-reply \
--dest=org.leechristophermurray.Launch.Plugin \
/org/leechristophermurray/Launch/Plugin \
org.freedesktop.DBus.Introspectable.Introspectdbus-monitor "interface='org.leechristophermurray.Launch.Plugin'"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.
Launch ships with several integrations that use the same Plugin API patterns. These serve as real-world reference implementations:
- 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 forActionTriggeredto execute VS Code commands
- 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
- 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.Shellservice that Launch consumes for focus tracking
-
Use the correct
app_id: It must match your.desktopfile name (minus the.desktopsuffix). If the ID doesn't match, your actions will never appear. -
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.
-
Clean up on exit: Call
SetContextActionswith an empty array when your plugin shuts down to remove stale actions. -
Slider values are normalized: The
initial_valueandvalueinGestureDatarange from0.0to1.0. Map this to your application's actual range (e.g.,0.0–1.0→0–255for brightness). -
Debounce gesture data:
GestureDatafires continuously during touchpad swipes. If your action is expensive (e.g., applying an image filter), debounce or throttle the incoming values. -
Filter by
app_id: Signal receivers get all signals, not just yours. Always checkapp_idmatches your application before processing. -
Handle Launch not running: Wrap your
SetContextActionscall in a try/except. Your plugin should degrade gracefully if Launch isn't running.
- Start Launch:
cargo runorlaunch toggle - Run your plugin script:
python3 my_plugin.py - Focus your application: Switch to the window whose
app_idmatches your plugin's - Verify actions appear: Your action buttons should show up in Launch's right region
- Click an action: Your script should print the
ActionTriggeredcallback - Test sliders: If your action requests a slider, verify
GestureDatasignals arrive when you swipe on your touchpad
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.