LUDASH / API

Every way to talk to LunaDash.

The dev branch exposes one owner-only Unix control socket, a versioned Settings API, the Quickshell shell object and Plugin SDK 2's synchronous C ABI. This page documents the contracts implemented by the current source tree.

This is a local session API, not HTTP. LunaDash binds a QLocalServer with user-only access. The website documents the API; it cannot control a running desktop.

Wire protocol and ludashctl

Processes launched by LunaDash inherit LUNADASH_CONTROL. The client still accepts the historical LUDASH_CONTROL, then searches the runtime directory for one unambiguous *-control socket. Prefer LUNADASH_CONTROL in new code.

TERMINAL
lunadashctl status
lunadashctl workspace 1
lunadashctl appearance '{"gap":16,"animations":true}'
lunadashctl quit confirm

The CLI sends one compact UTF-8 JSON object followed by a newline. method and value are strings, so structured payloads are JSON encoded inside value. The compositor accepts at most 65,536 request bytes, applies a three-second server timeout and returns one compact JSON object plus a newline.

WIRE REQUEST
{"method":"appearance","value":"{\"gap\":16}"}

Invalid JSON returns {"error":"Invalid request"}. An unsupported method/value returns {"error":"Invalid command or value"}. lunadashctl waits up to 1.5 seconds per socket read and refuses responses larger than 1 MiB.

DIRECT SOCKET / PYTHON
import json, os, socket

path = os.environ.get("LUNADASH_CONTROL") or os.environ.get("LUDASH_CONTROL")
if not path:
    raise RuntimeError("LUNADASH_CONTROL is not set")

request = {"method": "appearance", "value": json.dumps({"gap": 16})}
with socket.socket(socket.AF_UNIX) as client:
    client.settimeout(3)
    client.connect(path)
    client.sendall(json.dumps(request).encode() + b"\n")
    data = b""
    while b"\n" not in data:
        chunk = client.recv(65536)
        if not chunk or len(data) + len(chunk) > 1024 * 1024:
            raise RuntimeError("Invalid LunaDash response")
        data += chunk

response = json.loads(data.split(b"\n", 1)[0])
if "error" in response:
    raise RuntimeError(response["error"])
print(json.dumps(response, indent=2))

Status is the discovery API

Use status to discover client ids, display modes, connection names, plugin ids, available power/session actions and layout actions instead of hard-coding host-specific values.

FieldTypePurpose
clientsarrayWindow ids, metadata, geometry, workspace, focus/mapping and minimize/maximize/fullscreen state.
workspace / tiling / windowLayoutnumber / objectsCurrent workspace, layout snapshot, template id, overlap policy, settings schema/values and actions.
appearanceobjectEffective desktop preferences.
extensionsobjectInstalled plugins, remote Store catalogue, target registry, extensions document/path and Store state.
settingsApiobjectSettings API version and editable target descriptors; same payload as settings-describe.
shellModulesobjectModule document/descriptors/path, custom-code trust and runtime errors.
audio / network / powerobjectsService availability, devices/profiles, busy state and errors.
display / brightness / ddcBrightnessobjectsOutput modes/scale/pending confirmation, backlight state and DDC/CI monitors.
input / shortcutsobjectsKeyboard/backend/protocol state and effective shortcut map.
screenCaptureobjectzwlr_screencopy protocol/version, last path, busy phase and error.
xwaylandobjectXWayland compatibility and utility-surface state.
system / update / sessionActions / settingsToolsobjectsSystem state, updater, logind actions and validated settings helpers.
language / translationsstring / objectLocale and shell translation dictionary.
settingsSerial / pickerSerial / launcherSerialnumbersInteraction serials used by the shell to open UI surfaces.
graphicsApi / shaderReady / graphicsFailedstring / booleansRenderer-health summary for the active wlroots path.

Each ordinary status.clients[] entry includes a positive session-local id, title, app id, icon, desktop id, X11 flag, workspace, geometry, focus/minimize/maximize/fullscreen/mapped flags and buffer/content sizes. Do not persist these ids across compositor restarts.

Every control method

The tables are aligned with WaylandCompositor::control() on the dev branch. Examples use the CLI form; direct socket clients send the same method/value pair.

