mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-14 00:02:45 -04:00
add dms-plugin-dev agent skill for plugin development (#2394)
This commit is contained in:
@@ -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", "desktop"]
|
||||
},
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
property var popoutService: null
|
||||
|
||||
// TODO: Read configuration from settings
|
||||
property string configValue: pluginData?.configValue || ""
|
||||
|
||||
Connections {
|
||||
target: pluginService
|
||||
function onPluginDataChanged(changedId) {
|
||||
if (changedId !== pluginId) return
|
||||
configValue = pluginService.loadPluginData(pluginId, "configValue", "")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Connect to the service events you need
|
||||
// Connections {
|
||||
// target: SessionData
|
||||
// function onWallpaperPathChanged() {
|
||||
// console.log("[MyDaemon] Wallpaper changed:", SessionData.wallpaperPath)
|
||||
// handleEvent(SessionData.wallpaperPath)
|
||||
// }
|
||||
// }
|
||||
|
||||
function handleEvent(data) {
|
||||
Proc.runCommand(
|
||||
"myDaemon.handle",
|
||||
["echo", "Event received:", data],
|
||||
(stdout, exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
console.log("[MyDaemon] Output:", stdout)
|
||||
} else {
|
||||
console.error("[MyDaemon] Failed:", exitCode)
|
||||
ToastService?.showInfo("Daemon action failed")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("[MyDaemon] Started")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
pluginId: "myDaemon"
|
||||
|
||||
StringSetting {
|
||||
settingKey: "configValue"
|
||||
label: "Configuration"
|
||||
description: "Value used by the daemon"
|
||||
placeholder: "Enter value..."
|
||||
defaultValue: ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "myDaemon",
|
||||
"name": "My Daemon",
|
||||
"description": "A background service that reacts to events",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"type": "daemon",
|
||||
"capabilities": ["background-service"],
|
||||
"component": "./Daemon.qml",
|
||||
"icon": "settings",
|
||||
"settings": "./Settings.qml",
|
||||
"permissions": ["settings_read", "settings_write", "process"]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
pluginId: "myDesktopWidget"
|
||||
|
||||
SliderSetting {
|
||||
settingKey: "opacity"
|
||||
label: "Opacity"
|
||||
description: "Widget background opacity"
|
||||
defaultValue: 85
|
||||
minimum: 10
|
||||
maximum: 100
|
||||
unit: "%"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
property string pluginId: ""
|
||||
property bool editMode: false
|
||||
property real widgetWidth: 200
|
||||
property real widgetHeight: 200
|
||||
property real minWidth: 150
|
||||
property real minHeight: 150
|
||||
|
||||
// TODO: Load settings reactively
|
||||
property real bgOpacity: {
|
||||
if (!pluginService) return 0.85
|
||||
var val = pluginService.loadPluginData(pluginId, "opacity", 85)
|
||||
return val / 100
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: pluginService
|
||||
function onPluginDataChanged(changedId) {
|
||||
if (changedId !== pluginId) return
|
||||
var val = pluginService.loadPluginData(pluginId, "opacity", 85)
|
||||
bgOpacity = val / 100
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
opacity: root.bgOpacity
|
||||
border.color: root.editMode ? Theme.primary : "transparent"
|
||||
border.width: root.editMode ? 2 : 0
|
||||
|
||||
// TODO: Add your widget content here
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Desktop Widget"
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "myDesktopWidget",
|
||||
"name": "My Desktop Widget",
|
||||
"description": "A custom desktop widget",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"type": "desktop",
|
||||
"capabilities": ["desktop-widget"],
|
||||
"component": "./Widget.qml",
|
||||
"icon": "widgets",
|
||||
"settings": "./Settings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
property string trigger: "#"
|
||||
|
||||
signal itemsChanged()
|
||||
|
||||
// TODO: Define your items
|
||||
property var allItems: [
|
||||
{
|
||||
name: "Example Item",
|
||||
icon: "material:star",
|
||||
comment: "An example launcher item",
|
||||
action: "toast:Hello from my launcher!",
|
||||
categories: ["MyLauncher"]
|
||||
}
|
||||
]
|
||||
|
||||
function getItems(query) {
|
||||
if (!query || query.length === 0) return allItems
|
||||
|
||||
var q = query.toLowerCase()
|
||||
return allItems.filter(function(item) {
|
||||
return item.name.toLowerCase().includes(q) ||
|
||||
item.comment.toLowerCase().includes(q)
|
||||
})
|
||||
}
|
||||
|
||||
function executeItem(item) {
|
||||
var actionParts = item.action.split(":")
|
||||
var actionType = actionParts[0]
|
||||
var actionData = actionParts.slice(1).join(":")
|
||||
|
||||
switch (actionType) {
|
||||
case "toast":
|
||||
if (typeof ToastService !== "undefined")
|
||||
ToastService.showInfo(actionData)
|
||||
break
|
||||
case "copy":
|
||||
Quickshell.execDetached(["dms", "cl", "copy", actionData])
|
||||
if (typeof ToastService !== "undefined")
|
||||
ToastService.showInfo("Copied to clipboard")
|
||||
break
|
||||
default:
|
||||
console.warn("Unknown action type:", actionType)
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (pluginService) {
|
||||
trigger = pluginService.loadPluginData("myLauncher", "trigger", "#")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
pluginId: "myLauncher"
|
||||
|
||||
StringSetting {
|
||||
settingKey: "trigger"
|
||||
label: "Trigger"
|
||||
description: "Type this prefix in the launcher to activate the plugin"
|
||||
placeholder: "#"
|
||||
defaultValue: "#"
|
||||
}
|
||||
|
||||
ToggleSetting {
|
||||
settingKey: "noTrigger"
|
||||
label: "Always Visible"
|
||||
description: "Show items alongside regular apps without needing a trigger"
|
||||
defaultValue: false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "myLauncher",
|
||||
"name": "My Launcher",
|
||||
"description": "Custom launcher plugin with searchable items",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"type": "launcher",
|
||||
"capabilities": ["launcher"],
|
||||
"component": "./Launcher.qml",
|
||||
"trigger": "#",
|
||||
"icon": "search",
|
||||
"settings": "./Settings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
pluginId: "myWidget"
|
||||
|
||||
StringSetting {
|
||||
settingKey: "text"
|
||||
label: "Display Text"
|
||||
description: "Text shown in the bar widget"
|
||||
placeholder: "Hello"
|
||||
defaultValue: "Hello"
|
||||
}
|
||||
|
||||
ToggleSetting {
|
||||
settingKey: "showIcon"
|
||||
label: "Show Icon"
|
||||
description: "Display an icon next to the text"
|
||||
defaultValue: true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
property var popoutService: null
|
||||
|
||||
// TODO: Read settings reactively
|
||||
property string displayText: pluginData?.text || "Hello"
|
||||
|
||||
Connections {
|
||||
target: pluginService
|
||||
function onPluginDataChanged(changedId) {
|
||||
if (changedId !== pluginId) return
|
||||
displayText = pluginService.loadPluginData(pluginId, "text", "Hello")
|
||||
}
|
||||
}
|
||||
|
||||
horizontalBarPill: Component {
|
||||
StyledRect {
|
||||
width: label.implicitWidth + Theme.spacingM * 2
|
||||
height: parent.widgetThickness
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: root.displayText
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verticalBarPill: Component {
|
||||
StyledRect {
|
||||
width: parent.widgetThickness
|
||||
height: label.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: root.displayText
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
rotation: 90
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Uncomment and customize popout content
|
||||
// popoutWidth: 350
|
||||
// popoutHeight: 300
|
||||
// popoutContent: Component {
|
||||
// PopoutComponent {
|
||||
// headerText: "My Widget"
|
||||
// showCloseButton: true
|
||||
//
|
||||
// Column {
|
||||
// width: parent.width
|
||||
// spacing: Theme.spacingM
|
||||
//
|
||||
// StyledText {
|
||||
// text: "Popout content here"
|
||||
// color: Theme.surfaceText
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "myWidget",
|
||||
"name": "My Widget",
|
||||
"description": "A custom bar widget",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"type": "widget",
|
||||
"capabilities": ["dankbar-widget"],
|
||||
"component": "./Widget.qml",
|
||||
"icon": "extension",
|
||||
"settings": "./Settings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
Reference in New Issue
Block a user