# Advanced Patterns Patterns observed in production DMS plugins that go beyond the basics. ## Plugin Variants Create multiple widget instances from a single plugin definition. Each variant has its own configuration. ### Manifest No special manifest changes needed - the variant system is built into PluginComponent. ### Widget with Variant Support ```qml PluginComponent { property string variantId: "" property var variantData: ({}) property string displayText: variantData?.text || "Default" horizontalBarPill: Component { StyledRect { width: label.implicitWidth + Theme.spacingM * 2 height: parent.widgetThickness StyledText { id: label anchors.centerIn: parent text: root.displayText } } } } ``` Widget format in bar config: `pluginId:variantId` (e.g., `exampleVariants:variant_1234567890`) ### Settings with Variant Management ```qml PluginSettings { pluginId: "exampleVariants" // Variant creation UI DankButton { text: "Add New Instance" onClicked: { var id = "variant_" + Date.now() root.createVariant(id, { name: "New Instance", text: "Hello" }) } } // Per-variant configuration Repeater { model: root.variants delegate: Column { StringSetting { settingKey: modelData.id + "_text" label: modelData.name || modelData.id } } } } ``` ## JavaScript Utility Files For complex logic, split into `.js` files: ### utils.js ```javascript .pragma library function formatDuration(ms) { if (ms < 60000) return "just now" if (ms < 3600000) return Math.floor(ms / 60000) + "m ago" return Math.floor(ms / 3600000) + "h ago" } function parseResponse(json) { try { return JSON.parse(json) } catch (e) { return null } } ``` ### Using in QML ```qml import "utils.js" as Utils Item { StyledText { text: Utils.formatDuration(Date.now() - timestamp) } } ``` The `.pragma library` directive makes the JS file a shared singleton - it is loaded once and shared across all QML instances that import it. ## qmldir for Singleton Services For plugins with internal singleton services: ### qmldir ``` singleton MyService 1.0 MyService.qml ``` ### MyService.qml ```qml pragma Singleton import QtQuick QtObject { property var cache: ({}) function getData(key) { return cache[key] || null } function setData(key, value) { cache[key] = value } } ``` ### Using the singleton ```qml import "." as Local Item { Component.onCompleted: { Local.MyService.setData("key", "value") } } ``` ## Inline Component Declarations Reusable sub-components defined inline: ```qml Item { component StatusBadge: Rectangle { property string label: "" property color badgeColor: Theme.primary width: badgeText.implicitWidth + Theme.spacingM * 2 height: 24 radius: 12 color: badgeColor StyledText { id: badgeText anchors.centerIn: parent text: label color: Theme.onPrimary font.pixelSize: Theme.fontSizeSmall } } Row { spacing: Theme.spacingS StatusBadge { label: "Running"; badgeColor: Theme.success } StatusBadge { label: "Stopped"; badgeColor: Theme.error } } } ``` ## Multi-Provider Adapter Pattern For plugins supporting multiple backends (AI providers, API services): ### apiAdapters.js ```javascript .pragma library function createAdapter(provider) { switch (provider) { case "openai": return { url: "https://api.openai.com/v1/chat/completions", headers: (key) => ({ "Authorization": "Bearer " + key }), formatRequest: (messages) => JSON.stringify({ model: "gpt-4", messages: messages }), parseResponse: (text) => JSON.parse(text).choices[0].message.content } case "anthropic": return { url: "https://api.anthropic.com/v1/messages", headers: (key) => ({ "x-api-key": key, "anthropic-version": "2023-06-01" }), formatRequest: (messages) => JSON.stringify({ model: "claude-sonnet-4-20250514", messages: messages }), parseResponse: (text) => JSON.parse(text).content[0].text } default: return null } } ``` ## IPC Integration For plugins that respond to keyboard shortcuts or external commands: ```qml PluginComponent { Connections { target: DMSIpc function onCommandReceived(command, args) { if (command === "myPlugin.toggle") { doToggle() } else if (command === "myPlugin.next") { goNext() } } } } ``` External trigger: `dms ipc call myPlugin.toggle` ## Networking with Quickshell.Networking For API calls using the built-in networking module: ```qml import Quickshell.Networking Item { NetworkRequest { id: request url: "https://api.example.com/data" method: "GET" onResponseReceived: (response) => { const data = JSON.parse(response.body) processData(data) } onErrorOccurred: (error) => { console.error("Network error:", error) } } function fetchData() { request.send() } } ``` ## Toast Notifications Show user feedback: ```qml import qs.Services // Info toast ToastService?.showInfo("Operation completed") // With title ToastService?.showInfo("Plugin Name", "Data refreshed successfully") ``` Always use optional chaining since ToastService may not be available in all contexts. ## Clipboard Operations ```qml import Quickshell function copyToClipboard(text) { Quickshell.execDetached(["dms", "cl", "copy", text]) ToastService?.showInfo("Copied to clipboard") } ``` Do NOT use `globalThis.clipboard`, `navigator.clipboard`, or any browser API - they do not exist in the QML runtime. ## Multi-File Plugin Architecture Large plugins can be split across multiple files: ``` MyPlugin/ plugin.json Main.qml # Main widget component Settings.qml # Settings UI DetailView.qml # Popout detail view utils.js # Utility functions apiAdapter.js # API adapter layer qmldir # Optional: singleton registrations ``` Import sibling files: ```qml // In Main.qml import "." as Local Item { Loader { source: "DetailView.qml" } } ``` ## Performance Tips 1. Use `Proc.runCommand` with appropriate debounce for external commands 2. Pre-cache images and thumbnails for image-heavy plugins 3. Limit concurrent network requests 4. Use `Timer` with reasonable intervals (don't poll faster than needed) 5. Lazy-load heavy content (use `Loader` for complex popout content) 6. Avoid blocking the UI thread with synchronous operations