mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-08 04:09:15 -04:00
plugins: add support for composite plugins
- single plugin can register multiple types - e.g. daemon, bar widget, desktop widget
This commit is contained in:
@@ -0,0 +1,137 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
|
PluginComponent {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
layerNamespacePlugin: "composite-example"
|
||||||
|
|
||||||
|
property var enabledEmojis: pluginData.emojis || ["😊", "😢", "❤️"]
|
||||||
|
property int cycleInterval: pluginData.cycleInterval || 3000
|
||||||
|
property int maxBarEmojis: pluginData.maxBarEmojis || 3
|
||||||
|
|
||||||
|
property int currentIndex: 0
|
||||||
|
property var displayedEmojis: []
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: root.cycleInterval
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
if (root.enabledEmojis.length > 0) {
|
||||||
|
root.currentIndex = (root.currentIndex + 1) % root.enabledEmojis.length;
|
||||||
|
root.updateDisplayedEmojis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDisplayedEmojis() {
|
||||||
|
const maxToShow = Math.min(root.maxBarEmojis, root.enabledEmojis.length);
|
||||||
|
let emojis = [];
|
||||||
|
for (let i = 0; i < maxToShow; i++) {
|
||||||
|
const idx = (root.currentIndex + i) % root.enabledEmojis.length;
|
||||||
|
emojis.push(root.enabledEmojis[idx]);
|
||||||
|
}
|
||||||
|
root.displayedEmojis = emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
updateDisplayedEmojis();
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnabledEmojisChanged: updateDisplayedEmojis()
|
||||||
|
onMaxBarEmojisChanged: updateDisplayedEmojis()
|
||||||
|
|
||||||
|
horizontalBarPill: Component {
|
||||||
|
Row {
|
||||||
|
id: emojiRow
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.displayedEmojis
|
||||||
|
StyledText {
|
||||||
|
text: modelData
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verticalBarPill: Component {
|
||||||
|
Column {
|
||||||
|
id: emojiColumn
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.displayedEmojis
|
||||||
|
StyledText {
|
||||||
|
text: modelData
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popoutContent: Component {
|
||||||
|
PopoutComponent {
|
||||||
|
id: popoutColumn
|
||||||
|
|
||||||
|
headerText: "Emoji Picker"
|
||||||
|
detailsText: "Click an emoji to copy it to clipboard"
|
||||||
|
showCloseButton: true
|
||||||
|
|
||||||
|
property var allEmojis: ["😀", "😃", "😄", "😁", "😆", "😅", "🤣", "😂", "🙂", "🙃", "😉", "😊", "😇", "🥰", "😍", "🤩", "😘", "😗", "😚", "😙", "😋", "😛", "😜", "🤪", "😝", "🤑", "🤗", "🤭", "🤫", "🤔", "🤐", "🤨", "😐", "😑", "😶", "😏", "😒", "🙄", "😬", "🤥", "😌", "😔", "😪", "🤤", "😴", "😷", "🤒", "🤕", "🤢", "🤮", "❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍", "🤎", "💔", "👍", "👎", "👊", "✊", "🤛", "🤜", "🤞", "✌️", "🤟", "🤘"]
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: root.popoutHeight - popoutColumn.headerHeight - popoutColumn.detailsHeight - Theme.spacingXL
|
||||||
|
|
||||||
|
DankGridView {
|
||||||
|
id: emojiGrid
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
width: Math.floor(parent.width / 50) * 50
|
||||||
|
height: parent.height
|
||||||
|
clip: true
|
||||||
|
cellWidth: 50
|
||||||
|
cellHeight: 50
|
||||||
|
model: popoutColumn.allEmojis
|
||||||
|
|
||||||
|
delegate: StyledRect {
|
||||||
|
width: 45
|
||||||
|
height: 45
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: emojiMouseArea.containsMouse ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: emojiMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
Quickshell.execDetached(["dms", "cl", "copy", modelData]);
|
||||||
|
ToastService.showInfo("Copied " + modelData + " to clipboard");
|
||||||
|
popoutColumn.closePopout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popoutWidth: 400
|
||||||
|
popoutHeight: 500
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
|
PluginComponent {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string scriptPath: pluginData.scriptPath || ""
|
||||||
|
property var popoutService: null
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionData
|
||||||
|
function onWallpaperPathChanged() {
|
||||||
|
if (scriptPath) {
|
||||||
|
var scriptProcess = scriptProcessComponent.createObject(root, {
|
||||||
|
wallpaperPath: SessionData.wallpaperPath
|
||||||
|
});
|
||||||
|
scriptProcess.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: scriptProcessComponent
|
||||||
|
|
||||||
|
Process {
|
||||||
|
property string wallpaperPath: ""
|
||||||
|
|
||||||
|
command: [scriptPath, wallpaperPath]
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
if (text.trim()) {
|
||||||
|
console.log("CompositeDaemon script output:", text.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
if (text.trim()) {
|
||||||
|
ToastService.showError("Wallpaper Change Script Error", text.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
ToastService.showError("Wallpaper Change Script Error", "Script exited with code: " + exitCode);
|
||||||
|
}
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "compositeExample"
|
||||||
|
|
||||||
|
function runHook(): string {
|
||||||
|
if (!root.scriptPath)
|
||||||
|
return "no script configured";
|
||||||
|
var scriptProcess = scriptProcessComponent.createObject(root, {
|
||||||
|
wallpaperPath: SessionData.wallpaperPath
|
||||||
|
});
|
||||||
|
scriptProcess.running = true;
|
||||||
|
return "ran hook";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
console.info("CompositeDaemon: Started monitoring wallpaper changes");
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: {
|
||||||
|
console.info("CompositeDaemon: Stopped monitoring wallpaper changes");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
|
DesktopPluginComponent {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
minWidth: 120
|
||||||
|
minHeight: 120
|
||||||
|
|
||||||
|
property bool showSeconds: pluginData.showSeconds ?? true
|
||||||
|
property bool showDate: pluginData.showDate ?? true
|
||||||
|
property string clockStyle: pluginData.clockStyle ?? "analog"
|
||||||
|
property real backgroundOpacity: (pluginData.backgroundOpacity ?? 50) / 100
|
||||||
|
|
||||||
|
SystemClock {
|
||||||
|
id: systemClock
|
||||||
|
precision: root.showSeconds ? SystemClock.Seconds : SystemClock.Minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
opacity: root.backgroundOpacity
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
sourceComponent: root.clockStyle === "digital" ? digitalClock : analogClock
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: analogClock
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: analogClockRoot
|
||||||
|
|
||||||
|
property real clockSize: Math.min(width, height) - (root.showDate ? 30 : 0)
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: clockFace
|
||||||
|
width: analogClockRoot.clockSize
|
||||||
|
height: analogClockRoot.clockSize
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 12
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
property real markAngle: index * 30
|
||||||
|
property real markRadius: clockFace.width / 2 - 8
|
||||||
|
|
||||||
|
x: clockFace.width / 2 + markRadius * Math.sin(markAngle * Math.PI / 180) - width / 2
|
||||||
|
y: clockFace.height / 2 - markRadius * Math.cos(markAngle * Math.PI / 180) - height / 2
|
||||||
|
width: index % 3 === 0 ? 8 : 4
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: index % 3 === 0 ? Theme.primary : Theme.outlineVariant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: hourHand
|
||||||
|
property int hours: systemClock.date?.getHours() % 12 ?? 0
|
||||||
|
property int minutes: systemClock.date?.getMinutes() ?? 0
|
||||||
|
|
||||||
|
x: clockFace.width / 2 - width / 2
|
||||||
|
y: clockFace.height / 2 - height + 4
|
||||||
|
width: 6
|
||||||
|
height: clockFace.height * 0.25
|
||||||
|
radius: 3
|
||||||
|
color: Theme.primary
|
||||||
|
antialiasing: true
|
||||||
|
transformOrigin: Item.Bottom
|
||||||
|
rotation: (hours + minutes / 60) * 30
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: minuteHand
|
||||||
|
property int minutes: systemClock.date?.getMinutes() ?? 0
|
||||||
|
property int seconds: systemClock.date?.getSeconds() ?? 0
|
||||||
|
|
||||||
|
x: clockFace.width / 2 - width / 2
|
||||||
|
y: clockFace.height / 2 - height + 4
|
||||||
|
width: 4
|
||||||
|
height: clockFace.height * 0.35
|
||||||
|
radius: 2
|
||||||
|
color: Theme.onSurface
|
||||||
|
antialiasing: true
|
||||||
|
transformOrigin: Item.Bottom
|
||||||
|
rotation: (minutes + seconds / 60) * 6
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: secondHand
|
||||||
|
visible: root.showSeconds
|
||||||
|
property int seconds: systemClock.date?.getSeconds() ?? 0
|
||||||
|
|
||||||
|
x: clockFace.width / 2 - width / 2
|
||||||
|
y: clockFace.height / 2 - height + 4
|
||||||
|
width: 2
|
||||||
|
height: clockFace.height * 0.4
|
||||||
|
radius: 1
|
||||||
|
color: Theme.error
|
||||||
|
antialiasing: true
|
||||||
|
transformOrigin: Item.Bottom
|
||||||
|
rotation: seconds * 6
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 10
|
||||||
|
height: 10
|
||||||
|
radius: 5
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: root.showDate
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: Theme.spacingXS
|
||||||
|
text: systemClock.date?.toLocaleDateString(I18n.locale(), "ddd, MMM d") ?? ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: digitalClock
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: digitalRoot
|
||||||
|
|
||||||
|
property real timeFontSize: Math.min(width * 0.16, height * (root.showDate ? 0.4 : 0.5))
|
||||||
|
property real dateFontSize: Math.max(Theme.fontSizeSmall, timeFontSize * 0.35)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: timeText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.verticalCenterOffset: root.showDate ? -digitalRoot.dateFontSize * 0.8 : 0
|
||||||
|
text: systemClock.date?.toLocaleTimeString(Qt.locale(), root.showSeconds ? "hh:mm:ss" : "hh:mm") ?? ""
|
||||||
|
font.pixelSize: digitalRoot.timeFontSize
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.family: "monospace"
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: dateText
|
||||||
|
visible: root.showDate
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: timeText.bottom
|
||||||
|
anchors.topMargin: Theme.spacingXS
|
||||||
|
text: systemClock.date?.toLocaleDateString(I18n.locale(), "ddd, MMM d") ?? ""
|
||||||
|
font.pixelSize: digitalRoot.dateFontSize
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
|
PluginSettings {
|
||||||
|
id: root
|
||||||
|
pluginId: "exampleComposite"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: "Bar Widget — Emoji Cycler"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionSetting {
|
||||||
|
settingKey: "emojiSet"
|
||||||
|
label: "Emoji Set"
|
||||||
|
description: "Choose which collection of emojis to cycle through"
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Happy & Sad",
|
||||||
|
value: "happySad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Hearts",
|
||||||
|
value: "hearts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Hand Gestures",
|
||||||
|
value: "hands"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "All Mixed",
|
||||||
|
value: "mixed"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
defaultValue: "happySad"
|
||||||
|
|
||||||
|
onValueChanged: {
|
||||||
|
const sets = {
|
||||||
|
"happySad": ["😊", "😢", "😂", "😭", "😍", "😡"],
|
||||||
|
"hearts": ["❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍"],
|
||||||
|
"hands": ["👍", "👎", "👊", "✌️", "🤘", "👌", "✋", "🤚"],
|
||||||
|
"mixed": ["😊", "❤️", "👍", "🎉", "🔥", "✨", "🌟", "💯"]
|
||||||
|
};
|
||||||
|
root.saveValue("emojis", sets[value] || sets["happySad"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
const currentSet = value || defaultValue;
|
||||||
|
const sets = {
|
||||||
|
"happySad": ["😊", "😢", "😂", "😭", "😍", "😡"],
|
||||||
|
"hearts": ["❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍"],
|
||||||
|
"hands": ["👍", "👎", "👊", "✌️", "🤘", "👌", "✋", "🤚"],
|
||||||
|
"mixed": ["😊", "❤️", "👍", "🎉", "🔥", "✨", "🌟", "💯"]
|
||||||
|
};
|
||||||
|
root.saveValue("emojis", sets[currentSet] || sets["happySad"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SliderSetting {
|
||||||
|
settingKey: "cycleInterval"
|
||||||
|
label: "Cycle Speed"
|
||||||
|
description: "How quickly emojis rotate"
|
||||||
|
defaultValue: 3000
|
||||||
|
minimum: 500
|
||||||
|
maximum: 10000
|
||||||
|
unit: "ms"
|
||||||
|
leftIcon: "schedule"
|
||||||
|
}
|
||||||
|
|
||||||
|
SliderSetting {
|
||||||
|
settingKey: "maxBarEmojis"
|
||||||
|
label: "Max Bar Emojis"
|
||||||
|
description: "Maximum number of emojis to display in the bar at once"
|
||||||
|
defaultValue: 3
|
||||||
|
minimum: 1
|
||||||
|
maximum: 8
|
||||||
|
rightIcon: "emoji_emotions"
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: "Desktop Widget — Clock"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionSetting {
|
||||||
|
settingKey: "clockStyle"
|
||||||
|
label: "Clock Style"
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Analog",
|
||||||
|
value: "analog"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Digital",
|
||||||
|
value: "digital"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
defaultValue: "analog"
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleSetting {
|
||||||
|
settingKey: "showSeconds"
|
||||||
|
label: "Show Seconds"
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleSetting {
|
||||||
|
settingKey: "showDate"
|
||||||
|
label: "Show Date"
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
|
|
||||||
|
SliderSetting {
|
||||||
|
settingKey: "backgroundOpacity"
|
||||||
|
label: "Background Opacity"
|
||||||
|
defaultValue: 50
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
unit: "%"
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: "Daemon — Wallpaper Hook"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSetting {
|
||||||
|
settingKey: "scriptPath"
|
||||||
|
label: "Script Path"
|
||||||
|
description: "Script executed when the wallpaper changes. The new wallpaper path is passed as the first argument."
|
||||||
|
placeholder: "/path/to/your/script.sh"
|
||||||
|
defaultValue: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Composite Example
|
||||||
|
|
||||||
|
A single plugin that provides **all three surfaces at once** by combining three of
|
||||||
|
the standalone example plugins:
|
||||||
|
|
||||||
|
| Surface | Source example | File |
|
||||||
|
|---------|----------------|------|
|
||||||
|
| `daemon` | WallpaperWatcherDaemon | `CompositeDaemon.qml` |
|
||||||
|
| `widget` | Emoji Cycler (bar widget + popout) | `CompositeBarWidget.qml` |
|
||||||
|
| `desktop` | Desktop Clock | `CompositeDesktopWidget.qml` |
|
||||||
|
|
||||||
|
It demonstrates the `components` manifest map, where each surface points at its own
|
||||||
|
QML file:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"type": "composite",
|
||||||
|
"components": {
|
||||||
|
"daemon": "./CompositeDaemon.qml",
|
||||||
|
"widget": "./CompositeBarWidget.qml",
|
||||||
|
"desktop": "./CompositeDesktopWidget.qml"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All surfaces share one settings UI (`CompositeSettings.qml`) and one plugin-settings
|
||||||
|
namespace (`exampleComposite`), so `pluginData` is the same for every surface.
|
||||||
|
|
||||||
|
## Surfaces
|
||||||
|
|
||||||
|
- **Daemon** — watches `SessionData.wallpaperPath` and runs a user-configured script
|
||||||
|
on change. Also registers an `IpcHandler` (`target: "compositeExample"`) exposing a
|
||||||
|
`runHook` call, so you can trigger the hook over IPC.
|
||||||
|
- **Bar widget** — cycles emojis in the bar; click the pill for an emoji picker popout
|
||||||
|
that copies to the clipboard.
|
||||||
|
- **Desktop widget** — an analog/digital clock you can drag and resize on the desktop.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Copy this directory into `$CONFIGPATH/DankMaterialShell/plugins/`.
|
||||||
|
2. Settings → Plugins → **Scan for Plugins**, then enable **Composite Example**.
|
||||||
|
(Composite plugins respect the enable toggle — unlike a pure `desktop` plugin they
|
||||||
|
do not auto-load, because they also carry a daemon.)
|
||||||
|
3. Add the bar widget via Settings → Appearance → DankBar Layout.
|
||||||
|
4. Place the desktop clock via Settings → Desktop Widgets.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The daemon surface is instantiated once and lives for as long as the plugin is
|
||||||
|
enabled. The bar and desktop surfaces are instantiated per bar/placement per screen.
|
||||||
|
- Cross-surface runtime state (not needed here) is best shared via
|
||||||
|
`PluginService.getGlobalVar` / `setGlobalVar` or the daemon instance, since each
|
||||||
|
surface is a separate object.
|
||||||
|
- `requires_dms` is `>=1.5.0` because the `components` multi-surface manifest is only
|
||||||
|
understood by DMS 1.5.0 and later.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"id": "exampleComposite",
|
||||||
|
"name": "Composite Example",
|
||||||
|
"description": "One plugin providing all three surfaces at once: a wallpaper-watcher daemon, an emoji bar widget with popout, and a desktop clock",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "DankMaterialShell",
|
||||||
|
"type": "composite",
|
||||||
|
"capabilities": ["daemon", "dankbar-widget", "desktop-widget", "clipboard"],
|
||||||
|
"icon": "extension",
|
||||||
|
"components": {
|
||||||
|
"daemon": "./CompositeDaemon.qml",
|
||||||
|
"widget": "./CompositeBarWidget.qml",
|
||||||
|
"desktop": "./CompositeDesktopWidget.qml"
|
||||||
|
},
|
||||||
|
"settings": "./CompositeSettings.qml",
|
||||||
|
"requires_dms": ">=1.5.0",
|
||||||
|
"permissions": [
|
||||||
|
"settings_read",
|
||||||
|
"settings_write"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1635,6 +1635,79 @@ See `PLUGINS/ExampleDesktopClock/` for a complete working example demonstrating:
|
|||||||
- Responsive sizing
|
- Responsive sizing
|
||||||
- Edit mode handling
|
- Edit mode handling
|
||||||
|
|
||||||
|
## Composite Plugins
|
||||||
|
|
||||||
|
A single plugin can provide **multiple surfaces at once** — for example a background
|
||||||
|
daemon (for IPC / monitoring), a bar widget, and a desktop widget. Because each surface
|
||||||
|
has a different lifecycle (the daemon is instantiated once; bar and desktop widgets are
|
||||||
|
instantiated per bar/placement per screen), each surface is its own QML file.
|
||||||
|
|
||||||
|
### Plugin Type Configuration
|
||||||
|
|
||||||
|
Instead of a single `type` + `component`, declare a `components` map. Set `type` to
|
||||||
|
`composite` (any value works; `composite` is conventional):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "myComposite",
|
||||||
|
"name": "My Composite Plugin",
|
||||||
|
"description": "A daemon plus a bar widget plus a desktop widget",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Your Name",
|
||||||
|
"type": "composite",
|
||||||
|
"capabilities": ["daemon", "dankbar-widget", "desktop-widget"],
|
||||||
|
"components": {
|
||||||
|
"daemon": "./MyDaemon.qml",
|
||||||
|
"widget": "./MyBarWidget.qml",
|
||||||
|
"desktop": "./MyDesktopWidget.qml",
|
||||||
|
"launcher": "./MyLauncher.qml"
|
||||||
|
},
|
||||||
|
"trigger": "#",
|
||||||
|
"settings": "./MySettings.qml",
|
||||||
|
"requires_dms": ">=1.5.0",
|
||||||
|
"permissions": ["settings_read", "settings_write"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Surfaces
|
||||||
|
|
||||||
|
Provide any subset of these keys in `components`:
|
||||||
|
|
||||||
|
| Surface | Component contract | Notes |
|
||||||
|
|---------|--------------------|-------|
|
||||||
|
| `widget` | `PluginComponent` (bar pills + optional Control Center widget) | see [Widget Component](#widget-component) |
|
||||||
|
| `desktop` | `DesktopPluginComponent` (or an `Item` following the desktop contract) | see [Desktop Plugins](#desktop-plugins) |
|
||||||
|
| `daemon` | any `Item` exposing `pluginService` / `pluginId` | instantiated once; ideal for IPC handlers and background monitoring |
|
||||||
|
| `launcher` | launcher contract (`getItems` / `executeItem`) | requires `trigger` (or empty-trigger mode); see [Launcher Plugins](#launcher-plugins) |
|
||||||
|
|
||||||
|
Each surface is loaded independently into its own registry, so the same plugin can show
|
||||||
|
up in the bar **and** on the desktop **and** run a daemon simultaneously.
|
||||||
|
|
||||||
|
### Shared State
|
||||||
|
|
||||||
|
Each surface is a separate object, so share runtime state through:
|
||||||
|
|
||||||
|
- `PluginService.getGlobalVar(pluginId, name, default)` / `setGlobalVar(...)` — reactive,
|
||||||
|
in-process, namespaced per plugin (see [Plugin Global Variables](#plugin-global-variables)).
|
||||||
|
- The daemon instance — register `IpcHandler`s or expose data other surfaces read via
|
||||||
|
global vars.
|
||||||
|
- `savePluginData` / `loadPluginData` for persisted settings (all surfaces of a plugin
|
||||||
|
share one settings namespace, so one `settings` component configures them all).
|
||||||
|
|
||||||
|
### Settings, Enabling, and Backwards Compatibility
|
||||||
|
|
||||||
|
- Declare a single top-level `settings` component; it configures every surface.
|
||||||
|
- Composite plugins respect the **enable toggle** in Settings → Plugins (they are not
|
||||||
|
auto-loaded). A pure `desktop` plugin still auto-loads for backwards compatibility.
|
||||||
|
- The legacy single `type` + `component` form is unchanged and fully supported — it is
|
||||||
|
treated internally as a one-entry `components` map.
|
||||||
|
|
||||||
|
### Example Plugin
|
||||||
|
|
||||||
|
See `PLUGINS/ExampleCompositePlugin/` for a working composite that combines the
|
||||||
|
WallpaperWatcher daemon, the Emoji Cycler bar widget, and the Desktop Clock into one
|
||||||
|
plugin.
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- **Plugin Schema**: `plugin-schema.json` - JSON Schema for validation
|
- **Plugin Schema**: `plugin-schema.json` - JSON Schema for validation
|
||||||
@@ -1644,6 +1717,7 @@ See `PLUGINS/ExampleDesktopClock/` for a complete working example demonstrating:
|
|||||||
- [LauncherExample](./LauncherExample/)
|
- [LauncherExample](./LauncherExample/)
|
||||||
- [Calculator](https://github.com/rochacbruno/DankCalculator)
|
- [Calculator](https://github.com/rochacbruno/DankCalculator)
|
||||||
- [Desktop Clock](./ExampleDesktopClock/)
|
- [Desktop Clock](./ExampleDesktopClock/)
|
||||||
|
- [Composite Example](./ExampleCompositePlugin/)
|
||||||
- **PluginService**: `Services/PluginService.qml`
|
- **PluginService**: `Services/PluginService.qml`
|
||||||
- **Settings UI**: `Modules/Settings/PluginsTab.qml`
|
- **Settings UI**: `Modules/Settings/PluginsTab.qml`
|
||||||
- **DankBar Integration**: `Modules/DankBar/DankBar.qml`
|
- **DankBar Integration**: `Modules/DankBar/DankBar.qml`
|
||||||
|
|||||||
@@ -11,8 +11,7 @@
|
|||||||
"version",
|
"version",
|
||||||
"author",
|
"author",
|
||||||
"type",
|
"type",
|
||||||
"capabilities",
|
"capabilities"
|
||||||
"component"
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -42,8 +41,8 @@
|
|||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Plugin type",
|
"description": "Plugin type. Use 'composite' (or any value) together with 'components' to provide multiple surfaces from one plugin.",
|
||||||
"enum": ["widget", "daemon", "launcher", "desktop"]
|
"enum": ["widget", "daemon", "launcher", "desktop", "composite"]
|
||||||
},
|
},
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -55,9 +54,37 @@
|
|||||||
},
|
},
|
||||||
"component": {
|
"component": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Relative path to main QML component file",
|
"description": "Relative path to main QML component file. Required unless 'components' is provided.",
|
||||||
"pattern": "^\\./.*\\.qml$"
|
"pattern": "^\\./.*\\.qml$"
|
||||||
},
|
},
|
||||||
|
"components": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Map of surface name to relative QML component path, for multi-surface (composite) plugins. Provide any subset of surfaces; each is loaded independently.",
|
||||||
|
"properties": {
|
||||||
|
"widget": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Bar/Control Center widget component (PluginComponent)",
|
||||||
|
"pattern": "^\\./.*\\.qml$"
|
||||||
|
},
|
||||||
|
"desktop": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Desktop widget component",
|
||||||
|
"pattern": "^\\./.*\\.qml$"
|
||||||
|
},
|
||||||
|
"daemon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Background daemon component (instantiated once)",
|
||||||
|
"pattern": "^\\./.*\\.qml$"
|
||||||
|
},
|
||||||
|
"launcher": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Launcher provider component (requires 'trigger')",
|
||||||
|
"pattern": "^\\./.*\\.qml$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"minProperties": 1
|
||||||
|
},
|
||||||
"trigger": {
|
"trigger": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Trigger string for launcher activation (required for launcher type)"
|
"description": "Trigger string for launcher activation (required for launcher type)"
|
||||||
@@ -109,6 +136,29 @@
|
|||||||
"then": {
|
"then": {
|
||||||
"required": ["trigger"]
|
"required": ["trigger"]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"required": ["components"],
|
||||||
|
"properties": {
|
||||||
|
"components": {
|
||||||
|
"required": ["launcher"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": ["trigger"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"required": ["component"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": ["components"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ Singleton {
|
|||||||
Connections {
|
Connections {
|
||||||
target: PluginService
|
target: PluginService
|
||||||
function onPluginLoaded(pluginId) {
|
function onPluginLoaded(pluginId) {
|
||||||
const plugin = PluginService.availablePlugins[pluginId];
|
if (PluginService.pluginDesktopComponents[pluginId] !== undefined)
|
||||||
if (plugin?.type === "desktop")
|
|
||||||
syncPluginWidgets();
|
syncPluginWidgets();
|
||||||
}
|
}
|
||||||
function onPluginUnloaded(pluginId) {
|
function onPluginUnloaded(pluginId) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtCore
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Qt.labs.folderlistmodel
|
import Qt.labs.folderlistmodel
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -202,8 +201,51 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property var pluginSurfaceKeys: ["widget", "desktop", "daemon", "launcher"]
|
||||||
|
|
||||||
|
function _stripDotSlash(p) {
|
||||||
|
return p.startsWith("./") ? p.slice(2) : p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _deriveLegacySurface(type, capabilities) {
|
||||||
|
if (type === "daemon")
|
||||||
|
return "daemon";
|
||||||
|
if (type === "launcher" || (capabilities && capabilities.includes("launcher")))
|
||||||
|
return "launcher";
|
||||||
|
if (type === "desktop")
|
||||||
|
return "desktop";
|
||||||
|
return "widget";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _resolveComponentPaths(manifest, dir) {
|
||||||
|
const paths = {};
|
||||||
|
if (manifest.components && typeof manifest.components === "object") {
|
||||||
|
for (const surface in manifest.components) {
|
||||||
|
if (!pluginSurfaceKeys.includes(surface)) {
|
||||||
|
log.warn("unknown plugin surface", surface, "in", dir);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const rel = manifest.components[surface];
|
||||||
|
if (!rel)
|
||||||
|
continue;
|
||||||
|
paths[surface] = dir + "/" + _stripDotSlash(rel);
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
if (manifest.component) {
|
||||||
|
const surface = _deriveLegacySurface(manifest.type, manifest.capabilities);
|
||||||
|
paths[surface] = dir + "/" + _stripDotSlash(manifest.component);
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pluginHasSurface(pluginId, surface) {
|
||||||
|
const plugin = availablePlugins[pluginId];
|
||||||
|
return !!(plugin && plugin.surfaces && plugin.surfaces.includes(surface));
|
||||||
|
}
|
||||||
|
|
||||||
function _onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs) {
|
function _onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs) {
|
||||||
if (!manifest || !manifest.id || !manifest.name || !manifest.component) {
|
if (!manifest || !manifest.id || !manifest.name || (!manifest.component && !manifest.components)) {
|
||||||
log.error("invalid manifest fields:", absPath);
|
log.error("invalid manifest fields:", absPath);
|
||||||
knownManifests[absPath] = {
|
knownManifests[absPath] = {
|
||||||
mtime: mtimeEpochMs,
|
mtime: mtimeEpochMs,
|
||||||
@@ -214,13 +256,22 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dir = absPath.substring(0, absPath.lastIndexOf('/'));
|
const dir = absPath.substring(0, absPath.lastIndexOf('/'));
|
||||||
let comp = manifest.component;
|
|
||||||
if (comp.startsWith("./"))
|
|
||||||
comp = comp.slice(2);
|
|
||||||
let settings = manifest.settings;
|
let settings = manifest.settings;
|
||||||
if (settings && settings.startsWith("./"))
|
if (settings && settings.startsWith("./"))
|
||||||
settings = settings.slice(2);
|
settings = settings.slice(2);
|
||||||
|
|
||||||
|
const componentPaths = _resolveComponentPaths(manifest, dir);
|
||||||
|
const surfaces = Object.keys(componentPaths);
|
||||||
|
if (surfaces.length === 0) {
|
||||||
|
log.error("no valid component surfaces in manifest:", absPath);
|
||||||
|
knownManifests[absPath] = {
|
||||||
|
mtime: mtimeEpochMs,
|
||||||
|
source: sourceTag,
|
||||||
|
bad: true
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const info = {};
|
const info = {};
|
||||||
for (const k in manifest)
|
for (const k in manifest)
|
||||||
info[k] = manifest[k];
|
info[k] = manifest[k];
|
||||||
@@ -236,10 +287,12 @@ Singleton {
|
|||||||
|
|
||||||
info.manifestPath = absPath;
|
info.manifestPath = absPath;
|
||||||
info.pluginDirectory = dir;
|
info.pluginDirectory = dir;
|
||||||
info.componentPath = dir + "/" + comp;
|
info.componentPaths = componentPaths;
|
||||||
|
info.surfaces = surfaces;
|
||||||
|
info.componentPath = componentPaths.widget || componentPaths[surfaces[0]];
|
||||||
info.settingsPath = settings ? (dir + "/" + settings) : null;
|
info.settingsPath = settings ? (dir + "/" + settings) : null;
|
||||||
info.loaded = isPluginLoaded(manifest.id);
|
info.loaded = isPluginLoaded(manifest.id);
|
||||||
info.type = manifest.type || "widget";
|
info.type = manifest.type || (manifest.components ? "composite" : "widget");
|
||||||
info.source = sourceTag;
|
info.source = sourceTag;
|
||||||
info.requires_dms = manifest.requires_dms || null;
|
info.requires_dms = manifest.requires_dms || null;
|
||||||
|
|
||||||
@@ -260,7 +313,8 @@ Singleton {
|
|||||||
};
|
};
|
||||||
_updateAvailablePluginsList();
|
_updateAvailablePluginsList();
|
||||||
pluginListUpdated();
|
pluginListUpdated();
|
||||||
const enabled = info.type === "desktop" || SettingsData.getPluginSetting(manifest.id, "enabled", false);
|
const isPureDesktop = surfaces.length === 1 && surfaces[0] === "desktop";
|
||||||
|
const enabled = isPureDesktop || SettingsData.getPluginSetting(manifest.id, "enabled", false);
|
||||||
if (enabled && !info.loaded)
|
if (enabled && !info.loaded)
|
||||||
loadPlugin(manifest.id);
|
loadPlugin(manifest.id);
|
||||||
} else {
|
} else {
|
||||||
@@ -296,59 +350,70 @@ Singleton {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDaemon = plugin.type === "daemon";
|
const componentPaths = plugin.componentPaths || {};
|
||||||
const isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"));
|
const surfaces = Object.keys(componentPaths);
|
||||||
const isDesktop = plugin.type === "desktop";
|
if (surfaces.length === 0) {
|
||||||
|
log.error("Plugin has no component surfaces:", pluginId);
|
||||||
|
pluginLoadFailed(pluginId, "No component surfaces");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const prevInstance = pluginInstances[pluginId];
|
const newWidgets = Object.assign({}, pluginWidgetComponents);
|
||||||
|
const newDesktop = Object.assign({}, pluginDesktopComponents);
|
||||||
|
const newDaemons = Object.assign({}, pluginDaemonComponents);
|
||||||
|
const newLaunchers = Object.assign({}, pluginLauncherComponents);
|
||||||
|
const newInstances = Object.assign({}, pluginInstances);
|
||||||
|
|
||||||
|
const prevInstance = newInstances[pluginId];
|
||||||
if (prevInstance) {
|
if (prevInstance) {
|
||||||
prevInstance.destroy();
|
prevInstance.destroy();
|
||||||
const newInstances = Object.assign({}, pluginInstances);
|
|
||||||
delete newInstances[pluginId];
|
delete newInstances[pluginId];
|
||||||
pluginInstances = newInstances;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let url = "file://" + plugin.componentPath;
|
for (const surface of surfaces) {
|
||||||
if (bustCache)
|
let url = "file://" + componentPaths[surface];
|
||||||
url += "?t=" + Date.now();
|
if (bustCache)
|
||||||
const comp = Qt.createComponent(url, Component.PreferSynchronous);
|
url += "?t=" + Date.now();
|
||||||
if (comp.status === Component.Error) {
|
const comp = Qt.createComponent(url, Component.PreferSynchronous);
|
||||||
log.error("component error", pluginId, comp.errorString());
|
if (comp.status === Component.Error) {
|
||||||
pluginLoadFailed(pluginId, comp.errorString());
|
log.error("component error", pluginId, surface, comp.errorString());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDaemon) {
|
|
||||||
const newDaemons = Object.assign({}, pluginDaemonComponents);
|
|
||||||
newDaemons[pluginId] = comp;
|
|
||||||
pluginDaemonComponents = newDaemons;
|
|
||||||
} else if (isLauncher) {
|
|
||||||
const instance = comp.createObject(root, {
|
|
||||||
"pluginService": root
|
|
||||||
});
|
|
||||||
if (!instance) {
|
|
||||||
log.error("failed to instantiate plugin:", pluginId, comp.errorString());
|
|
||||||
pluginLoadFailed(pluginId, comp.errorString());
|
pluginLoadFailed(pluginId, comp.errorString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const newInstances = Object.assign({}, pluginInstances);
|
|
||||||
newInstances[pluginId] = instance;
|
|
||||||
pluginInstances = newInstances;
|
|
||||||
|
|
||||||
const newLaunchers = Object.assign({}, pluginLauncherComponents);
|
switch (surface) {
|
||||||
newLaunchers[pluginId] = comp;
|
case "daemon":
|
||||||
pluginLauncherComponents = newLaunchers;
|
newDaemons[pluginId] = comp;
|
||||||
} else if (isDesktop) {
|
break;
|
||||||
const newDesktop = Object.assign({}, pluginDesktopComponents);
|
case "desktop":
|
||||||
newDesktop[pluginId] = comp;
|
newDesktop[pluginId] = comp;
|
||||||
pluginDesktopComponents = newDesktop;
|
break;
|
||||||
} else {
|
case "launcher": {
|
||||||
const newComponents = Object.assign({}, pluginWidgetComponents);
|
const instance = comp.createObject(root, {
|
||||||
newComponents[pluginId] = comp;
|
"pluginService": root
|
||||||
pluginWidgetComponents = newComponents;
|
});
|
||||||
|
if (!instance) {
|
||||||
|
log.error("failed to instantiate launcher surface:", pluginId, comp.errorString());
|
||||||
|
pluginLoadFailed(pluginId, comp.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
newInstances[pluginId] = instance;
|
||||||
|
newLaunchers[pluginId] = comp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
newWidgets[pluginId] = comp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pluginWidgetComponents = newWidgets;
|
||||||
|
pluginDesktopComponents = newDesktop;
|
||||||
|
pluginDaemonComponents = newDaemons;
|
||||||
|
pluginLauncherComponents = newLaunchers;
|
||||||
|
pluginInstances = newInstances;
|
||||||
|
|
||||||
plugin.loaded = true;
|
plugin.loaded = true;
|
||||||
const newLoaded = Object.assign({}, loadedPlugins);
|
const newLoaded = Object.assign({}, loadedPlugins);
|
||||||
newLoaded[pluginId] = plugin;
|
newLoaded[pluginId] = plugin;
|
||||||
@@ -371,10 +436,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isDaemon = plugin.type === "daemon";
|
|
||||||
const isLauncher = plugin.type === "launcher" || (plugin.capabilities && plugin.capabilities.includes("launcher"));
|
|
||||||
const isDesktop = plugin.type === "desktop";
|
|
||||||
|
|
||||||
const instance = pluginInstances[pluginId];
|
const instance = pluginInstances[pluginId];
|
||||||
if (instance) {
|
if (instance) {
|
||||||
instance.destroy();
|
instance.destroy();
|
||||||
@@ -383,19 +444,22 @@ Singleton {
|
|||||||
pluginInstances = newInstances;
|
pluginInstances = newInstances;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDaemon && pluginDaemonComponents[pluginId]) {
|
if (pluginDaemonComponents[pluginId]) {
|
||||||
const newDaemons = Object.assign({}, pluginDaemonComponents);
|
const newDaemons = Object.assign({}, pluginDaemonComponents);
|
||||||
delete newDaemons[pluginId];
|
delete newDaemons[pluginId];
|
||||||
pluginDaemonComponents = newDaemons;
|
pluginDaemonComponents = newDaemons;
|
||||||
} else if (isLauncher && pluginLauncherComponents[pluginId]) {
|
}
|
||||||
|
if (pluginLauncherComponents[pluginId]) {
|
||||||
const newLaunchers = Object.assign({}, pluginLauncherComponents);
|
const newLaunchers = Object.assign({}, pluginLauncherComponents);
|
||||||
delete newLaunchers[pluginId];
|
delete newLaunchers[pluginId];
|
||||||
pluginLauncherComponents = newLaunchers;
|
pluginLauncherComponents = newLaunchers;
|
||||||
} else if (isDesktop && pluginDesktopComponents[pluginId]) {
|
}
|
||||||
|
if (pluginDesktopComponents[pluginId]) {
|
||||||
const newDesktop = Object.assign({}, pluginDesktopComponents);
|
const newDesktop = Object.assign({}, pluginDesktopComponents);
|
||||||
delete newDesktop[pluginId];
|
delete newDesktop[pluginId];
|
||||||
pluginDesktopComponents = newDesktop;
|
pluginDesktopComponents = newDesktop;
|
||||||
} else if (pluginWidgetComponents[pluginId]) {
|
}
|
||||||
|
if (pluginWidgetComponents[pluginId]) {
|
||||||
const newComponents = Object.assign({}, pluginWidgetComponents);
|
const newComponents = Object.assign({}, pluginWidgetComponents);
|
||||||
delete newComponents[pluginId];
|
delete newComponents[pluginId];
|
||||||
pluginWidgetComponents = newComponents;
|
pluginWidgetComponents = newComponents;
|
||||||
@@ -452,7 +516,8 @@ Singleton {
|
|||||||
const result = [];
|
const result = [];
|
||||||
for (const pluginId in availablePlugins) {
|
for (const pluginId in availablePlugins) {
|
||||||
const plugin = availablePlugins[pluginId];
|
const plugin = availablePlugins[pluginId];
|
||||||
if (plugin.type !== "widget") {
|
const hasWidgetSurface = plugin.surfaces ? plugin.surfaces.includes("widget") : (plugin.type === "widget");
|
||||||
|
if (!hasWidgetSurface) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const variants = getPluginVariants(pluginId);
|
const variants = getPluginVariants(pluginId);
|
||||||
|
|||||||
Reference in New Issue
Block a user