1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

docs: update PLUGIN docs (#443)

This commit is contained in:
Bruno Cesar Rocha
2025-10-15 13:14:49 +01:00
committed by GitHub
parent 5f810fe741
commit f4a4151632
10 changed files with 400 additions and 28 deletions

View File

@@ -4,8 +4,9 @@
"description": "Example plugin with Control Center detail dropdown",
"version": "1.0.0",
"author": "DankMaterialShell",
"icon": "settings",
"type": "widget",
"capabilities": ["control-center"],
"component": "./DetailExampleWidget.qml",
"icon": "settings",
"permissions": ["settings_read", "settings_write"]
}

View File

@@ -4,8 +4,9 @@
"description": "Example plugin with Control Center toggle widget",
"version": "1.0.0",
"author": "DankMaterialShell",
"icon": "toggle_on",
"type": "widget",
"capabilities": ["control-center"],
"component": "./ControlCenterExampleWidget.qml",
"icon": "toggle_on",
"permissions": ["settings_read", "settings_write"]
}

View File

@@ -4,8 +4,10 @@
"description": "Display cycling emojis in your bar with a handy emoji picker popout",
"version": "1.0.0",
"author": "AvengeMedia",
"icon": "mood",
"type": "widget",
"capabilities": ["emoji-search", "clipboard", "dankbar-widget"],
"component": "./EmojiWidget.qml",
"icon": "mood",
"settings": "./EmojiSettings.qml",
"permissions": [
"settings_read",

View File

@@ -4,8 +4,10 @@
"description": "Demonstrates dynamic variant creation for plugins",
"version": "1.0.0",
"author": "DMS",
"icon": "widgets",
"type": "widget",
"capabilities": ["multiple-usecases", "variants"],
"component": "./VariantWidget.qml",
"icon": "widgets",
"settings": "./VariantSettings.qml",
"permissions": [
"settings_read",

View File

@@ -1,4 +1,5 @@
import QtQuick
import Quickshell
import qs.Services
Item {
@@ -111,13 +112,8 @@ Item {
}
function copyToClipboard(text) {
if (typeof globalThis !== "undefined" && globalThis.clipboard) {
globalThis.clipboard.setText(text)
showToast("Copied to clipboard: " + text)
} else {
console.log("LauncherExample: Would copy to clipboard:", text)
showToast("Copy feature not available")
}
Quickshell.execDetached(["sh", "-c", "echo -n '" + text + "' | wl-copy"])
showToast("Copied to clipboard: " + text)
}
function runScript(command) {

View File

@@ -6,7 +6,7 @@
"author": "DMS Team",
"icon": "extension",
"type": "launcher",
"capabilities": ["launcher"],
"capabilities": ["clipboard", "command-execution"],
"component": "./LauncherExampleLauncher.qml",
"settings": "./LauncherExampleSettings.qml",
"trigger": "#",
@@ -14,4 +14,4 @@
"settings_read",
"settings_write"
]
}
}

View File

@@ -4,9 +4,10 @@
"description": "Example widget demonstrating PopoutService usage with pillClickAction",
"version": "1.0.0",
"author": "DankMaterialShell",
"icon": "widgets",
"type": "widget",
"capabilities": ["dankbar-widget"],
"component": "./PopoutControlWidget.qml",
"icon": "widgets",
"settings": "./PopoutControlSettings.qml",
"permissions": ["settings_read", "settings_write"]
}

View File