State and settings

MethodValueBehaviorExample
statusEmptyReturn the complete compositor snapshot: windows, workspaces, appearance, displays, input, audio, network, power, plugins, modules, capture state and Settings API descriptors.lunadashctl status
settings-describeEmptyReturn Settings API v1 and every editable target. Each descriptor contains id, schema, values and a SHA-256 revision token.lunadashctl settings-describe
settings-updateJSON {"target":"...","revision":"...","changes":{...}}Patch one Settings API target. The revision must still match; unknown fields, stale revisions, read-only changes and schema-invalid values are rejected. The request is capped at 24 KiB.lunadashctl settings-update '{"target":"builtin:window-animation","revision":"<revision>","changes":{"duration":260}}'
appearancePartial desktop-preferences JSONValidate and atomically apply a desktop preference patch; invalid or unknown fields reject the update.lunadashctl appearance '{"gap":16,"animations":true}'
reset-preferencesEmptyRemove the desktop preference group, then reapply keyboard configuration and layout. Plugin/module/default-app documents are separate.lunadashctl reset-preferences
open-settingsEmpty or a built-in page idOpen Settings at general, appearance, windows, shortcuts, display, input, input-method, sound, network, bluetooth, power, applications, privacy, system, devices, about, modules, plugins or dashboard.lunadashctl open-settings plugins

Windows and workspaces

MethodValueBehaviorExample
workspaceZero-based workspace indexSwitch to an existing workspace. The index must be within appearance.workspaceCount.lunadashctl workspace 1
focusPositive client idSwitch to the client's workspace, restore it if minimized, focus it in the layout and give the surface keyboard focus.lunadashctl focus 12
activate-windowPositive client idActivate a task/window through the compositor task activation path.lunadashctl activate-window 12
minimizePositive client idMinimize the client, update XWayland state when applicable, update the layout and synchronize focus.lunadashctl minimize 12
closePositive client idRequest a normal application close; this is not a process kill.lunadashctl close 12
group-windowJSON {"window":ID,"target":ID}Ask the active layout to group one window with another. The layout must support group-with.lunadashctl group-window '{"window":12,"target":7}'
expel-windowPositive client idAsk the active layout to expel a window from its current group.lunadashctl expel-window 12
window-layout-settingsJSON matching status.windowLayout.schemaPatch the active layout template settings and rearrange immediately.lunadashctl window-layout-settings '{"gap":12}'
window-layout-actionJSON {"action":"<id>","payload":{...}}Invoke an action advertised by status.windowLayout.actions. Only action and payload are accepted; required work-area data is injected by the compositor.lunadashctl window-layout-action '{"action":"expel","payload":{"window":12}}'
switch-windowPositive client idSelect a particular item in the active compositor window switcher.lunadashctl switch-window 12
switch-stepprevious or nextStep the switcher backward for previous, otherwise forward.lunadashctl switch-step next
switch-acceptEmptyAccept the current window-switch selection.lunadashctl switch-accept
switch-cancelEmptyCancel the current window-switch operation.lunadashctl switch-cancel
launcher-visibletrue or falseSynchronize launcher visibility with the compositor; primarily used by the LunaDash shell.lunadashctl launcher-visible true

Input and shortcuts

MethodValueBehaviorExample
shortcut-capturetrue or falseEnable/disable shortcut-capture mode while Settings records a key combination.lunadashctl shortcut-capture true
shortcutsPartial JSON mapping known action ids to shortcut stringsUpdate bindings. Values are Disabled or a Meta/Alt key combination; duplicates and Alt+Tab / Alt+Shift+Tab are rejected.lunadashctl shortcuts '{"launchTerminal":"Meta+Return","closeWindow":"Meta+Q"}'
reset-shortcutsEmptyRestore the built-in shortcut map, including workspace1..10 and moveToWorkspace1..10.lunadashctl reset-shortcuts
send-keycopy, paste, cut or selectAllInject the corresponding Ctrl chord into the currently focused wlroots keyboard surface.lunadashctl send-key copy

Applications and URLs

