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.
Overview
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.
How It Works
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.
Creating IPC Handlers
Basic Structure
In your plugin’s Main.qml file, register IPC handlers using the IpcHandler component:
Item {
property var pluginApi: null
IpcHandler {
target: "plugin:your-plugin-id"
function yourCommand() {
// Your handler logic here
}
}
}
Target Naming
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"
Tip The plugin ID in your target must exactly match the
idfield in yourmanifest.jsonfile.
Plugin API Access
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 - Launcher:
pluginApi.openLauncher(screen)/pluginApi.closeLauncher(screen)/pluginApi.toggleLauncher(screen)- Control the launcher for provider plugins - Translations:
pluginApi.tr(key, interpolations)- Translate text - Metadata:
pluginApi.manifest- Access plugin manifest data
For complete API documentation, see the Plugin API Reference.
Examples
Simple Command
A basic handler that updates a plugin setting:
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:
qs -c noctalia-shell ipc call plugin:hello-world setMessage "Hello from IPC!"
Multiple Commands
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();
}
}
}
Function Parameters
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();
}
}
Warning All IPC arguments are strings. Always parse numeric values, booleans, and other types as needed. Validate input before using it.
Calling IPC Commands
Once your plugin is loaded, you can call its IPC handlers using the standard Noctalia IPC command format:
qs -c noctalia-shell ipc call plugin:your-plugin-id commandName [arguments]
Command Examples
# 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"
Integration with Keybindings
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"
}
}
Best Practices
Input Validation
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();
}
Error Handling
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();
}
User Feedback
Use ToastService to provide feedback for important operations:
function updateSettings() {
// ... update logic ...
ToastService.showNotice("Settings updated successfully");
// or
ToastService.showError("Failed to update settings");
}
Logging
Use the Logger for debugging and error tracking:
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);
}
Using Plugin API
Leverage the pluginApi for common operations:
// Save settings
pluginApi.saveSettings();
// Open/close panels - use withCurrentScreen for proper screen selection
pluginApi.withCurrentScreen(screen => {
pluginApi.openPanel(screen);
});
// Or to close:
pluginApi.withCurrentScreen(screen => {
pluginApi.closePanel(screen);
});
// Toggle launcher for provider plugins
pluginApi.withCurrentScreen(screen => {
pluginApi.toggleLauncher(screen);
});
// Access translations
var text = pluginApi.tr("settings.enabled");
Tip The
withCurrentScreenhelper is the preferred way to get a screen reference in IPC handlers. It automatically selects the appropriate screen based on cursor position.
Complete Example
Here’s a complete example showing a plugin with multiple IPC handlers:
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) return;
pluginApi.withCurrentScreen(screen => {
pluginApi.openPanel(screen);
});
}
}
}
Launcher Provider IPC
If your plugin provides a launcher provider, you can use the launcher API methods to toggle the launcher from IPC. This works correctly in both normal and overlay launcher modes.
Item {
property var pluginApi: null
IpcHandler {
target: "plugin:my-provider"
function toggle() {
if (!pluginApi) return;
pluginApi.withCurrentScreen(screen => {
pluginApi.toggleLauncher(screen);
});
}
}
}
The toggleLauncher method uses the commandPrefix from your manifest’s metadata to determine the search prefix. For example, with "commandPrefix": "kaomoji", calling toggle will open the launcher with >kaomoji pre-filled.
# Toggle the launcher with your plugin's command prefix
qs -c noctalia-shell ipc call plugin:my-provider toggle
Tip For plugins that have both a panel and a launcher provider, you can provide separate IPC methods for each:
IpcHandler { target: "plugin:my-plugin" // Toggle the panel function togglePanel() { pluginApi.withCurrentScreen(screen => { pluginApi.togglePanel(screen); }); } // Toggle the launcher with the provider's command prefix function toggle() { pluginApi.withCurrentScreen(screen => { pluginApi.toggleLauncher(screen); }); } }
Testing
After creating your IPC handlers, follow these steps to test them:
- Reload your plugin: Disable and re-enable your plugin in Noctalia settings to ensure the handlers are registered
- Test via command line: Use the IPC command format to call your handlers
qs -c noctalia-shell ipc call plugin:your-plugin-id yourCommand - Check logs: Monitor Quickshell logs for any errors or debug messages
- Verify behavior: Ensure your plugin responds correctly to IPC calls
Troubleshooting
Handler Not Working
If your IPC handler isn’t responding:
- Verify target name: Ensure it’s exactly
"plugin:your-plugin-id"(with theplugin: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
Parameters Not Received
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
Plugin API Not Available
If pluginApi is null or undefined:
- Timing: The
pluginApiis injected after component creation - add null checks - Null checks: Always verify
pluginApiis available before using it - Component lifecycle: IPC handlers remain active as long as the plugin is loaded
See Also
- Getting Started - Plugin development basics
- Main Component - Overview of Main.qml
- Plugin API Reference - Complete API reference
- Manifest Reference - Plugin configuration