16 KiB
name, description, compatibility, metadata, allowed-tools
| name | description | compatibility | metadata | allowed-tools | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dms-plugin-dev | Develop plugins for DankMaterialShell (DMS), a QML-based Linux desktop shell built on Quickshell. Supports four plugin types: widget (bar + Control Center), daemon (background service), launcher (search + actions), and desktop (draggable desktop widgets). Covers manifest creation, QML component development, settings UI, data persistence, theme integration, PopoutService usage, and external command execution. Use when the user wants to create, modify, or debug a DMS plugin, or asks about the DMS plugin API. | Designed for Claude Code (or similar products) |
|
Bash Read Write Edit |
DankMaterialShell Plugin Development
Overview
DMS plugins extend the desktop shell with custom widgets, background services, launcher
integrations, and desktop widgets. Plugins are QML components discovered from
~/.config/DankMaterialShell/plugins/.
Minimum plugin structure:
~/.config/DankMaterialShell/plugins/YourPlugin/
plugin.json # Required: manifest with metadata
YourComponent.qml # Required: main QML component
YourSettings.qml # Optional: settings UI
*.js # Optional: JavaScript utilities
Plugin registry: Community plugins are available at https://plugins.danklinux.com/
Four plugin types:
| Type | Purpose | Base Component | Bar pills | CC integration |
|---|---|---|---|---|
widget |
Bar widget + popout | PluginComponent |
Yes | Yes |
daemon |
Background service | PluginComponent (no UI) |
No | Optional |
launcher |
Searchable items in launcher | Item |
No | No |
desktop |
Draggable desktop widget | DesktopPluginComponent |
No | No |
Step 1: Determine Plugin Type
Choose the type based on what the plugin does:
- Shows in the bar? - Use
widget. Displays a pill in DankBar, optionally opens a popout, optionally integrates with Control Center. - Runs in background only? - Use
daemon. No visible UI, reacts to events (wallpaper changes, notifications, battery level, etc.). - Provides searchable/actionable items? - Use
launcher. Items appear in the DMS launcher with trigger-based filtering (e.g., type=for calculator,:for emoji). - Shows on the desktop background? - Use
desktop. Draggable, resizable widget on the desktop layer.
Step 2: Create the Manifest
Create plugin.json in your plugin directory. See plugin-manifest-reference.md for the full schema.
Minimal manifest:
{
"id": "yourPlugin",
"name": "Your Plugin Name",
"description": "Brief description of what your plugin does",
"version": "1.0.0",
"author": "Your Name",
"type": "widget",
"capabilities": ["your-capability"],
"component": "./YourWidget.qml"
}
With settings and permissions:
{
"id": "yourPlugin",
"name": "Your Plugin Name",
"description": "Brief description",
"version": "1.0.0",
"author": "Your Name",
"type": "widget",
"capabilities": ["your-capability"],
"component": "./YourWidget.qml",
"icon": "extension",
"settings": "./Settings.qml",
"requires_dms": ">=0.1.0",
"permissions": ["settings_read", "settings_write"]
}
Key rules:
idmust be camelCase, matching pattern^[a-zA-Z][a-zA-Z0-9]*$versionmust be semver (e.g.,1.0.0)componentmust start with./and end with.qmltype: "launcher"requires atriggerfieldsettings_writepermission is required if the plugin has a settings component
Step 3: Create the Main Component
Widget
import QtQuick
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
property var popoutService: null
horizontalBarPill: Component {
StyledRect {
width: label.implicitWidth + Theme.spacingM * 2
height: parent.widgetThickness
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
StyledText {
id: label
anchors.centerIn: parent
text: "Hello"
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
}
}
}
verticalBarPill: Component {
StyledRect {
width: parent.widgetThickness
height: label.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
StyledText {
id: label
anchors.centerIn: parent
text: "Hi"
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
rotation: 90
}
}
}
}
See widget-plugin-guide.md for popouts, CC integration, and advanced features.
Launcher
import QtQuick
import qs.Services
Item {
id: root
property var pluginService: null
property string trigger: "#"
signal itemsChanged()
function getItems(query) {
const items = [
{ name: "Item One", icon: "material:star", comment: "Description",
action: "toast:Hello!", categories: ["MyPlugin"] }
]
if (!query) return items
const q = query.toLowerCase()
return items.filter(i => i.name.toLowerCase().includes(q))
}
function executeItem(item) {
const [type, ...rest] = item.action.split(":")
const data = rest.join(":")
if (type === "toast") ToastService?.showInfo(data)
else if (type === "copy") Quickshell.execDetached(["dms", "cl", "copy", data])
}
}
See launcher-plugin-guide.md for triggers, icon types, context menus, and image tiles.
Desktop
import QtQuick
import qs.Common
Item {
id: root
property var pluginService: null
property string pluginId: ""
property bool editMode: false
property real widgetWidth: 200
property real widgetHeight: 200
property real minWidth: 150
property real minHeight: 150
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.surfaceContainer
opacity: 0.85
border.color: root.editMode ? Theme.primary : "transparent"
border.width: root.editMode ? 2 : 0
Text {
anchors.centerIn: parent
text: "Desktop Widget"
color: Theme.surfaceText
}
}
}
See desktop-plugin-guide.md for sizing, persistence, and edit mode.
Daemon
import QtQuick
import qs.Common
import qs.Services
import qs.Modules.Plugins
PluginComponent {
property var popoutService: null
Connections {
target: SessionData
function onSomeSignal() {
console.log("Event received")
}
}
}
See daemon-plugin-guide.md for event-driven patterns and process execution.
Step 4: Add Settings (Optional)
Wrap settings in PluginSettings with your pluginId. All settings auto-save and auto-load.
import QtQuick
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
PluginSettings {
pluginId: "yourPlugin"
StringSetting {
settingKey: "apiKey"
label: "API Key"
description: "Your API key"
placeholder: "sk-..."
}
ToggleSetting {
settingKey: "enabled"
label: "Enable Feature"
defaultValue: true
}
SelectionSetting {
settingKey: "interval"
label: "Refresh Interval"
options: [
{ label: "1 min", value: "60" },
{ label: "5 min", value: "300" }
]
defaultValue: "300"
}
}
Available setting components: StringSetting, ToggleSetting, SelectionSetting, SliderSetting, ColorSetting, ListSetting, ListSettingWithInput.
See settings-components-reference.md for full property lists.
Important: Your plugin must declare "permissions": ["settings_write"] in plugin.json, or the settings UI will show an error.
Step 5: Use Data Persistence
Three tiers of persistence:
| API | Persisted | Use case |
|---|---|---|
pluginService.savePluginData(id, key, val) / loadPluginData(id, key, default) |
Yes (settings.json) | User preferences, config |
pluginService.savePluginState(id, key, val) / loadPluginState(id, key, default) |
Yes (separate state file) | Runtime state, history, cache |
PluginGlobalVar { varName; defaultValue; value; set() } |
No (runtime only) | Cross-instance shared state |
pluginDatais a reactive property on PluginComponent, auto-loaded from settings- React to settings changes with
Connections { target: pluginService; function onPluginDataChanged(id) { ... } } - Global vars sync across all instances (multi-monitor, multiple bar sections)
See data-persistence-guide.md for details and examples.
Step 6: Theme Integration
Always use Theme.* properties from qs.Common - never hardcode colors or sizes.
Essential properties:
- Colors:
Theme.surfaceContainerHigh,Theme.surfaceText,Theme.primary,Theme.onPrimary - Fonts:
Theme.fontSizeSmall(12),Theme.fontSizeMedium(14),Theme.fontSizeLarge(16),Theme.fontSizeXLarge(20) - Spacing:
Theme.spacingXS,Theme.spacingS,Theme.spacingM,Theme.spacingL,Theme.spacingXL - Radius:
Theme.cornerRadius,Theme.cornerRadiusSmall,Theme.cornerRadiusLarge - Icons:
Theme.iconSizeSmall(16),Theme.iconSize(24),Theme.iconSizeLarge(32)
Common widgets from qs.Widgets: StyledText, StyledRect, DankIcon, DankButton, DankToggle, DankTextField, DankSlider, DankGridView, CachingImage.
See theme-reference.md for the complete property list.
Step 7: Add Popout Content (Widgets Only)
Add a popout that opens when the bar pill is clicked:
PluginComponent {
popoutWidth: 400
popoutHeight: 300
popoutContent: Component {
PopoutComponent {
headerText: "My Plugin"
detailsText: "Optional subtitle"
showCloseButton: true
Column {
width: parent.width
spacing: Theme.spacingM
StyledText {
text: "Content here"
color: Theme.surfaceText
}
}
}
}
horizontalBarPill: Component { /* ... */ }
verticalBarPill: Component { /* ... */ }
}
PopoutComponent properties: headerText, detailsText, showCloseButton, closePopout() (auto-injected), headerHeight (readonly), detailsHeight (readonly).
Calculate available content height: popoutHeight - headerHeight - detailsHeight - spacing
Step 8: Control Center Integration (Widgets Only)
Add your widget to the Control Center grid:
PluginComponent {
ccWidgetIcon: "toggle_on"
ccWidgetPrimaryText: "My Feature"
ccWidgetSecondaryText: isActive ? "On" : "Off"
ccWidgetIsActive: isActive
onCcWidgetToggled: {
isActive = !isActive
pluginService?.savePluginData(pluginId, "active", isActive)
}
// Optional: expandable detail panel (for CompoundPill)
ccDetailContent: Component {
Rectangle {
implicitHeight: 200
color: Theme.surfaceContainerHigh
radius: Theme.cornerRadius
}
}
}
CC sizing: 25% width = SmallToggleButton (icon only), 50% width = ToggleButton or CompoundPill (if ccDetailContent is defined).
Step 9: External Commands and Clipboard
Run commands and capture output:
import qs.Common
Proc.runCommand(
"myPlugin.fetch",
["curl", "-s", "https://api.example.com/data"],
(stdout, exitCode) => {
if (exitCode === 0) processData(stdout)
},
500 // debounce ms
)
Fire-and-forget (clipboard, notifications):
import Quickshell
Quickshell.execDetached(["dms", "cl", "copy", textToCopy])
Long-running processes: Use the Process QML component from Quickshell.Io with StdioCollector.
Shell commands with pipes: ["sh", "-c", "ps aux | grep foo"]
Do NOT use globalThis.clipboard or browser JavaScript APIs - they don't exist in the QML runtime.
Step 10: Validate and Test
- Validate
plugin.jsonagainst the schema at assets/plugin-schema.json - Run the shell with verbose output:
qs -v -p $CONFIGPATH/quickshell/dms/shell.qml - Open Settings > Plugins > Scan for Plugins
- Enable your plugin and add it to the DankBar layout
Common issues:
- Plugin not detected: check plugin.json syntax with
jq . plugin.json - Widget not showing: ensure it's enabled AND added to a DankBar section
- Settings error: verify
settings_writepermission is declared - Data not persisting: check pluginService injection and permissions
Common Mistakes
- Missing
settings_writepermission - Settings UI shows error without it - Missing
property var popoutService: null- Must declare for injection to work - Missing vertical bar pill - Widget disappears when bar is on left/right edge
- Hardcoded colors - Use
Theme.*properties, not hex values - Using
globalThis.clipboard- Does not exist; useQuickshell.execDetached(["dms", "cl", "copy", text]) - Wrong Theme property names -
Theme.fontSizeSdoes not exist, useTheme.fontSizeSmall - Wrong import for Quickshell - Use
import Quickshell(notimport QtQuickfor execDetached) - Forgetting
categoriesin launcher items - Items won't display without it - Not handling null pluginService - Always use optional chaining or null checks
- Using
PluginComponentfor launchers - Launchers use plainItem, notPluginComponent
Quick Reference: Imports
Widget / Daemon:
import QtQuick
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
Launcher:
import QtQuick
import qs.Services
Desktop:
import QtQuick
import qs.Common
For clipboard/exec: import Quickshell
For processes: import Quickshell.Io
For networking: import Quickshell.Networking
For toast notifications: access ToastService from qs.Services
Quick Reference: File Naming
- Directory name: PascalCase (e.g.,
MyAwesomePlugin/) - Plugin ID: camelCase (e.g.,
myAwesomePlugin) - QML files: PascalCase (e.g.,
MyWidget.qml,Settings.qml) - Component paths in manifest: relative with
./prefix (e.g.,"./MyWidget.qml") - JS utility files: camelCase (e.g.,
utils.js,apiAdapter.js)
Reference Files
Load these on demand for detailed API documentation:
- plugin-manifest-reference.md - Complete plugin.json field reference and JSON schema
- widget-plugin-guide.md - PluginComponent, bar pills, popouts, click actions, CC integration
- launcher-plugin-guide.md - getItems/executeItem, triggers, icon types, context menus, tile view
- desktop-plugin-guide.md - DesktopPluginComponent, sizing, edit mode, position persistence
- daemon-plugin-guide.md - Event-driven background services, process execution
- settings-components-reference.md - All 7 setting components with complete property lists
- theme-reference.md - Theme colors, spacing, fonts, radii, common patterns
- data-persistence-guide.md - pluginData, state API, global variables
- popout-service-reference.md - PopoutService API for controlling shell popouts and modals
- advanced-patterns.md - Variants, JS utilities, qmldir, IPC, multi-file plugins