@@ -2,6 +2,10 @@
Create widgets for DankBar and Control Center using dynamically-loaded QML components.
## Plugin Registry
Browse and discover community plugins at **https://plugins.danklinux.com/**
## Overview
Plugins let you add custom widgets to DankBar and Control Center. They're discovered from `~/.config/DankMaterialShell/plugins/` and managed via PluginService.
@@ -45,7 +49,9 @@ $CONFIGPATH/DankMaterialShell/plugins/YourPlugin/
### Plugin Manifest (plugin.json)
The manifest file defines plugin metadata and configuration:
The manifest file defines plugin metadata and configuration.
**JSON Schema:** See `plugin-schema.json` for the complete specification and validation schema.
```json
{
@@ -54,9 +60,13 @@ The manifest file defines plugin metadata and configuration:
"description": "Brief description of what your plugin does",
"version": "1.0.0",
"author": "Your Name",
"icon": "material_icon_name",
"type": "widget",
"capabilities": ["thing-my-plugin-does"],
"component": "./YourWidget.qml",
"icon": "material_icon_name",
"settings": "./YourSettings.qml",
"requires_dms": ">=0.1.0",
"requires": ["some-system-tool"],
"permissions": [
"settings_read",
"settings_write"
@@ -67,15 +77,22 @@ The manifest file defines plugin metadata and configuration:
**Required Fields:**
- `id`: Unique plugin identifier (camelCase, no spaces)
- `name`: Human-readable plugin name
- `component`: Relative path to widget QML file
- `description`: Short description of plugin functionality (displayed in UI)
- `version`: Semantic version string (e.g., "1.0.0")
- `author`: Plugin creator name or email
- `type`: Plugin type - "widget", "daemon", or "launcher"
- `capabilities`: Array of plugin capabilities (e.g., ["dankbar-widget"], ["control-center"], ["monitoring"])
- `component`: Relative path to main QML component file
**Required for Launcher Type:**
- `trigger`: Trigger string for launcher activation (e.g., "=", "#", "!")
**Optional Fields:**
- `description`: Short description of plugin functionality (displayed in UI)
- `version`: Semantic version string (displayed in UI)
- `author`: Plugin creator name (displayed in UI)
- `icon`: Material Design icon name (displayed in UI)
- `settings`: Path to settings component (enables settings UI)
- `permissions`: Required capabilities (enforced by PluginSettings component)
- `requires_dms`: Minimum DMS version requirement (e.g., ">=0.1.18", ">0.1.0")
- `requires`: Array of required system tools/dependencies (e.g., ["wl-copy", "curl"])
- `permissions`: Required DMS permissions (e.g., ["settings_read", "settings_write"])
**Permissions:**
@@ -574,9 +591,12 @@ Create `plugin.json`:
"description": "A sample plugin",
"version": "1.0.0",
"author": "Your Name",
"icon": "extension",
"type": "widget",
"capabilities": ["my-functionality"],
"component": "./MyWidget.qml",
"icon": "extension",
"settings": "./MySettings.qml",
"requires_dms": ">=0.1.0",
"permissions": ["settings_read", "settings_write"]
}
```
@@ -709,6 +729,231 @@ Or edit `$CONFIGPATH/quickshell/dms/config.json`:
8. **Versioning**: Use semantic versioning for updates
9. **Dependencies**: Document external library requirements
## Clipboard Access
Plugins that need to copy text to the clipboard **must** use the Wayland clipboard utility `wl-copy` through Quickshell's `execDetached` function.
### Correct Method
Import Quickshell and use `execDetached` with `wl-copy`:
```qml
import QtQuick
import Quickshell
Item {
function copyToClipboard(text) {
Quickshell.execDetached(["sh", "-c", "echo -n '" + text + "' | wl-copy"])
}
}
```
### Example Usage
From the ExampleEmojiPlugin (EmojiWidget.qml:136):
```qml
MouseArea {
onClicked: {
Quickshell.execDetached(["sh", "-c", "echo -n '" + modelData + "' | wl-copy"])
ToastService.showInfo("Copied " + modelData + " to clipboard")
popoutColumn.closePopout()
}
}
```
### Important Notes
1. **Do NOT** use `globalThis.clipboard` or similar JavaScript APIs - they don't exist in the QML runtime
2. **Always** import `Quickshell` at the top of your QML file
3. **Use** `echo -n` to prevent adding a trailing newline to the clipboard content
4. The `-c` flag for `sh` is required to execute the pipe command properly
5. Consider showing a toast notification to confirm the copy action to users
### Dependencies
This method requires `wl-copy` from the `wl-clipboard` package, which is standard on Wayland systems.
## Running External Commands
Plugins that need to execute external commands and capture their output should use the `Proc` singleton, which provides debounced command execution with automatic cleanup.
### Correct Method
Import the `Proc` singleton from `qs.Common` and use `runCommand`:
```qml
import QtQuick
import qs.Common
Item {
function fetchData() {
Proc.runCommand(
"myPlugin.fetchData",
["curl", "-s", "https://api.example.com/data"],
(stdout, exitCode) => {
if (exitCode === 0) {
console.log("Success:", stdout)
processData(stdout)
} else {
console.error("Command failed with exit code:", exitCode)
}
},
100
)
}
}
```
### Function Signature
```qml
Proc.runCommand(id, command, callback, debounceMs)
```
**Parameters:**
- `id` (string): Unique identifier for this command. Used for debouncing - multiple calls with the same ID within the debounce window will only execute the last one
- `command` (array): Command and arguments as an array (e.g., `["sh", "-c", "echo hello"]`)
- `callback` (function): Callback function receiving `(stdout, exitCode)` when the command completes
- `stdout` (string): Captured standard output from the command
- `exitCode` (number): Exit code of the process (0 typically means success)
- `debounceMs` (number, optional): Debounce delay in milliseconds. Defaults to 50ms if not specified
### Key Features
1. **Automatic Cleanup**: Process objects are automatically destroyed after completion
2. **Debouncing**: Rapid successive calls with the same ID are debounced, only executing the last one
3. **Output Capture**: Automatically captures stdout for processing
4. **Error Handling**: Exit codes are passed to the callback for error detection
### Example Usage
#### Simple Command Execution
```qml
import QtQuick
import qs.Common
Item {
function checkNetwork() {
Proc.runCommand(
"myPlugin.ping",
["ping", "-c", "1", "8.8.8.8"],
(output, exitCode) => {
if (exitCode === 0) {
console.log("Network is up")
} else {
console.log("Network is down")
}
}
)
}
}
```
#### Parsing Command Output
```qml
import QtQuick
import qs.Common
Item {
property var diskUsage: ({})
function updateDiskUsage() {
Proc.runCommand(
"myPlugin.df",
["df", "-h", "/home"],
(output, exitCode) => {
if (exitCode === 0) {
const lines = output.trim().split("\n")
if (lines.length > 1) {
const parts = lines[1].split(/\s+/)
diskUsage = {
total: parts[1],
used: parts[2],
available: parts[3],
percent: parts[4]
}
}
}
}
)
}
}
```
#### Shell Commands with Pipes
```qml
import QtQuick
import qs.Common
Item {
function getTopProcess() {
Proc.runCommand(
"myPlugin.topProcess",
["sh", "-c", "ps aux | sort -nrk 3,3 | head -n 1"],
(output, exitCode) => {
if (exitCode === 0) {
console.log("Top process:", output)
}
}
)
}
}
```
#### Debouncing Rapid Updates
```qml
import QtQuick
import qs.Common
import qs.Widgets
Item {
DankTextField {
id: searchField
placeholderText: "Search files..."
onTextChanged: {
Proc.runCommand(
"myPlugin.search",
["find", "/home", "-name", "*" + text + "*"],
(output, exitCode) => {
if (exitCode === 0) {
updateSearchResults(output)
}
},
500
)
}
}
}
```
### Important Notes
1. **Unique IDs**: Use descriptive, namespaced IDs (e.g., `"myPlugin.actionName"`) to avoid conflicts
2. **Debouncing**: Use appropriate debounce delays for your use case:
- Fast updates (50-100ms): System monitoring, real-time data
- User input (300-500ms): Search fields, text input processing
- Network requests (500-1000ms): API calls, web scraping
3. **Error Handling**: Always check the exit code in your callback before processing output
4. **Shell Commands**: Use `["sh", "-c", "command"]` for complex shell commands with pipes or redirects
5. **Security**: Sanitize user input before passing to commands to prevent command injection
6. **Performance**: Avoid running expensive commands too frequently - use debouncing wisely
### Comparison with Other Methods
**Proc.runCommand** vs **Quickshell.execDetached**:
- Use `Proc.runCommand` when you need to capture output or check exit codes
- Use `Quickshell.execDetached` for fire-and-forget operations (like clipboard copy)
**Proc.runCommand** vs **Process component**:
- Use `Proc.runCommand` for simple, one-off command executions with automatic cleanup
- Use `Process` component for long-running processes or when you need fine-grained control
## Debugging
### Console Logging
@@ -792,10 +1037,16 @@ To create a launcher plugin, set the plugin type in `plugin.json`:
{
"id": "myLauncher",
"name": "My Launcher Plugin",
"description": "A custom launcher plugin for quick actions",
"version": "1.0.0",
"author": "Your Name",
"type": "launcher",
"capabilities": ["launcher"],
"capabilities": ["show-thing"],
"component": "./MyLauncher.qml",
"trigger": "#",
"icon": "search",
"settings": "./MySettings.qml",
"requires_dms": ">=0.1.18",
"permissions": ["settings_read", "settings_write"]
}
```
@@ -1022,9 +1273,10 @@ See `PLUGINS/LauncherExample/` for a complete working example demonstrating:
## Resources
- **Example Plugins**:
- [Emoji Picker](./ExampleEmojiPlugin/)
- [WorldClock](https://github.com/rochacbruno/WorldClock)
- **Plugin Schema**: `plugin-schema.json` - JSON Schema for validation
- **Example Plugins**:
- [Emoji Picker](./ExampleEmojiPlugin/)
- [WorldClock](https://github.com/rochacbruno/WorldClock)
- [LauncherExample](./LauncherExample/)
- [Calculator](https://github.com/rochacbruno/DankCalculator)
- **PluginService**: `Services/PluginService.qml`
@@ -1039,7 +1291,8 @@ See `PLUGINS/LauncherExample/` for a complete working example demonstrating:
Share your plugins with the community:
1. Create a public repository with your plugin
2. Include comprehensive README.md
2. Validate your `plugin.json` against `plugin-schema.json`
3. Include comprehensive README.md
4. Add example screenshots
5. Document dependencies and permissions

View File

@@ -4,9 +4,10 @@
"description": "Background daemon that monitors wallpaper changes and logs them",
"version": "1.0.0",
"author": "DankMaterialShell",
"icon": "wallpaper",
"type": "daemon",
"capabilities": ["wallpaper", "monitoring"],
"component": "./WallpaperWatcherDaemon.qml",
"icon": "wallpaper",
"settings": "./WallpaperWatcherSettings.qml",
"permissions": [
"settings_read",

115
PLUGINS/plugin-schema.json Normal file
View File

@@ -0,0 +1,115 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://danklinux.com/schemas/plugin.json",
"title": "DankMaterialShell Plugin Manifest",
"description": "Schema for DankMaterialShell plugin.json manifest files",
"type": "object",
"required": [
"id",
"name",
"description",
"version",
"author",
"type",
"capabilities",
"component"
],
"properties": {
"id": {
"type": "string",
"description": "Unique plugin identifier (camelCase, no spaces)",
"pattern": "^[a-zA-Z][a-zA-Z0-9]*$"
},
"name": {
"type": "string",
"description": "Human-readable plugin name",
"minLength": 1
},
"description": {
"type": "string",
"description": "Short description of plugin functionality",
"minLength": 1
},
"version": {
"type": "string",
"description": "Semantic version string (e.g., '1.0.0')",
"pattern": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$"
},
"author": {
"type": "string",
"description": "Plugin creator name or email",
"minLength": 1
},
"type": {
"type": "string",
"description": "Plugin type",
"enum": ["widget", "daemon", "launcher"]
},
"capabilities": {
"type": "array",
"description": "Array of plugin capabilities",
"items": {
"type": "string"
},
"minItems": 1
},
"component": {
"type": "string",
"description": "Relative path to main QML component file",
"pattern": "^\\./.*\\.qml$"
},
"trigger": {
"type": "string",
"description": "Trigger string for launcher activation (required for launcher type)"
},
"icon": {
"type": "string",
"description": "Material Design icon name"
},
"settings": {
"type": "string",
"description": "Path to settings component QML file",
"pattern": "^\\./.*\\.qml$"
},
"requires_dms": {
"type": "string",
"description": "Minimum DMS version requirement (e.g., '>=0.1.18', '>0.1.0')",
"pattern": "^(>=?|<=?|=|>|<)\\d+\\.\\d+\\.\\d+$"
},
"requires": {
"type": "array",
"description": "Array of required system tools/dependencies",
"items": {
"type": "string"
}
},
"permissions": {
"type": "array",
"description": "Required capabilities",
"items": {
"type": "string",
"enum": [
"settings_read",
"settings_write",
"process",
"network"
]
}
}
},
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "launcher"
}
}
},
"then": {
"required": ["trigger"]
}
}
],
"additionalProperties": true
}