mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-28 05:55:21 -04:00
skill(dms-plugin-dev): update for composite plugins, startupCheck, IPC discovery (#2699)
- Add composite plugin type with multi-surface components field - Add startupCheck manifest field and dependency gating docs - Add IPC runtime plugin discovery commands (plugin-scan target) - Add getPluginPath() to data persistence reference - Document bar reveal visibility optimization for widget plugins - Sync plugin-schema.json with source, add dependencies field - Bump skill version to 1.1
This commit is contained in:
@@ -2,15 +2,16 @@
|
||||
name: dms-plugin-dev
|
||||
description: >
|
||||
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.
|
||||
Quickshell. Supports five plugin types: widget (bar + Control Center), daemon (background
|
||||
service), launcher (search + actions), desktop (draggable desktop widgets), and composite
|
||||
(multi-surface). Covers manifest creation, QML component development, startup checks,
|
||||
settings UI, data persistence, theme integration, PopoutService usage, IPC runtime
|
||||
discovery, and external command execution. Use when the user wants to create, modify,
|
||||
or debug a DMS plugin, or asks about the DMS plugin API.
|
||||
compatibility: Designed for Claude Code (or similar products)
|
||||
metadata:
|
||||
author: DankMaterialShell
|
||||
version: "1.0"
|
||||
version: "1.1"
|
||||
domain: qml-desktop-development
|
||||
framework: DankMaterialShell
|
||||
languages: qml, javascript
|
||||
@@ -37,14 +38,15 @@ integrations, and desktop widgets. Plugins are QML components discovered from
|
||||
|
||||
**Plugin registry:** Community plugins are available at https://plugins.danklinux.com/
|
||||
|
||||
**Four plugin types:**
|
||||
**Five 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 |
|
||||
| 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 |
|
||||
| `composite` | Multi-surface plugin | One component per surface | Optional | Optional |
|
||||
|
||||
## Step 1: Determine Plugin Type
|
||||
|
||||
@@ -58,6 +60,9 @@ Choose the type based on what the plugin does:
|
||||
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.
|
||||
- **Needs multiple surfaces?** - Use `composite`. A single plugin that registers any combination
|
||||
of the above (e.g., a daemon + bar widget + desktop widget). Each surface gets its own
|
||||
QML component file.
|
||||
|
||||
## Step 2: Create the Manifest
|
||||
|
||||
@@ -78,7 +83,7 @@ Create `plugin.json` in your plugin directory. See [plugin-manifest-reference.md
|
||||
}
|
||||
```
|
||||
|
||||
**With settings and permissions:**
|
||||
**With settings, startup check, and permissions:**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -92,7 +97,31 @@ Create `plugin.json` in your plugin directory. See [plugin-manifest-reference.md
|
||||
"component": "./YourWidget.qml",
|
||||
"icon": "extension",
|
||||
"settings": "./Settings.qml",
|
||||
"startupCheck": "./StartupCheck.qml",
|
||||
"requires_dms": ">=0.1.0",
|
||||
"dependencies": ["mytool"],
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
```
|
||||
|
||||
**Composite plugin (multi-surface):**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "myComposite",
|
||||
"name": "My Composite Plugin",
|
||||
"description": "Daemon + widget + desktop from one plugin",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"type": "composite",
|
||||
"capabilities": ["daemon", "dankbar-widget", "desktop-widget"],
|
||||
"icon": "extension",
|
||||
"components": {
|
||||
"daemon": "./MyDaemon.qml",
|
||||
"widget": "./MyBarWidget.qml",
|
||||
"desktop": "./MyDesktopWidget.qml"
|
||||
},
|
||||
"settings": "./Settings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
```
|
||||
@@ -100,9 +129,11 @@ Create `plugin.json` in your plugin directory. See [plugin-manifest-reference.md
|
||||
**Key rules:**
|
||||
- `id` must be camelCase, matching pattern `^[a-zA-Z][a-zA-Z0-9]*$`
|
||||
- `version` must be semver (e.g., `1.0.0`)
|
||||
- `component` must start with `./` and end with `.qml`
|
||||
- `type: "launcher"` requires a `trigger` field
|
||||
- Provide either `component` (single-surface) or `components` (multi-surface), not both
|
||||
- `component` / component paths must start with `./` and end with `.qml`
|
||||
- `type: "launcher"` (or a `components` object with a `launcher` key) requires a `trigger` field
|
||||
- `settings_write` permission is **required** if the plugin has a settings component
|
||||
- `dependencies` replaces the deprecated `requires` field
|
||||
|
||||
## Step 3: Create the Main Component
|
||||
|
||||
@@ -249,7 +280,69 @@ PluginComponent {
|
||||
|
||||
See [daemon-plugin-guide.md](references/daemon-plugin-guide.md) for event-driven patterns and process execution.
|
||||
|
||||
## Step 4: Add Settings (Optional)
|
||||
### Composite
|
||||
|
||||
For composite plugins, create a separate QML file per surface. Each surface uses the same
|
||||
base component as the corresponding single-surface type (PluginComponent for widget/daemon,
|
||||
Item for launcher, etc.). All surfaces share the same `pluginId` and `pluginService`.
|
||||
|
||||
```
|
||||
MyCompositePlugin/
|
||||
plugin.json
|
||||
MyBarWidget.qml # PluginComponent (widget surface)
|
||||
MyDaemon.qml # PluginComponent (daemon surface)
|
||||
MyDesktopWidget.qml # Item with desktop widget properties
|
||||
Settings.qml # Shared settings for all surfaces
|
||||
```
|
||||
|
||||
Use `pluginService.pluginHasSurface(pluginId, "widget")` to check whether a specific surface
|
||||
is registered for a plugin at runtime.
|
||||
|
||||
## Step 4: Add Startup Check (Optional)
|
||||
|
||||
Gate plugin activation on dependency checks by providing a `startupCheck` component. This
|
||||
runs before the plugin loads and blocks activation if a required tool or condition is missing.
|
||||
|
||||
Create a `StartupCheck.qml` (non-visual QtObject):
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
QtObject {
|
||||
function check(done) {
|
||||
Proc.runCommand("myPlugin.depCheck", ["sh", "-c", "command -v mytool"], (stdout, exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
done(null);
|
||||
return;
|
||||
}
|
||||
done({
|
||||
"title": I18n.tr("mytool is required"),
|
||||
"details": I18n.tr("Install 'mytool' and re-enable this plugin.")
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `done` callback accepts:
|
||||
- `null` - allow activation
|
||||
- A string - block with a short error message
|
||||
- `{ title, details }` - block with a title and expandable details body
|
||||
|
||||
A synchronous variant (no `done` parameter, return the result directly) is also supported.
|
||||
|
||||
Failed checks show a toast error and store the error in `pluginService.pluginLoadErrors`.
|
||||
|
||||
Add to your manifest:
|
||||
```json
|
||||
{
|
||||
"startupCheck": "./StartupCheck.qml",
|
||||
"dependencies": ["mytool"]
|
||||
}
|
||||
```
|
||||
|
||||
## Step 5: Add Settings (Optional)
|
||||
|
||||
Wrap settings in `PluginSettings` with your `pluginId`. All settings auto-save and auto-load.
|
||||
|
||||
@@ -293,7 +386,7 @@ See [settings-components-reference.md](references/settings-components-reference.
|
||||
|
||||
**Important:** Your plugin must declare `"permissions": ["settings_write"]` in plugin.json, or the settings UI will show an error.
|
||||
|
||||
## Step 5: Use Data Persistence
|
||||
## Step 6: Use Data Persistence
|
||||
|
||||
Three tiers of persistence:
|
||||
|
||||
@@ -302,6 +395,7 @@ Three tiers of persistence:
|
||||
| `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 |
|
||||
| `pluginService.getPluginPath(id)` | N/A | Get the plugin's installation directory path |
|
||||
|
||||
- `pluginData` is a reactive property on PluginComponent, auto-loaded from settings
|
||||
- React to settings changes with `Connections { target: pluginService; function onPluginDataChanged(id) { ... } }`
|
||||
@@ -309,7 +403,7 @@ Three tiers of persistence:
|
||||
|
||||
See [data-persistence-guide.md](references/data-persistence-guide.md) for details and examples.
|
||||
|
||||
## Step 6: Theme Integration
|
||||
## Step 7: Theme Integration
|
||||
|
||||
Always use `Theme.*` properties from `qs.Common` - never hardcode colors or sizes.
|
||||
|
||||
@@ -324,7 +418,7 @@ Always use `Theme.*` properties from `qs.Common` - never hardcode colors or size
|
||||
|
||||
See [theme-reference.md](references/theme-reference.md) for the complete property list.
|
||||
|
||||
## Step 7: Add Popout Content (Widgets Only)
|
||||
## Step 8: Add Popout Content (Widgets Only)
|
||||
|
||||
Add a popout that opens when the bar pill is clicked:
|
||||
|
||||
@@ -360,7 +454,7 @@ PluginComponent {
|
||||
|
||||
Calculate available content height: `popoutHeight - headerHeight - detailsHeight - spacing`
|
||||
|
||||
## Step 8: Control Center Integration (Widgets Only)
|
||||
## Step 9: Control Center Integration (Widgets Only)
|
||||
|
||||
Add your widget to the Control Center grid:
|
||||
|
||||
@@ -389,7 +483,7 @@ PluginComponent {
|
||||
|
||||
**CC sizing:** 25% width = SmallToggleButton (icon only), 50% width = ToggleButton or CompoundPill (if ccDetailContent is defined).
|
||||
|
||||
## Step 9: External Commands and Clipboard
|
||||
## Step 10: External Commands and Clipboard
|
||||
|
||||
**Run commands and capture output:**
|
||||
|
||||
@@ -420,18 +514,33 @@ Quickshell.execDetached(["dms", "cl", "copy", textToCopy])
|
||||
|
||||
**Do NOT use** `globalThis.clipboard` or browser JavaScript APIs - they don't exist in the QML runtime.
|
||||
|
||||
## Step 10: Validate and Test
|
||||
## Step 11: Validate and Test
|
||||
|
||||
1. Validate `plugin.json` against the schema at [assets/plugin-schema.json](assets/plugin-schema.json)
|
||||
2. Run the shell with verbose output: `qs -v -p $CONFIGPATH/quickshell/dms/shell.qml`
|
||||
3. Open Settings > Plugins > Scan for Plugins
|
||||
4. Enable your plugin and add it to the DankBar layout
|
||||
|
||||
**Runtime plugin discovery via IPC:**
|
||||
|
||||
Plugins can be scanned, rescanned, and reloaded at runtime without restarting the shell:
|
||||
|
||||
```bash
|
||||
dms ipc plugin-scan scan # Trigger a full rescan of all plugin directories
|
||||
dms ipc plugin-scan rescan <id> # Force rescan of a specific plugin
|
||||
dms ipc plugin-scan reload <id> # Force reload of a loaded plugin
|
||||
dms ipc plugin-scan list # List all known plugins (TSV: id, loaded, type, name)
|
||||
dms ipc plugin-scan status <id> # Get status of a specific plugin (TSV: loaded, type, error)
|
||||
```
|
||||
|
||||
Plugin IDs are validated against `^[a-zA-Z0-9_\-:]{1,64}$`.
|
||||
|
||||
**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_write` permission is declared
|
||||
- Data not persisting: check pluginService injection and permissions
|
||||
- Startup check failing: check `pluginService.pluginLoadErrors` or run `dms ipc plugin-scan status <id>`
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
@@ -445,6 +554,9 @@ Quickshell.execDetached(["dms", "cl", "copy", textToCopy])
|
||||
8. **Forgetting `categories` in launcher items** - Items won't display without it
|
||||
9. **Not handling null pluginService** - Always use optional chaining or null checks
|
||||
10. **Using `PluginComponent` for launchers** - Launchers use plain `Item`, not `PluginComponent`
|
||||
11. **Using `requires` instead of `dependencies`** - `requires` is deprecated; use `dependencies`
|
||||
12. **Providing both `component` and `components`** - Use one or the other, not both
|
||||
13. **Missing `trigger` on composite with launcher surface** - Still required when `components` has a `launcher` key
|
||||
|
||||
## Quick Reference: Imports
|
||||
|
||||
|
||||
Reference in New Issue
Block a user