MethodValueBehaviorExample
default-appsJSON object of command arraysSave default terminal/files/browser command arrays. Empty terminal/files commands can use LunaDash fallbacks.lunadashctl default-apps '{"browser":["google-chrome-stable"]}'
launch-defaultterminal, files or browserLaunch the configured default. Terminal/files can fall back to LunaDash built-ins; browser requires an available browser command.lunadashctl launch-default browser
launch-commandNon-empty JSON command arrayLaunch a program directly without a shell. Every argument is a string up to 1024 characters.lunadashctl launch-command '["foot","-e","htop"]'
launch-with-x11JSON command array; CLI also accepts -- program argsLaunch through the external-command path with authenticated X11 compatibility prepared.lunadashctl launch-with-x11 -- xterm -e htop
launch-x11Command-line string parsed by QProcess::splitCommandLaunch directly through XWayland. This parses arguments; it does not evaluate shell operators.lunadashctl launch-x11 'xterm -e htop'
launch-applicationJSON exactly {"desktopId":"...","command":[...]}Launch a desktop-app command after capability resolution. desktopId <=256 chars; command has 1–64 non-empty strings, each <=1024 chars.lunadashctl launch-application '{"desktopId":"org.example.App","command":["example-app"]}'
open-urlURL stringResolve the configured browser command and open the URL.lunadashctl open-url https://github.com/LuYishan-4/LunaDash
system-toolId from status.settingsToolsOpen one validated system-settings helper; discover ids through status rather than guessing executables.lunadashctl system-tool '<tool-id>'

Plugins

MethodValueBehaviorExample
extension-saveComplete extensions schemaVersion 1 JSONValidate and atomically save built-in/plugin configuration, refresh PluginManager and rearrange. Maximum size: 24 KiB.lunadashctl extension-save '{"schemaVersion":1,"builtins":{},"plugins":{}}'
extension-installPlugin id from status.extensions.remoteStart an install from the reviewed Store catalogue. The immediate response is pending; progress/error lives in extensions.remote.lunadashctl extension-install org.example.plugin
extension-removeUser-installed plugin idDisable and remove only a plugin under the canonical user LunaDash plugin root. System plugins cannot be removed here.lunadashctl extension-remove org.example.plugin
extension-errorJSON {"id":"package@target","error":"message"}Report a plugin runtime failure; messages are capped at 1024 chars. Send an empty error to clear quarantine and retry/refresh.lunadashctl extension-error '{"id":"org.example.panel@panel","error":""}'

Shell modules

MethodValueBehaviorExample
module-validateComplete shell-modules JSONValidate the versioned module document without writing it.lunadashctl module-validate "$(cat shell-modules.json)"
module-saveComplete shell-modules JSONValidate, atomically save and apply the module document.lunadashctl module-save '<json>'
module-templateTemplate idInstall a supported starter module file without overwriting an existing user file.lunadashctl module-template panel
module-code-trusttrue or falseOpt in/out of executing user custom QML; this code is trusted user code, not sandboxed.lunadashctl module-code-trust true
module-resetEmptyRestore built-in module settings and disable custom-code trust while preserving user QML files.lunadashctl module-reset
module-errorJSON {"id":"module-id","error":"message"}Report or clear a custom-module runtime error so recovery content remains available.lunadashctl module-error '{"id":"panel","error":"load failed"}'

Audio, network and power

MethodValueBehaviorExample
audioJSON with exactly device plus id, volume or mutedevice is output/input. Select output with id, set integer volume 0..100, or set mute boolean. Requires wpctl/WirePlumber and no pending audio change.lunadashctl audio '{"device":"output","volume":50}'
networkJSON with "action" and action-specific fieldsStructured NetworkManager/Bluetooth operations; accepted actions are documented below. Read status.network for current names/devices first.lunadashctl network '{"action":"wifi-connect","ssid":"My WiFi","password":"secret"}'
power-profileProfile from status.power.profilesSwitch with powerprofilesctl to an advertised profile such as power-saver, balanced or performance.lunadashctl power-profile balanced
session-actionsuspend, reboot or poweroffCall systemd-logind only when status.sessionActions reports the action available.lunadashctl session-action suspend

Displays

