- Introduces a general-purpose opencode skill to help agents build, debug, and publish DMS plugins. - Includes orientation, decision trees (plugin types), condensed cheat sheets, ecosystem map, and four generic vertical-slice starter templates (bar widget, popout widget, launcher, desktop widget). - Skill is versioned here for this project while remaining available as a global opencode skill. - Updated .gitignore to ignore globally-installed copies of the skill. Written by AI agent working for @jtmorris. Model: grok-build-0.1.
5.9 KiB
DMS Plugin Cheat Sheets
These are deliberately condensed surfaces. For full details and current property lists, read the official plugin-development guide for the exact DMS version in use.
plugin.json — Fields That Actually Matter
Required for all:
id(camelCase, unique, used for reload and data keys)name,description,version,authortype: "widget" | "launcher" | "daemon" | "desktop"component: relative path to the root QML file
Strongly recommended:
icon(Material icon name shown in the Plugins list)permissions: array —"settings_write"is required if you provide a settings component usingPluginSettingscapabilities: helps the UI know where to offer the plugin (e.g.["dankbar-widget"],["control-center"],["desktop-widget"],["launcher"])settings: relative path to the settings QML (only if you want a UI in Settings → Plugins)
Launcher-specific:
trigger: the prefix string (or""for always-visible)viewMode: "list" | "grid" | "tile"viewModeEnforced: boolean
Desktop-specific:
requires_dms: e.g.">=1.2.0"
requires (array of external tools) is used for documentation and install hints.
PluginComponent (the base for almost every widget)
Auto-injected (never declare these yourself):
pluginData— reactive object containing everything saved via the settings system for this pluginpluginServicepluginIdaxis,section,parentScreen,widgetThickness,barThickness,iconSize,variants
You define:
horizontalBarPill: Component { ... }verticalBarPill: Component { ... }(can be the same or different layout)popoutContent: Component { PopoutComponent { ... } }(optional but very common)popoutWidth,popoutHeightlayerNamespacePlugin: "unique-name"(strongly recommended for any popout widget)pillClickAction/pillRightClickAction(to override the default popout behavior)
For Control Center presence, also define:
ccWidgetIcon,ccWidgetPrimaryText,ccWidgetSecondaryText,ccWidgetIsActiveonCcWidgetToggledccDetailContent(for the expandable detail panel on CompoundPill)
PopoutComponent
Convenience wrapper that gives you a consistent header + details area + closePopout() function.
Expose headerHeight and detailsHeight so your content can compute the remaining space correctly.
PluginService Data APIs (three different stores)
-
Settings (
savePluginData/loadPluginData)- Persisted in the shared
plugin_settings.json - Shown/edited via the
*Settingcomponents insidePluginSettings - Use for user preferences
- Persisted in the shared
-
State (
savePluginState/loadPluginState)- Per-plugin
_state.jsonfile in~/.local/state/DankMaterialShell/plugins/ - Not shown in settings UI
- Good for command history, recent items, runtime caches that should survive reloads
- Per-plugin
-
Global Variables (
getGlobalVar/setGlobalVar+PluginGlobalVarhelper)- In-memory, reactive across all instances of the same plugin
- Not persisted
- Perfect for "current selection", counters, shared UI state on multi-monitor bars
The modern settings UI components (StringSetting, ToggleSetting, ColorSetting, SliderSetting, SelectionSetting, ListSettingWithInput, etc.) live inside a PluginSettings { pluginId: "..." } root and handle load/save automatically.
Launcher Plugin Shape (different from widgets)
Root must be a QtObject { id: root ... }
You must provide (at minimum):
getItems(query)→ array of item objectsexecuteItem(item)
Optional but powerful:
getContextMenuActions(item)→ array of{icon, text, action, closeLauncher?}getCategories()+setCategory(id)getPasteText(item)and/orgetPasteArgs(item)for Shift+Enter paste support
Item shape includes name, icon (or unicode:...), comment, action, imageUrl, animated, attribution, categories, keywords.
Call pluginService.requestLauncherUpdate(pluginId) after async data arrives.
External Commands
Prefer Proc.runCommand(id, argv, (stdout, exitCode) => {...}, debounceMs) from qs.Common.
It gives you captured stdout, automatic cleanup, and debouncing by id.
Only fall back to a raw Process item when you truly need a long-lived or streaming process.
Desktop Widgets (DesktopPluginComponent)
- Set
minWidth/minHeight(and optionallydefaultWidth/defaultHeight,forceSquare) - Read
widgetWidth,widgetHeight,pluginData - Use the helper methods
getData(key, default)andsetData(key, value)if you want the thin wrappers - Position/size is persisted automatically per screen
- User interaction: right-click-drag anywhere to move, right-click-drag bottom-right corner to resize
Permissions (the one that bites agents)
If your plugin has a settings component that uses PluginSettings, you must list "settings_write" in the permissions array.
Without it the settings UI will show an error instead of your form.
Other permissions (settings_read, process, network) are declared for documentation and future enforcement.
Layer Namespaces for Popouts
On any PluginComponent that opens a popout:
layerNamespacePlugin: "my-unique-popout"
This gives the popout its own layer namespace under dms:plugins:... and avoids focus/z-order fights with other DMS popouts.
Hot Reload & Debugging
dms ipc call plugins reload <id>dms ipc call plugins listdms ipc call plugins status <id>- Run the shell from a terminal (
dms runor equivalent) so you see QML errors andconsole.log
Theme, Toast, I18n
Always go through the singletons:
Theme.*for colors, spacing, font sizes, corner radiiToastService.showInfo(...)/showError(...)I18n.tr("text", "context for translators")
Never hard-code colors or sizes.
This is enough to get 80% of plugins correct on the first serious attempt. For the remaining 20%, go to the official development guide for the exact property lists and edge cases.