12 KiB
Plugin System
The DMS shell includes an experimental plugin system that allows extending functionality through self-contained, dynamically-loaded QML components.
Overview
The plugin system enables developers to create custom widgets that can be displayed in the DankBar alongside built-in widgets. Plugins are discovered, loaded, and managed through the PluginService, providing a clean separation between core shell functionality and user extensions.
Architecture
Core Components
-
PluginService (
Services/PluginService.qml)- Singleton service managing plugin lifecycle
- Discovers plugins from
$CONFIGPATH/DankMaterialShell/plugins/ - Handles loading, unloading, and state management
- Provides data persistence for plugin settings
-
PluginsTab (
Modules/Settings/PluginsTab.qml)- UI for managing available plugins
- Access plugin settings
-
PluginsTab Settings (
Modules/Settings/PluginsTab.qml)- Accordion-style plugin configuration interface
- Dynamically loads plugin settings components inline
- Provides consistent settings interface with proper focus handling
-
DankBar Integration (
Modules/DankBar/DankBar.qml)- Renders plugin widgets in the bar
- Merges plugin components with built-in widgets
- Supports left, center, and right sections
Plugin Structure
Each plugin must be a directory in $CONFIGPATH/DankMaterialShell/plugins/ containing:
$CONFIGPATH/DankMaterialShell/plugins/YourPlugin/
├── plugin.json # Required: Plugin manifest
├── YourWidget.qml # Required: Widget component
├── YourSettings.qml # Optional: Settings UI
├── qmldir # Optional: QML module definition
└── *.js # Optional: JavaScript utilities
Plugin Manifest (plugin.json)
The manifest file defines plugin metadata and configuration:
{
"id": "yourPlugin",
"name": "Your Plugin Name",
"description": "Brief description of what your plugin does",
"version": "1.0.0",
"author": "Your Name",
"icon": "material_icon_name",
"component": "./YourWidget.qml",
"settings": "./YourSettings.qml",
"dependencies": {
"libraryName": {
"url": "https://cdn.example.com/library.js",
"optional": true
}
},
"settings_schema": {
"settingKey": {
"type": "string|number|boolean|array|object",
"default": "defaultValue"
}
},
"permissions": [
"settings_read",
"settings_write"
]
}
Required Fields:
id: Unique plugin identifier (camelCase, no spaces)name: Human-readable plugin namecomponent: Relative path to widget QML file
Optional Fields:
description: Short description of plugin functionalityversion: Semantic version stringauthor: Plugin creator nameicon: Material Design icon namesettings: Path to settings componentdependencies: External JS librariessettings_schema: Configuration schemapermissions: Required capabilities
Widget Component
The main widget component is displayed in the DankBar. It receives several properties from the shell:
import QtQuick
Rectangle {
id: root
// Standard properties provided by DankBar
property bool compactMode: false
property string section: "center" // "left", "center", or "right"
property var popupTarget: null
property var parentScreen: null
property real barHeight: 48
property real widgetHeight: 30
// Widget dimensions
width: content.implicitWidth + horizontalPadding * 2
height: widgetHeight
// PluginService is injected by PluginsTab when loading settings
property var pluginService
// Access plugin data
Component.onCompleted: {
if (pluginService) {
var savedData = pluginService.loadPluginData("yourPlugin", "dataKey", defaultValue)
}
}
// Save plugin data
function saveData(key, value) {
if (pluginService) {
pluginService.savePluginData("yourPlugin", key, value)
}
}
}
Important Properties:
compactMode: Whether the bar is in compact display modesection: Which bar section the widget is inbarHeight: Height of the entire barwidgetHeight: Recommended widget heightparentScreen: Reference to the screen object
Settings Component
Optional settings UI loaded inline in the PluginsTab accordion interface:
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
Column {
id: root
// PluginService is injected by PluginsTab
property var pluginService
spacing: Theme.spacingM
DankTextField {
id: settingInput
width: parent.width
label: "Setting Label"
text: pluginService ? pluginService.loadPluginData("yourPlugin", "settingKey", "default") : ""
onTextChanged: {
if (pluginService) {
pluginService.savePluginData("yourPlugin", "settingKey", text)
}
}
}
DankToggle {
checked: pluginService ? pluginService.loadPluginData("yourPlugin", "enabled", true) : false
onToggled: {
if (pluginService) {
pluginService.savePluginData("yourPlugin", "enabled", checked)
}
}
}
}
PluginService API
Properties
PluginService.pluginDirectory: string
// Path to plugins directory ($CONFIGPATH/DankMaterialShell/plugins)
PluginService.availablePlugins: object
// Map of all discovered plugins {pluginId: pluginInfo}
PluginService.loadedPlugins: object
// Map of currently loaded plugins {pluginId: pluginInfo}
PluginService.pluginWidgetComponents: object
// Map of loaded widget components {pluginId: Component}
Functions
// Plugin Management
PluginService.loadPlugin(pluginId: string): bool
PluginService.unloadPlugin(pluginId: string): bool
PluginService.reloadPlugin(pluginId: string): bool
PluginService.enablePlugin(pluginId: string): bool
PluginService.disablePlugin(pluginId: string): bool
// Plugin Discovery
PluginService.scanPlugins(): void
PluginService.getAvailablePlugins(): array
PluginService.getLoadedPlugins(): array
PluginService.isPluginLoaded(pluginId: string): bool
PluginService.getWidgetComponents(): object
// Data Persistence
PluginService.savePluginData(pluginId: string, key: string, value: any): bool
PluginService.loadPluginData(pluginId: string, key: string, defaultValue: any): any
Signals
PluginService.pluginLoaded(pluginId: string)
PluginService.pluginUnloaded(pluginId: string)
PluginService.pluginLoadFailed(pluginId: string, error: string)
Creating a Plugin
Step 1: Create Plugin Directory
mkdir -p $CONFIGPATH/DankMaterialShell/plugins/MyPlugin
cd $CONFIGPATH/DankMaterialShell/plugins/MyPlugin
Step 2: Create Manifest
Create plugin.json:
{
"id": "myPlugin",
"name": "My Plugin",
"description": "A sample plugin",
"version": "1.0.0",
"author": "Your Name",
"icon": "extension",
"component": "./MyWidget.qml",
"settings": "./MySettings.qml",
"permissions": ["settings_read", "settings_write"]
}
Step 3: Create Widget Component
Create MyWidget.qml:
import QtQuick
import qs.Services
Rectangle {
id: root
property bool compactMode: false
property string section: "center"
property real widgetHeight: 30
property string displayText: "Hello World"
width: textItem.implicitWidth + 16
height: widgetHeight
radius: 8
color: "#20FFFFFF"
Component.onCompleted: {
displayText = PluginService.loadPluginData("myPlugin", "text", "Hello World")
}
Text {
id: textItem
anchors.centerIn: parent
text: root.displayText
color: "#FFFFFF"
font.pixelSize: 13
}
MouseArea {
anchors.fill: parent
onClicked: console.log("Plugin clicked!")
}
}
Step 4: Create Settings Component (Optional)
Create MySettings.qml:
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
Column {
// PluginService is injected by PluginsTab
property var pluginService
spacing: Theme.spacingM
StyledText {
text: "Plugin Settings"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
}
DankTextField {
width: parent.width
label: "Display Text"
text: pluginService ? pluginService.loadPluginData("myPlugin", "text", "Hello World") : ""
onTextChanged: {
if (pluginService) {
pluginService.savePluginData("myPlugin", "text", text)
}
}
}
}
Step 5: Enable Plugin
- Run the shell:
qs -p $CONFIGPATH/quickshell/dms/shell.qml - Open Settings (Ctrl+,)
- Navigate to Plugins tab
- Click "Scan for Plugins"
- Enable your plugin with the toggle switch
- Add the plugin to your DankBar configuration
Adding Plugin to DankBar
After enabling a plugin, add it to the bar:
- Open Settings → Appearance → DankBar Layout
- Add a new widget entry with your plugin ID
- Choose section (left, center, right)
- Save and reload
Or edit $CONFIGPATH/quickshell/dms/config.json:
{
"dankBarLeftWidgets": [
{"widgetId": "myPlugin", "enabled": true}
]
}
Best Practices
- Use Existing Widgets: Leverage
qs.Widgetscomponents (DankIcon, DankToggle, etc.) for consistency - Follow Theme: Use
Themesingleton for colors, spacing, and fonts - Data Persistence: Use PluginService data APIs instead of manual file operations
- Error Handling: Gracefully handle missing dependencies and invalid data
- Performance: Keep widgets lightweight, avoid expensive operations
- Responsive Design: Adapt to
compactModeand different screen sizes - Clean Code: Follow QML code conventions from CLAUDE.md
- Documentation: Include README.md explaining plugin usage
- Versioning: Use semantic versioning for updates
- Dependencies: Document external library requirements
Debugging
Console Logging
View plugin logs:
qs -v -p $CONFIGPATH/quickshell/dms/shell.qml
Look for lines prefixed with:
PluginService:- Service operationsPluginsTab:- UI interactionsPluginsTab:- Settings loading and accordion interface
Common Issues
-
Plugin Not Detected
- Check plugin.json syntax (use
jqor JSON validator) - Verify directory is in
$CONFIGPATH/DankMaterialShell/plugins/ - Click "Scan for Plugins" in Settings
- Check plugin.json syntax (use
-
Widget Not Displaying
- Ensure plugin is enabled in Settings
- Add plugin ID to DankBar widget list
- Check widget width/height properties
-
Settings Not Loading
- Verify
settingspath in plugin.json - Check settings component for errors
- Ensure plugin is enabled and loaded
- Review PluginsTab console output for injection issues
- Verify
-
Data Not Persisting
- Confirm pluginService.savePluginData() calls (with injection)
- Check
$CONFIGPATH/DankMaterialShell/settings.jsonfor pluginSettings data - Verify plugin has settings permissions
- Ensure PluginService was properly injected into settings component
Security Considerations
Plugins run with full QML runtime access. Only install plugins from trusted sources.
Permissions System:
settings_read: Read plugin configurationsettings_write: Write plugin configurationprocess: Execute system commandsnetwork: Network access
Future versions may enforce permission restrictions.
API Stability
The plugin API is currently experimental. Breaking changes may occur in minor version updates. Pin to specific DMS versions for production use.
Roadmap:
- Plugin marketplace/repository
- Sandboxed plugin execution
- Enhanced permission system
- Plugin update notifications
- Inter-plugin communication
Resources
- Example Plugin: https://github.com/rochacbruno/WorldClock
- PluginService:
Services/PluginService.qml - Settings UI:
Modules/Settings/PluginsTab.qml - DankBar Integration:
Modules/DankBar/DankBar.qml - Theme Reference:
Common/Theme.qml - Widget Library:
Widgets/
Contributing
Share your plugins with the community:
- Create a public repository with your plugin
- Include comprehensive README.md
- Add example screenshots
- Document dependencies and permissions
For plugin system improvements, submit issues or PRs to the main DMS repository.