MethodValueBehaviorExample
brightnessInteger 1..100Set the active backlight through brightnessctl; status.brightness must report an available device.lunadashctl brightness 75
ddc-brightnessJSON {"id":"<device-id>","percent":0..100}Set and verify external-monitor VCP 0x10 brightness through ddcutil.lunadashctl ddc-brightness '{"id":"i2c-6:DELL U2720Q","percent":60}'
ddc-refreshEmptyRescan DDC/CI displays and query current brightness.lunadashctl ddc-refresh
display-configureJSON containing mode and/or scaleTest+commit a primary-display change. mode must be advertised; scale is 1.0..3.0. Success starts a 15-second confirmation timer.lunadashctl display-configure '{"scale":1.25}'
display-confirmEmptyPersist the pending display width/height/refresh/scale before the 15-second revert timer expires.lunadashctl display-confirm
display-revertEmptyImmediately restore the display state saved before the pending change.lunadashctl display-revert
desktop-sizeNested output sizeResize a supported windowed nested session; physical/fullscreen sessions reject unsupported resizing.lunadashctl desktop-size 1440x900

Capture and wallpaper

MethodValueBehaviorExample
captureAbsolute new output pathStart an asynchronous screenshot save. Relative paths and existing files are rejected; response includes path/pending/phase.lunadashctl capture /tmp/lunadash-shot.png
screenshotEmptyStart the normal LunaDash screenshot flow; read status.screenCapture for completion/error.lunadashctl screenshot
wallpaper0 or 1Select a built-in shader wallpaper palette and set wallpaperMode to shader.lunadashctl wallpaper 1
choose-wallpaperEmptyOpen Settings → Appearance and signal the image picker.lunadashctl choose-wallpaper
wallpaper-imageAbsolute local path or file URLValidate and select an image wallpaper.lunadashctl wallpaper-image /home/user/Pictures/wallpaper.png
wallpaper-defaultEmptyClear the custom image wallpaper and restore the default image state.lunadashctl wallpaper-default

Language, setup and maintenance

MethodValueBehaviorExample
languageSupported locale idSave a supported LunaDash UI locale.lunadashctl language zh_TW
configure-networkEmptyOpen nm-connection-editor, otherwise run nmtui in a supported terminal.lunadashctl configure-network
check-updateEmptyAsk the update checker to refresh; read status.update for the result.lunadashctl check-update
setupEmptyMark first-run setup incomplete and reopen the welcome flow.lunadashctl setup
finish-setupEmptyRecord first-run setup completion.lunadashctl finish-setup
quitLiteral "confirm"Request normal LunaDash session shutdown. Empty is rejected.lunadashctl quit confirm

Network action payloads

network accepts structured operations, never arbitrary nmcli text. Actions are bluetooth-scan, network-reset, connection-reconnect, wifi-scan, wifi-connect, connection-up/down/delete, device-connect/disconnect, networking-enable, wifi-enable, connection-autoconnect, connection-ipv4-auto/manual, connection-ipv6-auto/disabled and connection-route-metric.

NETWORK EXAMPLES
lunadashctl network '{"action":"wifi-scan"}'
lunadashctl network '{"action":"wifi-connect","ssid":"Lab WiFi","password":"..."}'
lunadashctl network '{"action":"networking-enable","enabled":true}'
lunadashctl network '{"action":"connection-ipv4-manual","name":"Lab","address":"192.0.2.20/24","gateway":"192.0.2.1","dns":"1.1.1.1"}'

Shortcut ids

Known actions are focusLeft/Right/Up/Down, groupLeft/Right, reorderLeft/Right, expelWindow, centerColumn, widenColumn, narrowColumn, maximizeWindow, closeWindow, minimizeWindow, launchTerminal, launchFiles, launchLauncher, screenshot, plus workspace1..10 and moveToWorkspace1..10. Values are Disabled or one key with modifiers; at least Meta or Alt is required.

Settings API version 1

settings-describe returns descriptors with id, name, type, category, normalized schema, current values and a SHA-256 revision. The revision hashes schema plus values for optimistic concurrency.

