Skip to content

Plugin IPC System

The Noctalia plugin system allows plugins to register their own IPC handlers, making them accessible via the shell’s IPC interface. This enables external scripts, keybindings, and other tools to interact with your plugin programmatically.

IPC (Inter-Process Communication) handlers allow your plugin to respond to commands from external sources. This is useful for:

  • Triggering plugin actions from keybindings
  • Integrating with external scripts and tools
  • Automating plugin behavior
  • Creating command-line interfaces for your plugin

When a plugin is loaded, its IPC handlers are automatically registered and can be called using the standard Noctalia IPC command format.

Plugins register IPC handlers in their Main.qml file using the IpcHandler component from Quickshell. Each handler function becomes a callable command that can be invoked from the command line or other IPC clients.

The IPC system routes commands to the appropriate plugin handler based on the target prefix plugin:your-plugin-id.

In your plugin’s Main.qml file, register IPC handlers using the IpcHandler component:

import QtQuick
import Quickshell.Io
import qs.Services.UI
Item {
property var pluginApi: null
IpcHandler {
target: "plugin:your-plugin-id"
function yourCommand() {
// Your handler logic here
}
}
}

The target property must follow this format:

  • Prefix: Always use "plugin:" as the prefix
  • Plugin ID: Follow with your plugin’s ID from manifest.json
  • Example: If your plugin ID is "hello-world", use "plugin:hello-world"

The pluginApi property is automatically injected by the plugin system and provides access to:

  • Settings: pluginApi.pluginSettings - Read and write plugin settings
  • Persistence: pluginApi.saveSettings() - Save settings to disk
  • Panels: pluginApi.openPanel(screen) / pluginApi.closePanel(screen) - Control plugin panels
  • Translations: pluginApi.tr(key, interpolations) - Translate text
  • Metadata: pluginApi.manifest - Access plugin manifest data

For complete API documentation, see the Plugin API Reference.

A basic handler that updates a plugin setting:

import QtQuick
import Quickshell.Io
import qs.Services.UI
Item {
property var pluginApi: null
IpcHandler {
target: "plugin:hello-world"
function setMessage(message: string) {
if (pluginApi && message) {
pluginApi.pluginSettings.message = message;
pluginApi.saveSettings();
ToastService.showNotice("Message updated to: " + message);
}
}
}
}

This handler can be called from the command line:

Terminal window
qs -c noctalia-shell ipc call plugin:hello-world setMessage "Hello from IPC!"

You can define multiple functions within a single IpcHandler:

IpcHandler {
target: "plugin:my-plugin"
function toggle() {
if (!pluginApi) return;
pluginApi.pluginSettings.enabled = !pluginApi.pluginSettings.enabled;
pluginApi.saveSettings();
}
function enable() {
if (!pluginApi) return;
pluginApi.pluginSettings.enabled = true;
pluginApi.saveSettings();
}
function disable() {
if (!pluginApi) return;
pluginApi.pluginSettings.enabled = false;
pluginApi.saveSettings();
}
function setValue(value: string) {
if (!pluginApi) return;
var numValue = parseFloat(value);
if (!Number.isNaN(numValue)) {
pluginApi.pluginSettings.value = numValue;
pluginApi.saveSettings();
}
}
}

IPC handler functions can accept parameters. When called via IPC, all arguments are passed as strings and should be parsed as needed:

IpcHandler {
target: "plugin:my-plugin"
function setValue(value: string) {
// Parse string to number
var numValue = parseFloat(value);
if (Number.isNaN(numValue)) {
Logger.w("MyPlugin", "Invalid value:", value);
return;
}
pluginApi.pluginSettings.value = numValue;
pluginApi.saveSettings();
}
function setMultiple(param1: string, param2: string) {
// Handle multiple parameters
pluginApi.pluginSettings.param1 = param1;
pluginApi.pluginSettings.param2 = param2;
pluginApi.saveSettings();
}
}

Once your plugin is loaded, you can call its IPC handlers using the standard Noctalia IPC command format:

Terminal window
qs -c noctalia-shell ipc call plugin:your-plugin-id commandName [arguments]
Terminal window
# Call a command with a string argument
qs -c noctalia-shell ipc call plugin:hello-world setMessage "Hello from IPC!"
# Call a command with no arguments
qs -c noctalia-shell ipc call plugin:my-plugin toggle
# Call a command with a numeric argument (passed as string)
qs -c noctalia-shell ipc call plugin:my-plugin setValue "42"

IPC commands can be bound to keyboard shortcuts in your Noctalia configuration:

{
"keybinds": {
"Super+Shift+P": "qs -c noctalia-shell ipc call plugin:my-plugin toggle"
}
}

