mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-06 05:42:07 -04:00
457 lines
12 KiB
Markdown
457 lines
12 KiB
Markdown
# 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
|
|
|
|
1. **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
|
|
|
|
2. **PluginsTab** (`Modules/Settings/PluginsTab.qml`)
|
|
- UI for managing available plugins
|
|
- Access plugin settings
|
|
|
|
3. **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
|
|
|
|
4. **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:
|
|
|
|
```json
|
|
{
|
|
"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 name
|
|
- `component`: Relative path to widget QML file
|
|
|
|
**Optional Fields:**
|
|
- `description`: Short description of plugin functionality
|
|
- `version`: Semantic version string
|
|
- `author`: Plugin creator name
|
|
- `icon`: Material Design icon name
|
|
- `settings`: Path to settings component
|
|
- `dependencies`: External JS libraries
|
|
- `settings_schema`: Configuration schema
|
|
- `permissions`: Required capabilities
|
|
|
|
### Widget Component
|
|
|
|
The main widget component is displayed in the DankBar. It receives several properties from the shell:
|
|
|
|
```qml
|
|
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 mode
|
|
- `section`: Which bar section the widget is in
|
|
- `barHeight`: Height of the entire bar
|
|
- `widgetHeight`: Recommended widget height
|
|
- `parentScreen`: Reference to the screen object
|
|
|
|
### Settings Component
|
|
|
|
Optional settings UI loaded inline in the PluginsTab accordion interface:
|
|
|
|
```qml
|
|
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
|
|
|
|
```qml
|
|
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
|
|
|
|
```qml
|
|
// 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
|
|
|
|
```qml
|
|
PluginService.pluginLoaded(pluginId: string)
|
|
PluginService.pluginUnloaded(pluginId: string)
|
|
PluginService.pluginLoadFailed(pluginId: string, error: string)
|
|
```
|
|
|
|
## Creating a Plugin
|
|
|
|
### Step 1: Create Plugin Directory
|
|
|
|
```bash
|
|
mkdir -p $CONFIGPATH/DankMaterialShell/plugins/MyPlugin
|
|
cd $CONFIGPATH/DankMaterialShell/plugins/MyPlugin
|
|
```
|
|
|
|
### Step 2: Create Manifest
|
|
|
|
Create `plugin.json`:
|
|
|
|
```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`:
|
|
|
|
```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`:
|
|
|
|
```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
|
|
|
|
1. Run the shell: `qs -p $CONFIGPATH/quickshell/dms/shell.qml`
|
|
2. Open Settings (Ctrl+,)
|
|
3. Navigate to Plugins tab
|
|
4. Click "Scan for Plugins"
|
|
5. Enable your plugin with the toggle switch
|
|
6. Add the plugin to your DankBar configuration
|
|
|
|
## Adding Plugin to DankBar
|
|
|
|
After enabling a plugin, add it to the bar:
|
|
|
|
1. Open Settings → Appearance → DankBar Layout
|
|
2. Add a new widget entry with your plugin ID
|
|
3. Choose section (left, center, right)
|
|
4. Save and reload
|
|
|
|
Or edit `$CONFIGPATH/quickshell/dms/config.json`:
|
|
|
|
```json
|
|
{
|
|
"dankBarLeftWidgets": [
|
|
{"widgetId": "myPlugin", "enabled": true}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use Existing Widgets**: Leverage `qs.Widgets` components (DankIcon, DankToggle, etc.) for consistency
|
|
2. **Follow Theme**: Use `Theme` singleton for colors, spacing, and fonts
|
|
3. **Data Persistence**: Use PluginService data APIs instead of manual file operations
|
|
4. **Error Handling**: Gracefully handle missing dependencies and invalid data
|
|
5. **Performance**: Keep widgets lightweight, avoid expensive operations
|
|
6. **Responsive Design**: Adapt to `compactMode` and different screen sizes
|
|
7. **Clean Code**: Follow QML code conventions from CLAUDE.md
|
|
8. **Documentation**: Include README.md explaining plugin usage
|
|
9. **Versioning**: Use semantic versioning for updates
|
|
10. **Dependencies**: Document external library requirements
|
|
|
|
## Debugging
|
|
|
|
### Console Logging
|
|
|
|
View plugin logs:
|
|
|
|
```bash
|
|
qs -v -p $CONFIGPATH/quickshell/dms/shell.qml
|
|
```
|
|
|
|
Look for lines prefixed with:
|
|
- `PluginService:` - Service operations
|
|
- `PluginsTab:` - UI interactions
|
|
- `PluginsTab:` - Settings loading and accordion interface
|
|
|
|
### Common Issues
|
|
|
|
1. **Plugin Not Detected**
|
|
- Check plugin.json syntax (use `jq` or JSON validator)
|
|
- Verify directory is in `$CONFIGPATH/DankMaterialShell/plugins/`
|
|
- Click "Scan for Plugins" in Settings
|
|
|
|
2. **Widget Not Displaying**
|
|
- Ensure plugin is enabled in Settings
|
|
- Add plugin ID to DankBar widget list
|
|
- Check widget width/height properties
|
|
|
|
3. **Settings Not Loading**
|
|
- Verify `settings` path in plugin.json
|
|
- Check settings component for errors
|
|
- Ensure plugin is enabled and loaded
|
|
- Review PluginsTab console output for injection issues
|
|
|
|
4. **Data Not Persisting**
|
|
- Confirm pluginService.savePluginData() calls (with injection)
|
|
- Check `$CONFIGPATH/DankMaterialShell/settings.json` for 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 configuration
|
|
- `settings_write`: Write plugin configuration
|
|
- `process`: Execute system commands
|
|
- `network`: 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:
|
|
|
|
1. Create a public repository with your plugin
|
|
2. Include comprehensive README.md
|
|
4. Add example screenshots
|
|
5. Document dependencies and permissions
|
|
|
|
For plugin system improvements, submit issues or PRs to the main DMS repository.
|