TARGET ID FORMS
builtin:<extension-target>
plugin:<package-id>:<extension-target>
module:<module-id>:<section>
layout:<active-window-template>
PATCH FLOW
# Read descriptors
lunadashctl settings-describe

# Send the selected target's exact revision with a partial change
lunadashctl settings-update '{"target":"plugin:org.example.motion:window-animation","revision":"<64-hex-sha256>","changes":{"duration":280}}'

If another editor changed the target, the patch fails with Settings changed. Reload before applying this edit.. Schemas can describe boolean, string, integer, number and bounded enum-array fields. Plugin schemas intentionally allow only toggle, select, number and slider controls.

QML shell API

SDK 2 Quickshell plugins receive shell, validated settings and target-specific context before construction. Treat shell.state as read-only and perform mutations through control methods.

MemberTypeUse
shell.statevarLatest status snapshot; treat it as read-only and mutate through commands.
shell.command(method, value)functionQueue a ludashctl operation. JSON-valued methods must receive JSON.stringify(...).
shell.commandCompleted(method, result)signalEmitted after a queued command returns with parsed JSON.
shell.launch(id)functionLaunch terminal/files/browser defaults, open Settings/Plugins, or a built-in LunaDash desktop app.
shell.openUrl(url)functionOpen a URL through the configured browser.
shell.setAppearance(changes)functionConvenience wrapper around the appearance control method.
shell.openSettingsPage(page)functionOpen the shell Settings surface at a page id.
shell.settingsOpenboolCurrent Settings visibility.
shell.focusedTitlestringFocused client title, or LunaDash when none is focused.
shell.tr(source)functionTranslate with state.translations, falling back to source.
shell.notify(title, body, kind, details)functionShow shell feedback while respecting notification preferences.
shell.configureNetwork()functionOpen the host network editor while preserving first-run behavior.
QML / SDK 2
import QtQuick

Item {
    required property var shell
    required property var settings
    required property var context

    Text { text: shell.tr("Focused") + ": " + shell.focusedTitle }
    TapHandler { onTapped: shell.command("workspace", "1") }

    Connections {
        target: shell
        function onCommandCompleted(method, result) {
            if (method === "workspace" && result.error)
                context.reportError(result.error)
        }
    }
}

Every visual context adds target, built-in source, builtinSettings, effective mode and reportError(message). A plugin may expose pluginReady: false; a replacement does not hide the built-in until the loader is ready and pluginReady is true.

Native Plugin SDK 2 ABI

Native effects do not receive Qt or wlroots pointers. Hooks run synchronously on the compositor thread over UTF-8 JSON; do not block, retain host pointers, run nested event loops or create lifetime-escaping callbacks/background work.

ABI
#include <stddef.h>
#include <stdint.h>

#define LUDASH_PLUGIN_API_VERSION 2u

typedef struct ludash_plugin_api {
    uint32_t struct_size;
    uint32_t api_version;
    const char *metadata_json;
    int (*process)(const char *request, char *response, size_t capacity);
} ludash_plugin_api;

int ludash_plugin_process(const char *request, char *response, size_t capacity);
const ludash_plugin_api *ludash_plugin_entry_v2(void);
int ludash_plugin_write_json(const char *json, char *response, size_t capacity);

The SDK generates ludash_plugin_entry_v2, embeds the built manifest and produces the receipt. The loader checks struct size, API version 2, process pointer and equality between embedded and installed metadata.

REQUEST ENVELOPE
{
  "target":"window-animation",
  "mode":"replace",
  "context":{},
  "builtin":{"duration":220,"enterOffset":12,"focusOpacity":0.82,"exitScale":0.9,"easing":"outCubic"},
  "current":{},
  "settings":{"duration":280}
}

builtin is always available. Replacement hooks get empty current; augment hooks receive the previous valid result. Return a complete JSON object and its byte length excluding NUL, or a negative value. The response buffer is 262,145 bytes. Invalid/oversized output quarantines the instance and preserves the built-in/previous result.

The native targets are window-animation, shell-animation, window-rules and window-layout. Their contracts plus every visual target are in the Plugin SDK and registration guide.

Trust boundary: Quickshell plugins run with user permissions and native effects run inside the compositor without a sandbox.