From 01ac7e9041973487285b64ba14fff737edb78a1d Mon Sep 17 00:00:00 2001 From: vybe Date: Sun, 24 May 2026 09:31:06 +0000 Subject: [PATCH] feat: add dms-plugin-dev agent skill for Dank Material Shell plugin development - 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. --- .gitignore | 6 + skills/dms-plugin-dev/README.md | 43 ++++++ skills/dms-plugin-dev/SKILL.md | 103 +++++++++++++ skills/dms-plugin-dev/docs/cheatsheets.md | 142 ++++++++++++++++++ skills/dms-plugin-dev/docs/ecosystem.md | 64 ++++++++ skills/dms-plugin-dev/docs/plugin-types.md | 44 ++++++ .../templates/desktop/DesktopWidget.qml | 43 ++++++ .../templates/desktop/README.md | 19 +++ .../templates/desktop/Settings.qml | 32 ++++ .../templates/desktop/plugin.json | 14 ++ .../templates/launcher/Launcher.qml | 63 ++++++++ .../templates/launcher/README.md | 20 +++ .../templates/launcher/plugin.json | 14 ++ .../templates/widget-bar/README.md | 40 +++++ .../templates/widget-bar/Settings.qml | 33 ++++ .../templates/widget-bar/Widget.qml | 53 +++++++ .../templates/widget-bar/plugin.json | 13 ++ .../templates/widget-popout/README.md | 18 +++ .../templates/widget-popout/Settings.qml | 23 +++ .../templates/widget-popout/Widget.qml | 74 +++++++++ .../templates/widget-popout/plugin.json | 12 ++ 21 files changed, 873 insertions(+) create mode 100644 skills/dms-plugin-dev/README.md create mode 100644 skills/dms-plugin-dev/SKILL.md create mode 100644 skills/dms-plugin-dev/docs/cheatsheets.md create mode 100644 skills/dms-plugin-dev/docs/ecosystem.md create mode 100644 skills/dms-plugin-dev/docs/plugin-types.md create mode 100644 skills/dms-plugin-dev/templates/desktop/DesktopWidget.qml create mode 100644 skills/dms-plugin-dev/templates/desktop/README.md create mode 100644 skills/dms-plugin-dev/templates/desktop/Settings.qml create mode 100644 skills/dms-plugin-dev/templates/desktop/plugin.json create mode 100644 skills/dms-plugin-dev/templates/launcher/Launcher.qml create mode 100644 skills/dms-plugin-dev/templates/launcher/README.md create mode 100644 skills/dms-plugin-dev/templates/launcher/plugin.json create mode 100644 skills/dms-plugin-dev/templates/widget-bar/README.md create mode 100644 skills/dms-plugin-dev/templates/widget-bar/Settings.qml create mode 100644 skills/dms-plugin-dev/templates/widget-bar/Widget.qml create mode 100644 skills/dms-plugin-dev/templates/widget-bar/plugin.json create mode 100644 skills/dms-plugin-dev/templates/widget-popout/README.md create mode 100644 skills/dms-plugin-dev/templates/widget-popout/Settings.qml create mode 100644 skills/dms-plugin-dev/templates/widget-popout/Widget.qml create mode 100644 skills/dms-plugin-dev/templates/widget-popout/plugin.json diff --git a/.gitignore b/.gitignore index ea0259b..2d1b45a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ # Ignore any directory named `.john` anywhere in the repository .john/ + +# dms-plugin-dev skill +# The canonical version is versioned inside this repository at skills/dms-plugin-dev/ +# Ignore the globally-installed copy (in ~/.config/opencode/skills/) to prevent +# accidental commits of user-local installations. +**/.config/opencode/skills/dms-plugin-dev/ diff --git a/skills/dms-plugin-dev/README.md b/skills/dms-plugin-dev/README.md new file mode 100644 index 0000000..08403b9 --- /dev/null +++ b/skills/dms-plugin-dev/README.md @@ -0,0 +1,43 @@ +# dms-plugin-dev + +A focused skill for agents that builds or maintains plugins for **Dank Material Shell (DMS)**. + +## What It Provides + +- Strong orientation and mental model for the DMS plugin system +- Decision trees for choosing the right plugin type +- Condensed cheat sheets for the highest-uncertainty surfaces (`plugin.json`, `PluginComponent`, `PluginService`, settings, popouts, permissions, launcher shape, etc.) +- Curated ecosystem map (official docs, first-party examples, monorepo examples, registry) +- Generic, modern vertical-slice starter templates (widget, popout widget, launcher, desktop) + +## Philosophy + +This skill is deliberately **thin and pointer-heavy**. The official DMS documentation (especially the Plugin Development guide) is excellent and comprehensive. This skill exists to reduce the "I don't know what I don't know" problem for agents and to give them good starting points (templates) that follow current conventions. + +It does **not** duplicate large sections of the official docs. + +## Location in the Skill + +After the skill is loaded, you will have access to: +- `SKILL.md` (entry point) +- `docs/plugin-types.md` +- `docs/cheatsheets.md` +- `docs/ecosystem.md` +- `templates/` (copy these folders wholesale when starting a new plugin) + +## When to Load This Skill + +Any time the user says: +- "build a DMS plugin" +- "add a widget / launcher / desktop thing to Dank Material Shell" +- "debug this plugin.json / PluginComponent" +- "publish this to the DMS plugin registry" +- or mentions any of the core DMS plugin concepts + +## Maintenance + +Low-maintenance by design. When the official docs or best practices evolve significantly, update the pointers and the templates. Do not try to be a second source of truth. + +--- + +Written by AI agent working for @jtmorris. Model: grok-build-0.1. \ No newline at end of file diff --git a/skills/dms-plugin-dev/SKILL.md b/skills/dms-plugin-dev/SKILL.md new file mode 100644 index 0000000..fa76271 --- /dev/null +++ b/skills/dms-plugin-dev/SKILL.md @@ -0,0 +1,103 @@ +--- +name: dms-plugin-dev +description: Guidance, decision trees, cheat sheets, ecosystem map, and generic vertical-slice starter templates for building plugins for Dank Material Shell (DMS / DankMaterialShell) using Quickshell QML. Use when the user asks to create, extend, debug, review, or publish a DMS plugin, or mentions plugin.json, PluginComponent, DankBar widgets, launcher plugins, desktop widgets, or the DMS plugin registry. +license: MIT +compatibility: opencode +--- + +# DMS Plugin Development + +## Mental Model (Read This First) + +Dank Material Shell plugins are **small, self-contained directories** dropped into `~/.config/DankMaterialShell/plugins//`. + +Each plugin declares what it is in `plugin.json` (type, capabilities, entry component, permissions, dependencies). + +The shell discovers them, loads the QML components into its running context, and injects services (`PluginService`, `Theme`, `ToastService`, etc.). + +Plugins do **not** run in isolation like a normal QML app. They participate in the larger DMS object tree. + +There are four primary plugin types (see `docs/plugin-types.md` for the decision tree): + +- **widget** — Appears in the DankBar or Control Center (most common). +- **launcher** — Extends the Spotlight-style launcher with searchable items (root is a `QtObject`, not an `Item`). +- **daemon** — Background logic, no UI (react to events, run automation). +- **desktop** — Free-floating, user-positionable and resizable widgets on the desktop layer (uses `DesktopPluginComponent`). + +## Development Loop (Fastest Feedback) + +1. Create your plugin directory anywhere (e.g. `~/src/my-dms-plugin`). +2. Symlink it: `ln -s ~/src/my-dms-plugin ~/.config/DankMaterialShell/plugins/MyPlugin`. +3. Work in the source tree. +4. After changes: `dms ipc call plugins reload myPlugin` (uses the `id` from plugin.json). +5. For popout widgets, set `layerNamespacePlugin: "something-unique"` in your `PluginComponent`. +6. Set up qmlls for autocomplete (see official docs; create `.qmlls.ini` in a DMS checkout). +7. Use the `Proc` singleton (from `qs.Common`) for any external command whose output you need to capture. It handles debouncing and auto-cleanup. + +Never restart the whole shell while iterating on a plugin unless absolutely necessary. + +## Must-Read Official Sources + +This skill is deliberately a **thin orientation + index + templates layer**. The canonical, detailed, and up-to-date reference is the official documentation: + +- Plugin Overview: https://danklinux.com/docs/dankmaterialshell/plugins-overview +- Plugin Development (the big one): https://danklinux.com/docs/dankmaterialshell/plugin-development +- Plugin schema (validation): Look for `plugin-schema.json` in the DMS repo or registry. + +**Always** check the version notes at the top of those pages and match them to the DMS version the user is running. + +## High-Signal Cheat Sheets in This Skill + +- `docs/plugin-types.md` — Decision tree for choosing the right plugin shape. +- `docs/cheatsheets.md` — Condensed surfaces for `plugin.json`, `PluginComponent`, `PluginService`, settings components, popouts, and permissions. +- `docs/ecosystem.md` — Where to find real examples (first-party plugins, monorepo PLUGINS/, registry, community). + +## Generic Vertical-Slice Templates + +Located in the `templates/` directory of this skill. Each is a minimal, correct, modern starter for one plugin type. + +**How to use them**: +1. Copy the entire template folder to `~/.config/DankMaterialShell/plugins/YourPluginName/`. +2. Rename files and update `plugin.json` (especially `id`, `name`, `author`, paths). +3. Run `dms ipc call plugins reload ` (or restart). +4. Enable in DMS Settings → Plugins. + +Current templates: +- `widget-bar/` — Simple DankBar widget with settings. +- `widget-popout/` — Bar widget that opens a nice popout (uses `PopoutComponent` + layer namespace). +- `launcher/` — Basic launcher extension (QtObject root + `getItems`/`executeItem`). +- `desktop/` — Minimal desktop layer widget (uses `DesktopPluginComponent`). + +These templates follow current best practice (PluginSettings + `*Setting` components, proper permissions, `Proc` where relevant, etc.). They are intentionally small. + +## Common Agent Failure Modes Specific to DMS Plugins + +- Assuming the plugin is a standalone QML application (it lives inside DMS's context and singleton graph). +- Writing settings UI without declaring `"permissions": ["settings_write"]` (the settings simply won't appear). +- Using raw `Process` items for one-shot commands that need stdout (use the `Proc` singleton instead). +- Forgetting that `pluginData` comes from the settings store and is not a general reactive property bag. +- Neglecting `layerNamespacePlugin` on popout widgets (z-order and focus problems). +- Treating launcher plugins like normal QML Items (they must be `QtObject` and export specific functions). +- Over-caching or pre-computing things at load time that the shell can provide on demand. +- Not testing the hot-reload path early. + +## Publishing & Distribution + +- Add good screenshots, a README, and a license. +- Validate your `plugin.json` against the schema. +- Submit via PR to https://github.com/AvengeMedia/dms-plugin-registry (follow their CONTRIBUTING.md). +- The site at https://plugins.danklinux.com/ rebuilds automatically. + +## How to Explore When Stuck + +1. Re-read the two official development pages for the exact DMS version in use. +2. Look at the small example plugins inside the main DMS repo (`quickshell/PLUGINS/`). +3. Study first-party plugins in https://github.com/AvengeMedia/dms-plugins (they are maintained to current standards). +4. Browse the registry for real-world usage of the feature you need. +5. Use `dms ipc call plugins list` and the logs from `dms run` for runtime diagnostics. + +--- + +This skill exists to reduce uncertainty and variability when an agent is asked to work on a DMS plugin. It is intentionally lightweight and points at the official sources for depth. + +Written by AI agent working for @jtmorris. Model: grok-build-0.1. diff --git a/skills/dms-plugin-dev/docs/cheatsheets.md b/skills/dms-plugin-dev/docs/cheatsheets.md new file mode 100644 index 0000000..ff31dd4 --- /dev/null +++ b/skills/dms-plugin-dev/docs/cheatsheets.md @@ -0,0 +1,142 @@ +# 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`, `author` +- `type`: "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 using `PluginSettings` +- `capabilities`: 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 plugin +- `pluginService` +- `pluginId` +- `axis`, `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`, `popoutHeight` +- `layerNamespacePlugin: "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`, `ccWidgetIsActive` +- `onCcWidgetToggled` +- `ccDetailContent` (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) + +1. **Settings** (`savePluginData` / `loadPluginData`) + - Persisted in the shared `plugin_settings.json` + - Shown/edited via the `*Setting` components inside `PluginSettings` + - Use for user preferences + +2. **State** (`savePluginState` / `loadPluginState`) + - Per-plugin `_state.json` file in `~/.local/state/DankMaterialShell/plugins/` + - Not shown in settings UI + - Good for command history, recent items, runtime caches that should survive reloads + +3. **Global Variables** (`getGlobalVar` / `setGlobalVar` + `PluginGlobalVar` helper) + - 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 objects +- `executeItem(item)` + +Optional but powerful: +- `getContextMenuActions(item)` → array of `{icon, text, action, closeLauncher?}` +- `getCategories()` + `setCategory(id)` +- `getPasteText(item)` and/or `getPasteArgs(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 optionally `defaultWidth` / `defaultHeight`, `forceSquare`) +- Read `widgetWidth`, `widgetHeight`, `pluginData` +- Use the helper methods `getData(key, default)` and `setData(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: + +```qml +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 ` +- `dms ipc call plugins list` +- `dms ipc call plugins status ` +- Run the shell from a terminal (`dms run` or equivalent) so you see QML errors and `console.log` + +## Theme, Toast, I18n + +Always go through the singletons: +- `Theme.*` for colors, spacing, font sizes, corner radii +- `ToastService.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. \ No newline at end of file diff --git a/skills/dms-plugin-dev/docs/ecosystem.md b/skills/dms-plugin-dev/docs/ecosystem.md new file mode 100644 index 0000000..4687853 --- /dev/null +++ b/skills/dms-plugin-dev/docs/ecosystem.md @@ -0,0 +1,64 @@ +# DMS Plugin Ecosystem — Where to Look + +## Canonical Documentation (Start Here) + +- Plugin Overview: https://danklinux.com/docs/dankmaterialshell/plugins-overview +- Plugin Development (the comprehensive guide): https://danklinux.com/docs/dankmaterialshell/plugin-development +- Current DMS version docs are versioned; always match the user's installed version. + +## High-Quality Example Code + +1. **Inside the main DMS monorepo** (best for "how the shell itself expects plugins to behave"): + https://github.com/AvengeMedia/DankMaterialShell/tree/master/quickshell/PLUGINS + + Small, focused examples: + - ColorDemoPlugin + - ExampleEmojiPlugin (popout + grid) + - LauncherExample / LauncherImageExample + - ControlCenterExample + - WallpaperWatcherDaemon + - ExampleDesktopClock + - QuickNotesExample (good state API usage) + +2. **First-party maintained plugins** (real production code, kept up to date): + https://github.com/AvengeMedia/dms-plugins + + Good references for patterns: + - DankGifSearch, DankStickerSearch (launcher with images/tiles/categories) + - DankActions, DankHooks, DankPomodoroTimer, DankBatteryAlerts + +3. **Community plugins via the registry** (inspiration for what people actually ship): + https://plugins.danklinux.com/ + Source: https://github.com/AvengeMedia/dms-plugin-registry (the `plugins/` and `themes/` directories) + + Look for plugins in the same category as what you're building. Many are high quality. + +## Tooling & Helpers You Should Know Exist + +- `dms` CLI (the Go binary) — `dms ipc call plugins ...`, `dms plugins install`, etc. +- `Proc` singleton (`qs.Common`) — debounced external command runner with stdout capture. +- `PopoutComponent` (`qs.Modules.Plugins`) — consistent header + close behavior for popouts. +- The family of `*Setting` components + `PluginSettings` wrapper (`qs.Modules.Plugins`). +- `PluginGlobalVar` helper for reactive cross-instance state. +- `DesktopPluginComponent` for desktop layer widgets. + +## Development Environment + +- Quickshell language server (qmlls) setup is documented in the official development guide and in the DMS repo's CONTRIBUTING.md. +- Symlink + `dms ipc call plugins reload ` is the standard fast iteration loop. +- Many plugin authors keep their source outside the plugins directory and symlink during development. + +## Registry Submission + +- Follow the instructions in https://github.com/AvengeMedia/dms-plugin-registry/blob/master/CONTRIBUTING.md +- You will need: public Git repo, `plugin.json`, README, screenshots, (ideally) a version tag. +- The schema is enforced. + +## When You're Really Stuck + +1. Reproduce the problem in the smallest possible plugin using one of the templates in this skill. +2. Run the shell from a terminal and watch the logs. +3. Search the DMS GitHub issues and discussions with the exact error or behavior. +4. The Matrix/Discord space linked from the official site is active. + +This ecosystem is unusually well-documented and has a healthy set of real examples. The fastest path is almost always "find a plugin that does something similar and read its source while keeping the official development guide open." \ No newline at end of file diff --git a/skills/dms-plugin-dev/docs/plugin-types.md b/skills/dms-plugin-dev/docs/plugin-types.md new file mode 100644 index 0000000..1016d89 --- /dev/null +++ b/skills/dms-plugin-dev/docs/plugin-types.md @@ -0,0 +1,44 @@ +# DMS Plugin Types — Decision Guide + +Use this when you need to choose (or confirm) the right `type` and shape for a new plugin. + +## Quick Decision Tree + +**Does the user want something that appears in the DankBar (the top/bottom/left/right bar)?** +→ `type: "widget"` + `horizontalBarPill` / `verticalBarPill` (and usually `popoutContent`). + +**Does it belong in the Control Center (the quick-settings panel)?** +→ `type: "widget"` + the `ccWidget*` properties (and optionally `ccDetailContent` for CompoundPill style). + +**Does it add searchable items to the launcher / spotlight?** +→ `type: "launcher"`. Root element must be a `QtObject` (not an `Item`). You implement `getItems(query)` and `executeItem(item)`. Optional: `getContextMenuActions`, `getCategories`, paste support via `getPasteText`/`getPasteArgs`. + +**Is it purely background work (react to wallpaper changes, battery events, run scheduled tasks, provide services to other plugins)?** +→ `type: "daemon"`. No UI components required. Often just `Connections` to singletons + `Process` / `Proc` usage. + +**Does the user want a free-floating, draggable, resizable widget that lives on the desktop background layer (like a desktop clock or system monitor tile)?** +→ `type: "desktop"` + `DesktopPluginComponent`. User can right-click-drag to move and resize. Supports `minWidth`/`minHeight`, persistence of position/size per screen, responsive layout based on `widgetWidth`/`widgetHeight`. + +## Secondary Signals + +- Needs to show a grid or list in a popout from the bar → widget + `PopoutComponent` + `layerNamespacePlugin`. +- Needs user-configurable settings that appear in the Plugins tab → add `"settings": "./SomethingSettings.qml"` + `PluginSettings` root + the `*Setting` components. **Must** declare `settings_write` permission. +- Needs to share reactive state across multiple instances of the same plugin (multi-monitor bar, multiple widgets) → `PluginGlobalVar` or `PluginService.getGlobalVar` / `setGlobalVar`. +- Needs to persist runtime data that is **not** user settings (command history, recent items, caches that should survive reloads but not be in settings.json) → `PluginService` state API (`savePluginState` / `loadPluginState`). +- Needs to run external commands and capture output reliably → `Proc.runCommand(id, argv, callback, debounceMs)` from `qs.Common`. + +## Type + Capabilities Examples (from real plugins) + +- Simple status pill in bar: `"type": "widget"`, capabilities `["dankbar-widget"]` +- VPN / service toggle that also appears in Control Center: `"type": "widget"`, capabilities `["dankbar-widget", "control-center"]` +- Emoji / GIF / calculator picker in launcher: `"type": "launcher"`, `"trigger": "..."` or `""` +- Battery alert daemon that shows toasts: `"type": "daemon"`, capabilities `["watch-events"]` +- Desktop clock or resource graph the user can park anywhere: `"type": "desktop"`, capabilities `["desktop-widget"]` + +## One-Paragraph Rule of Thumb + +Start with **widget** unless the request is explicitly "add this to the launcher", "run this in the background with no UI", or "a thing the user can drag around on the desktop like a sticky note or monitor". + +When in doubt, ask one clarifying question: "Should this live in the bar, in the launcher, on the desktop background, or be invisible background logic?" + +See also the official "Plugin Types" section in the development guide for the full property lists per type. \ No newline at end of file diff --git a/skills/dms-plugin-dev/templates/desktop/DesktopWidget.qml b/skills/dms-plugin-dev/templates/desktop/DesktopWidget.qml new file mode 100644 index 0000000..b4d4681 --- /dev/null +++ b/skills/dms-plugin-dev/templates/desktop/DesktopWidget.qml @@ -0,0 +1,43 @@ +import QtQuick +import Quickshell +import qs.Common +import qs.Modules.Plugins + +DesktopPluginComponent { + id: root + + minWidth: 160 + minHeight: 100 + defaultWidth: 200 + defaultHeight: 120 + + // Settings are available via pluginData (reactive) + property string label: pluginData.label || "Desktop Widget" + property real bgOpacity: (pluginData.bgOpacity ?? 70) / 100 + + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainer, root.bgOpacity) + border.color: Theme.outline + border.width: 1 + + Column { + anchors.centerIn: parent + spacing: Theme.spacingS + + StyledText { + text: root.label + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: "Drag to move • Resize from corner" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + } + } + } +} diff --git a/skills/dms-plugin-dev/templates/desktop/README.md b/skills/dms-plugin-dev/templates/desktop/README.md new file mode 100644 index 0000000..30bb4f4 --- /dev/null +++ b/skills/dms-plugin-dev/templates/desktop/README.md @@ -0,0 +1,19 @@ +# Example Desktop Widget Template + +A minimal free-floating, user-movable and resizable widget that lives on the desktop background layer. + +## Key Points + +- Uses `DesktopPluginComponent` (not `PluginComponent`) +- Set `minWidth`/`minHeight` (and optionally defaults) +- Read `widgetWidth`, `widgetHeight` for responsive behavior +- Position and size are persisted automatically per screen +- User interaction: right-click + drag anywhere to move, right-click + drag the bottom-right corner to resize + +## Usage + +Copy the folder, update `plugin.json`, customize the visual content. + +Add real settings as needed. The template already demonstrates `PluginSettings` + `StringSetting` + `SliderSetting`. + +See the official development guide for the full `DesktopPluginComponent` surface (including `getData`/`setData` helpers and time-based update patterns using `SystemClock`). \ No newline at end of file diff --git a/skills/dms-plugin-dev/templates/desktop/Settings.qml b/skills/dms-plugin-dev/templates/desktop/Settings.qml new file mode 100644 index 0000000..7c40b03 --- /dev/null +++ b/skills/dms-plugin-dev/templates/desktop/Settings.qml @@ -0,0 +1,32 @@ +import QtQuick +import qs.Common +import qs.Widgets +import qs.Modules.Plugins + +PluginSettings { + id: root + pluginId: "exampleDesktopWidget" + + StyledText { + width: parent.width + text: "Desktop Widget Settings" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + } + + StringSetting { + settingKey: "label" + label: "Label" + defaultValue: "Desktop Widget" + } + + SliderSetting { + settingKey: "bgOpacity" + label: "Background Opacity" + defaultValue: 70 + minimum: 0 + maximum: 100 + unit: "%" + } +} diff --git a/skills/dms-plugin-dev/templates/desktop/plugin.json b/skills/dms-plugin-dev/templates/desktop/plugin.json new file mode 100644 index 0000000..bfadfbe --- /dev/null +++ b/skills/dms-plugin-dev/templates/desktop/plugin.json @@ -0,0 +1,14 @@ +{ + "id": "exampleDesktopWidget", + "name": "Example Desktop Widget", + "description": "Minimal free-floating desktop layer widget. User can drag and resize it.", + "version": "0.1.0", + "author": "DMS Plugin Skill", + "icon": "desktop_windows", + "type": "desktop", + "capabilities": ["desktop-widget"], + "component": "./DesktopWidget.qml", + "settings": "./Settings.qml", + "permissions": ["settings_read", "settings_write"], + "requires_dms": ">=1.2.0" +} \ No newline at end of file diff --git a/skills/dms-plugin-dev/templates/launcher/Launcher.qml b/skills/dms-plugin-dev/templates/launcher/Launcher.qml new file mode 100644 index 0000000..8fa5222 --- /dev/null +++ b/skills/dms-plugin-dev/templates/launcher/Launcher.qml @@ -0,0 +1,63 @@ +import QtQuick +import Quickshell +import qs.Common +import qs.Services + +QtObject { + id: root + + property var pluginService: null + property string pluginId: "exampleLauncher" + + signal itemsChanged + + Component.onCompleted: { + // Example: load a trigger from settings if you want it configurable + if (pluginService) { + // trigger is usually set in plugin.json, but you can override at runtime + } + } + + // Required for launcher plugins + function getItems(query) { + if (!query || query.trim().length === 0) { + return [] + } + + const q = query.trim().toLowerCase() + + // Very simple demo: echo back what the user typed + return [{ + name: "Echo: " + query, + icon: "material:search", + comment: "Demo launcher item — replace with real logic", + action: "copy:" + query, + categories: ["Example"] + }] + } + + function executeItem(item) { + if (!item || !item.action) return + + if (item.action.startsWith("copy:")) { + const text = item.action.substring(5) + Quickshell.execDetached(["dms", "cl", "copy", text]) + ToastService.showInfo("Copied to clipboard") + } + } + + // Optional but nice: context menu on right-click / Tab + function getContextMenuActions(item) { + if (!item) return [] + return [ + { + icon: "content_copy", + text: "Copy", + action: () => { + Quickshell.execDetached(["dms", "cl", "copy", item.action.substring(5)]) + ToastService.showInfo("Copied") + } + } + ] + } +} diff --git a/skills/dms-plugin-dev/templates/launcher/README.md b/skills/dms-plugin-dev/templates/launcher/README.md new file mode 100644 index 0000000..6eab621 --- /dev/null +++ b/skills/dms-plugin-dev/templates/launcher/README.md @@ -0,0 +1,20 @@ +# Example Launcher Plugin Template + +Minimal correct launcher extension. + +## Important Differences from Widget Plugins + +- Root element **must** be a `QtObject`, not an `Item` or `PluginComponent`. +- You implement `getItems(query)` and `executeItem(item)`. +- No visual UI of your own — you return data that the launcher renders. + +## Usage + +1. Copy the folder. +2. Update `plugin.json` — especially the `trigger` (what the user types to activate you). +3. Implement real search logic in `getItems`. +4. Handle actions in `executeItem` (copy, open URL, run command, etc.). + +See the official docs for paste support (`getPasteText`/`getPasteArgs`), dynamic categories, and requesting launcher updates after async work. + +Good real-world references: DankGifSearch and DankStickerSearch in the first-party plugins repo. \ No newline at end of file diff --git a/skills/dms-plugin-dev/templates/launcher/plugin.json b/skills/dms-plugin-dev/templates/launcher/plugin.json new file mode 100644 index 0000000..4e012ab --- /dev/null +++ b/skills/dms-plugin-dev/templates/launcher/plugin.json @@ -0,0 +1,14 @@ +{ + "id": "exampleLauncher", + "name": "Example Launcher Plugin", + "description": "Minimal launcher extension. Root is a QtObject. Demonstrates getItems/executeItem.", + "version": "0.1.0", + "author": "DMS Plugin Skill", + "icon": "search", + "type": "launcher", + "trigger": "!", + "viewMode": "list", + "viewModeEnforced": false, + "component": "./Launcher.qml", + "permissions": ["settings_read", "settings_write"] +} \ No newline at end of file diff --git a/skills/dms-plugin-dev/templates/widget-bar/README.md b/skills/dms-plugin-dev/templates/widget-bar/README.md new file mode 100644 index 0000000..84e4ef5 --- /dev/null +++ b/skills/dms-plugin-dev/templates/widget-bar/README.md @@ -0,0 +1,40 @@ +# Example Bar Widget Template + +Minimal, modern starter for a simple DankBar widget that shows text and has one setting. + +## How to Use This Template + +1. Copy the entire `widget-bar/` folder to: + `~/.config/DankMaterialShell/plugins/YourPluginName/` + +2. Edit `plugin.json`: + - Change `id` (must be unique, camelCase) + - Update `name`, `description`, `author` + - Adjust `icon` if desired + +3. Rename and customize `Widget.qml` and `Settings.qml`. + +4. Enable the plugin: + - Open DMS Settings → Plugins + - Click "Scan for Plugins" + - Toggle it on + - Add it to your DankBar layout (Appearance → DankBar) + +5. Reload during development: + `dms ipc call plugins reload yourPluginId` + +## What This Demonstrates + +- Correct `plugin.json` with `settings_write` permission +- Use of `PluginComponent` with both horizontal and vertical pills +- Reactive access to settings via `pluginData` +- Modern settings UI using `PluginSettings` + `StringSetting` +- Proper use of `Theme` and `DankIcon` + +## Next Steps + +- Add a `popoutContent` for a panel when the user clicks the pill. +- Add `layerNamespacePlugin` if you add a popout. +- Explore the other templates in this skill for launcher, desktop, and Control Center patterns. + +This template is intentionally tiny. Read the official plugin development guide for the full surface. \ No newline at end of file diff --git a/skills/dms-plugin-dev/templates/widget-bar/Settings.qml b/skills/dms-plugin-dev/templates/widget-bar/Settings.qml new file mode 100644 index 0000000..da95e25 --- /dev/null +++ b/skills/dms-plugin-dev/templates/widget-bar/Settings.qml @@ -0,0 +1,33 @@ +import QtQuick +import qs.Common +import qs.Widgets +import qs.Modules.Plugins + +PluginSettings { + id: root + pluginId: "exampleBarWidget" + + StyledText { + width: parent.width + text: "Example Bar Widget Settings" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + } + + StyledText { + width: parent.width + text: "This is a minimal starter. Replace with your own settings." + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + wrapMode: Text.WordWrap + } + + StringSetting { + settingKey: "displayText" + label: "Display Text" + description: "Text shown in the bar pill" + placeholder: "Hello" + defaultValue: "Hello" + } +} diff --git a/skills/dms-plugin-dev/templates/widget-bar/Widget.qml b/skills/dms-plugin-dev/templates/widget-bar/Widget.qml new file mode 100644 index 0000000..242077b --- /dev/null +++ b/skills/dms-plugin-dev/templates/widget-bar/Widget.qml @@ -0,0 +1,53 @@ +import QtQuick +import qs.Common +import qs.Widgets +import qs.Modules.Plugins + +PluginComponent { + id: root + + // Access saved settings reactively via pluginData + property string displayText: pluginData.displayText || "Hello" + + horizontalBarPill: Component { + Row { + spacing: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + name: "widgets" + size: root.iconSize + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: root.displayText + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + } + + verticalBarPill: Component { + Column { + spacing: Theme.spacingXS + anchors.horizontalCenter: parent.horizontalCenter + + DankIcon { + name: "widgets" + size: root.iconSize + color: Theme.primary + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: root.displayText + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + anchors.horizontalCenter: parent.horizontalCenter + } + } + } +} diff --git a/skills/dms-plugin-dev/templates/widget-bar/plugin.json b/skills/dms-plugin-dev/templates/widget-bar/plugin.json new file mode 100644 index 0000000..935190a --- /dev/null +++ b/skills/dms-plugin-dev/templates/widget-bar/plugin.json @@ -0,0 +1,13 @@ +{ + "id": "exampleBarWidget", + "name": "Example Bar Widget", + "description": "Minimal generic starter for a DankBar widget with settings. Copy and customize.", + "version": "0.1.0", + "author": "DMS Plugin Skill", + "icon": "widgets", + "type": "widget", + "capabilities": ["dankbar-widget"], + "component": "./Widget.qml", + "settings": "./Settings.qml", + "permissions": ["settings_read", "settings_write"] +} \ No newline at end of file diff --git a/skills/dms-plugin-dev/templates/widget-popout/README.md b/skills/dms-plugin-dev/templates/widget-popout/README.md new file mode 100644 index 0000000..d7db5a5 --- /dev/null +++ b/skills/dms-plugin-dev/templates/widget-popout/README.md @@ -0,0 +1,18 @@ +# Example Popout Widget Template + +Bar widget that opens a styled popout panel when clicked. + +## Key Things This Template Shows + +- `layerNamespacePlugin` (critical for popouts) +- `PopoutComponent` usage with proper height calculation +- `closePopout()` available on the PopoutComponent root +- Separate horizontal/vertical bar pills + +## Usage + +Copy the folder, update `plugin.json`, customize the content inside the `PopoutComponent`, add real settings as needed. + +Add `layerNamespacePlugin: "your-unique-id"` on every widget that opens a popout. + +See the official docs for `PopoutComponent` props (`headerHeight`, `detailsHeight`, etc.). \ No newline at end of file diff --git a/skills/dms-plugin-dev/templates/widget-popout/Settings.qml b/skills/dms-plugin-dev/templates/widget-popout/Settings.qml new file mode 100644 index 0000000..635cee9 --- /dev/null +++ b/skills/dms-plugin-dev/templates/widget-popout/Settings.qml @@ -0,0 +1,23 @@ +import QtQuick +import qs.Common +import qs.Widgets +import qs.Modules.Plugins + +PluginSettings { + id: root + pluginId: "examplePopoutWidget" + + StyledText { + width: parent.width + text: "Popout Widget Settings" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + } + + StringSetting { + settingKey: "header" + label: "Popout Header" + defaultValue: "Example Popout" + } +} diff --git a/skills/dms-plugin-dev/templates/widget-popout/Widget.qml b/skills/dms-plugin-dev/templates/widget-popout/Widget.qml new file mode 100644 index 0000000..795337f --- /dev/null +++ b/skills/dms-plugin-dev/templates/widget-popout/Widget.qml @@ -0,0 +1,74 @@ +import QtQuick +import qs.Common +import qs.Widgets +import qs.Modules.Plugins + +PluginComponent { + id: root + + // REQUIRED for popouts to avoid z-order/focus issues with other DMS popouts + layerNamespacePlugin: "example-popout" + + property string header: pluginData.header || "Example Popout" + + horizontalBarPill: Component { + Row { + spacing: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + name: "menu" + size: root.iconSize + color: Theme.primary + } + StyledText { + text: root.header + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + } + } + } + + verticalBarPill: Component { + Column { + spacing: Theme.spacingXS + anchors.horizontalCenter: parent.horizontalCenter + + DankIcon { + name: "menu" + size: root.iconSize + color: Theme.primary + } + StyledText { + text: root.header + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + } + } + } + + popoutContent: Component { + PopoutComponent { + id: popout + headerText: root.header + detailsText: "This is a generic popout starter. Replace the content below." + showCloseButton: true + + // Calculate available height for your content + Item { + width: parent.width + implicitHeight: 200 // Adjust as needed + + StyledText { + anchors.centerIn: parent + text: "Your custom content goes here.\n\nAccess pluginData for settings.\nCall popout.closePopout() when done." + color: Theme.surfaceText + horizontalAlignment: Text.AlignHCenter + } + } + } + } + + popoutWidth: 360 + popoutHeight: 300 +} diff --git a/skills/dms-plugin-dev/templates/widget-popout/plugin.json b/skills/dms-plugin-dev/templates/widget-popout/plugin.json new file mode 100644 index 0000000..3dcbf12 --- /dev/null +++ b/skills/dms-plugin-dev/templates/widget-popout/plugin.json @@ -0,0 +1,12 @@ +{ + "id": "examplePopoutWidget", + "name": "Example Popout Widget", + "description": "Bar widget that opens a popout panel. Demonstrates PopoutComponent and layer namespaces.", + "version": "0.1.0", + "author": "DMS Plugin Skill", + "icon": "menu", + "type": "widget", + "capabilities": ["dankbar-widget"], + "component": "./Widget.qml", + "permissions": ["settings_read", "settings_write"] +} \ No newline at end of file