Talk to your desktop.
Use the local control socket, write a Quickshell component, or build an opt-in native effect. Each interface has a different scope and trust boundary.
This is a local API, not an HTTP service. The website documents and previews configuration; it does not control your desktop. The compositor's Unix socket is restricted to the session user.
Start with ludashctl
Applications launched inside LunaDash inherit LUDASH_CONTROL. For an external terminal, point it at your running session's socket. The default session name is ludash-0; substitute the name passed to --socket.
export LUDASH_CONTROL="$XDG_RUNTIME_DIR/ludash-test-control"
./build/ludashctl status
./build/ludashctl appearance '{"gap":16,"animations":true}'
./build/ludashctl open-settings modulesSend one UTF-8 JSON object followed by a newline. method and value are strings; JSON-valued settings are encoded inside value. Numeric IDs are also sent as strings.
{"method":"appearance","value":"{\"gap\":16}"}
A successful call generally returns an updated state object. Failure returns {"error":"message"}; the CLI exits nonzero. Audio and power helpers run asynchronously, so acceptance is not completion—poll status for busy/error fields. Requests are limited to 64 KiB with a three-second server deadline; a client should also bound response size and time.
import json, os, socket
request = {
"method": "appearance",
"value": json.dumps({"accent": "#c4b5fd", "gap": 16})
}
with socket.socket(socket.AF_UNIX) as client:
client.settimeout(3)
client.connect(os.environ["LUDASH_CONTROL"])
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)
if "error" in response:
raise RuntimeError(response["error"])
print(response["appearance"])Control methods
| Method | Value | Result |
|---|---|---|
| status | Empty | Read the session, clients, graphics health, preferences, module state and available services. |
| appearance | JSON object encoded as a string | Validate and save a partial desktop-preference update. Invalid fields reject the entire update. |
| open-settings | Category ID or empty | Open the settings center. IDs include general, appearance, windows, modules, display, input, sound, network, bluetooth, power, applications, privacy, system, devices and about. |
| workspace | Zero-based workspace index | Switch to an existing workspace (0 to workspaceCount−1). |
| focus / minimize / close | Client ID from status | Restore and focus, minimize, or request normal close of a window. Close can trigger an application's save dialog. |
| language | en_US or zh_TW | Save the UI language. The shell refreshes its translation dictionary; reopen native applications. |
| wallpaper-image | Local absolute path or file URL | Validate and select an image wallpaper. |
| wallpaper / wallpaper-default | 0 or 1 / empty | Select a shader palette or restore the bundled image. |
| default-apps | JSON object: {"terminal":[],"files":[]} | Save literal executable/argument arrays. Empty arrays select LunaDash defaults; executables must be available. |
| launch-default | terminal or files | Launch the selected default application in this Wayland session. |
| module-validate / module-save | Versioned module JSON string | Validate without writing, or atomically save and apply a module document (maximum 16 KiB). |
| module-template | panel or overview | Create a starter QML file without overwriting existing code. |
| module-code-trust | Literal string "true" or "false" | Allow or disable custom QML execution for this user. Code is not sandboxed. |
| module-reset | Empty | Restore built-in module settings and disable custom code; keep user QML files. |
| audio | JSON string: {"device":"output","volume":50} | Request default input/output volume 0–100, or use a boolean mute field. Requires WirePlumber. |
| power-profile | Advertised profile ID from status | Request a supported power-profiles-daemon profile; check status for completion or errors. |
| system-tool | Available tool ID from status.settingsTools | Open a fixed, validated settings helper. Host tools are explicitly marked in the snapshot. |
| desktop-size | 1280x720, 1440x900 or 1920x1080 | Resize a nested window. Fullscreen and standalone EGLFS are rejected. |
| configure-network | Empty | Open the available system network editor. Credentials remain in that tool. |
| setup / finish-setup | Empty | Reopen the first-run guide or mark it complete. |
| reset-preferences | Empty | Reset LunaDash desktop preferences; module JSON and default-app commands have separate reset controls. |
| launch-x11 | Program and quoted arguments | Launch an X11 program in the authenticated rootful compatibility container. Shell operators are not evaluated. |
| quit | Empty | Request session shutdown and normal client close. |
A small QML interface
Each custom component receives shell, resolved style, and moduleId. Read shell.state as a snapshot and use commands for changes. The host manages the window, dimensions, animation and fallback content.
shell.launch("terminal")
shell.launch("files")
shell.command("workspace", 1)
shell.setAppearance({ blur: true, blurRadius: 18 })
shell.settingsOpen = true
const title = shell.focusedTitle
const label = shell.tr("Settings")The component guide includes a complete Item template, JSON styles and recovery steps. Custom QML can execute processes and access files; metadata does not isolate it.
Native effect plugins · API 1
Native plugins use the LuDash::CompositorPlugin interface. Implement windowOpened(QQuickItem*) and windowFocused(QQuickItem*) in a QObject subclass, declare Q_INTERFACES, and embed metadata with Q_PLUGIN_METADATA.
{
"KPlugin": {
"Id": "org.example.effect",
"Name": "Example effect",
"Version": "1.0.0",
"License": "GPL-3.0-only",
"EnabledByDefault": false
},
"LuDash": {
"ApiVersion": 1,
"Type": "WindowEffect",
"Library": "libexample-effect.so"
}
}#include <QObject>
#include <LuDash/plugins/CompositorPlugin.h>
namespace LuDash {
class ExampleEffect final : public QObject, public CompositorPlugin {
Q_OBJECT
Q_PLUGIN_METADATA(IID LUDASH_COMPOSITOR_PLUGIN_IID FILE "metadata.json")
Q_INTERFACES(LuDash::CompositorPlugin)
public:
void windowOpened(QQuickItem* frame) override;
void windowFocused(QQuickItem* frame) override;
};
}Put implementations in a matching .cpp file and build a CMake MODULE library linked to Qt Quick. Store the library and metadata together in ~/.local/share/ludash/plugins/<id>/, enable it in Plugins, then restart the compositor. Keep retained window references in QPointer and never access a destroyed frame.
Native code runs inside the compositor. Plugins are disabled by default. There is no sandbox or signing system, and a faulty plugin can crash the session. The metadata resembles KDE's structure; LunaDash does not implement KWin's ABI or load KWin plugins.
The repository contains a complete fade effect implementation, its header, and packaging instructions.