Always validate and parse input parameters before use:

function setValue(value: string) {
if (!pluginApi) {
Logger.e("MyPlugin", "Plugin API not available");
return;
}
var numValue = parseFloat(value);
if (Number.isNaN(numValue)) {
Logger.w("MyPlugin", "Invalid numeric value:", value);
ToastService.showError("Invalid value: " + value);
return;
}
pluginApi.pluginSettings.value = numValue;
pluginApi.saveSettings();
}

Check for null/undefined values and handle errors gracefully:

function updateSetting(value: string) {
if (!pluginApi) {
Logger.e("MyPlugin", "Plugin API not available");
return;
}
if (!value || value.trim() === "") {
Logger.w("MyPlugin", "Value is required");
return;
}
// Proceed with update...
pluginApi.pluginSettings.setting = value;
pluginApi.saveSettings();
}

Use ToastService to provide feedback for important operations:

import qs.Services.UI
function updateSettings() {
// ... update logic ...
ToastService.showNotice("Settings updated successfully");
// or
ToastService.showError("Failed to update settings");
}

Use the Logger for debugging and error tracking:

import qs.Commons
function handleCommand(value: string) {
Logger.d("MyPlugin", "Command called with value:", value);
Logger.i("MyPlugin", "Settings updated");
Logger.w("MyPlugin", "Warning: invalid input");
Logger.e("MyPlugin", "Error occurred:", error);
}

Leverage the pluginApi for common operations:

// Save settings
pluginApi.saveSettings();
// Open/close panels
if (Quickshell.screens.length > 0) {
var screen = Quickshell.screens[0];
pluginApi.openPanel(screen);
pluginApi.closePanel(screen);
}
// Access translations
var text = pluginApi.tr("settings.enabled");

Here’s a complete example showing a plugin with multiple IPC handlers:

import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.UI
Item {
property var pluginApi: null
IpcHandler {
target: "plugin:example-plugin"
// Toggle plugin state
function toggle() {
if (!pluginApi) return;
pluginApi.pluginSettings.enabled = !pluginApi.pluginSettings.enabled;
pluginApi.saveSettings();
var status = pluginApi.pluginSettings.enabled ? "enabled" : "disabled";
ToastService.showNotice("Plugin " + status);
Logger.i("ExamplePlugin", "Toggled to:", status);
}
// Enable plugin
function enable() {
if (!pluginApi) return;
pluginApi.pluginSettings.enabled = true;
pluginApi.saveSettings();
ToastService.showNotice("Plugin enabled");
}
// Disable plugin
function disable() {
if (!pluginApi) return;
pluginApi.pluginSettings.enabled = false;
pluginApi.saveSettings();
ToastService.showNotice("Plugin disabled");
}
// Set a numeric value
function setValue(value: string) {
if (!pluginApi) return;
var numValue = parseFloat(value);
if (Number.isNaN(numValue)) {
Logger.w("ExamplePlugin", "Invalid value:", value);
ToastService.showError("Invalid value: " + value);
return;
}
pluginApi.pluginSettings.value = numValue;
pluginApi.saveSettings();
ToastService.showNotice("Value set to: " + numValue);
}
// Open plugin panel
function openPanel() {
if (!pluginApi || Quickshell.screens.length === 0) return;
var screen = Quickshell.screens[0];
pluginApi.openPanel(screen);
}
}
}

After creating your IPC handlers, follow these steps to test them:

  1. Reload your plugin: Disable and re-enable your plugin in Noctalia settings to ensure the handlers are registered
  2. Test via command line: Use the IPC command format to call your handlers
    Terminal window
    qs -c noctalia-shell ipc call plugin:your-plugin-id yourCommand
  3. Check logs: Monitor Quickshell logs for any errors or debug messages
  4. Verify behavior: Ensure your plugin responds correctly to IPC calls

If your IPC handler isn’t responding:

  • Verify target name: Ensure it’s exactly "plugin:your-plugin-id" (with the plugin: prefix)
  • Check plugin status: Verify that your plugin is enabled and loaded
  • Function name: Function names are case-sensitive - ensure exact matches
  • Review logs: Check Quickshell/Noctalia logs for registration errors

If parameters aren’t being received correctly:

  • String conversion: Remember that all IPC arguments are strings - parse them as needed
  • Parameter count: Ensure the number of parameters matches your function signature
  • Type validation: Always validate parameter types before use

If pluginApi is null or undefined:

  • Timing: The pluginApi is injected after component creation - add null checks
  • Null checks: Always verify pluginApi is available before using it
  • Component lifecycle: IPC handlers remain active as long as the plugin is loaded