PluginSystem: added support for desktop widgets.

This commit is contained in:
Lemmy
2025-12-16 07:57:17 -05:00
parent fd1e7c55a1
commit 4ab86449c8
7 changed files with 178 additions and 19 deletions
+18 -6
View File
@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services.Compositor
import qs.Services.Noctalia
import qs.Services.UI
import qs.Widgets
@@ -12,6 +13,9 @@ Variants {
id: root
model: Quickshell.screens
// Direct binding to registry's widgets property for reactivity
readonly property var registeredWidgets: DesktopWidgetRegistry.widgets
delegate: Loader {
id: screenLoader
required property ShellScreen modelData
@@ -63,17 +67,16 @@ Variants {
delegate: Loader {
id: widgetLoader
active: DesktopWidgetRegistry.hasWidget(modelData.id)
// Bind to registeredWidgets to re-evaluate when plugins register/unregister
active: (modelData.id in root.registeredWidgets)
property var widgetData: modelData
property int widgetIndex: index
sourceComponent: {
var component = DesktopWidgetRegistry.getWidget(modelData.id);
if (component) {
return component;
}
return null;
// Access registeredWidgets to create reactive binding
var widgets = root.registeredWidgets;
return widgets[modelData.id] || null;
}
onLoaded: {
@@ -82,6 +85,15 @@ Variants {
item.parent = widgetsContainer;
item.widgetData = widgetData;
item.widgetIndex = widgetIndex;
// Inject plugin API for plugin widgets
if (DesktopWidgetRegistry.isPluginWidget(modelData.id)) {
var pluginId = modelData.id.replace("plugin:", "");
var api = PluginService.getPluginAPI(pluginId);
if (api && item.hasOwnProperty("pluginApi")) {
item.pluginApi = api;
}
}
}
}
}
@@ -14,7 +14,6 @@ Item {
// Optional customization
property real defaultX: 100
property real defaultY: 100
property color textColor: Color.mOnSurface
// Content slot - allows natural QML child syntax
default property alias content: contentContainer.data
@@ -111,8 +110,8 @@ Item {
id: decorationRect
anchors.fill: parent
anchors.margins: -Style.marginS
color: Settings.data.desktopWidgets.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : "transparent"
border.color: (Settings.data.desktopWidgets.editMode || internal.isDragging) ? (internal.isDragging ? Qt.rgba(textColor.r, textColor.g, textColor.b, 0.5) : Color.mPrimary) : "transparent"
color: Settings.data.desktopWidgets.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : Color.transparent
border.color: (Settings.data.desktopWidgets.editMode || internal.isDragging) ? (internal.isDragging ? Color.mOutline : Color.mPrimary) : Color.transparent
border.width: Settings.data.desktopWidgets.editMode ? 3 : (internal.isDragging ? 2 : 0)
radius: Style.radiusL + Style.marginS
z: -1
@@ -22,8 +22,6 @@ DraggableDesktopWidget {
property bool showSeconds: (widgetData && widgetData.showSeconds !== undefined) ? widgetData.showSeconds : true
property bool showDate: (widgetData && widgetData.showDate !== undefined) ? widgetData.showDate : true
textColor: clockTextColor
implicitWidth: contentLayout.implicitWidth + Style.marginXL * 2
implicitHeight: contentLayout.implicitHeight + Style.marginXL * 2
width: implicitWidth
@@ -4,6 +4,7 @@ import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Noctalia
import qs.Services.UI
import qs.Widgets
@@ -140,6 +141,29 @@ Popup {
}
function loadWidgetSettings() {
// Handle plugin widgets
if (DesktopWidgetRegistry.isPluginWidget(widgetId)) {
var pluginId = widgetId.replace("plugin:", "");
var manifest = PluginRegistry.getPluginManifest(pluginId);
if (!manifest || !manifest.entryPoints || !manifest.entryPoints.settings) {
Logger.w("DesktopWidgetSettingsDialog", "Plugin does not have settings:", pluginId);
return;
}
var pluginDir = PluginRegistry.getPluginDir(pluginId);
var settingsPath = "file://" + pluginDir + "/" + manifest.entryPoints.settings;
var loadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0;
var api = PluginService.getPluginAPI(pluginId);
settingsLoader.setSource(settingsPath + "?v=" + loadVersion, {
"widgetData": widgetData,
"pluginApi": api
});
return;
}
// Handle core widgets
const source = DesktopWidgetRegistry.widgetSettingsMap[widgetId];
if (source) {
var currentWidgetData = widgetData;
@@ -4,6 +4,7 @@ import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Compositor
import qs.Services.Noctalia
import qs.Services.UI
import qs.Widgets
@@ -34,11 +35,11 @@ ColumnLayout {
NButton {
visible: Settings.data.desktopWidgets.enabled
Layout.fillWidth: true
text: I18n.tr("settings.desktop-widgets.edit-mode.button.label")
text: Settings.data.desktopWidgets.editMode ? I18n.tr("settings.desktop-widgets.edit-mode.exit-button") : I18n.tr("settings.desktop-widgets.edit-mode.button.label")
icon: "edit"
onClicked: {
Settings.data.desktopWidgets.editMode = true;
if (Settings.data.ui.settingsPanelMode !== "window") {
Settings.data.desktopWidgets.editMode = !Settings.data.desktopWidgets.editMode;
if (Settings.data.desktopWidgets.editMode && Settings.data.ui.settingsPanelMode !== "window") {
var item = root.parent;
while (item) {
if (item.closeRequested !== undefined) {
@@ -110,9 +111,32 @@ ColumnLayout {
}
for (var i = 0; i < widgetIds.length; i++) {
var widgetId = widgetIds[i];
var displayName = widgetId;
// Get plugin name for plugin widgets
var isPlugin = false;
if (DesktopWidgetRegistry.isPluginWidget(widgetId)) {
isPlugin = true;
var pluginId = widgetId.replace("plugin:", "");
var manifest = PluginRegistry.getPluginManifest(pluginId);
if (manifest && manifest.name) {
displayName = manifest.name;
}
}
// Add plugin badge first (with custom color)
const badges = [];
if (isPlugin) {
badges.push({
"icon": "plugin",
"color": Color.mSecondary
});
}
availableWidgets.append({
"key": widgetId,
"name": widgetId
"name": displayName,
"badges": badges
});
}
Logger.d("DesktopWidgetsTab", "Available widgets model count:", availableWidgets.count);
@@ -180,6 +204,10 @@ ColumnLayout {
} else if (widgetId === "Weather") {
newWidget.x = 100;
newWidget.y = 300;
} else {
// Default position for plugin widgets
newWidget.x = 150;
newWidget.y = 150;
}
var widgets = getWidgetsForMonitor(monitorName).slice();
widgets.push(newWidget);
+25
View File
@@ -467,6 +467,7 @@ Singleton {
// Initialize plugin entry with API and manifest
root.loadedPlugins[pluginId] = {
barWidget: null,
desktopWidget: null,
mainInstance: null,
api: pluginApi,
manifest: manifest
@@ -528,6 +529,24 @@ Singleton {
}
}
// Load desktop widget component if provided (don't instantiate - DesktopWidgetRegistry will do that)
if (manifest.entryPoints && manifest.entryPoints.desktopWidget) {
var desktopWidgetPath = pluginDir + "/" + manifest.entryPoints.desktopWidget;
var desktopWidgetLoadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0;
var desktopWidgetComponent = Qt.createComponent("file://" + desktopWidgetPath + "?v=" + desktopWidgetLoadVersion);
if (desktopWidgetComponent.status === Component.Ready) {
root.loadedPlugins[pluginId].desktopWidget = desktopWidgetComponent;
pluginApi.desktopWidget = desktopWidgetComponent;
// Register with DesktopWidgetRegistry
DesktopWidgetRegistry.registerPluginWidget(pluginId, desktopWidgetComponent, manifest.metadata);
Logger.i("PluginService", "Loaded desktop widget for plugin:", pluginId);
} else if (desktopWidgetComponent.status === Component.Error) {
root.recordPluginError(pluginId, "desktopWidget", desktopWidgetComponent.errorString());
}
}
Logger.i("PluginService", "Plugin loaded:", pluginId);
root.pluginLoaded(pluginId);
}
@@ -547,6 +566,11 @@ Singleton {
BarWidgetRegistry.unregisterPluginWidget(pluginId);
}
// Unregister from DesktopWidgetRegistry
if (plugin.manifest.entryPoints && plugin.manifest.entryPoints.desktopWidget) {
DesktopWidgetRegistry.unregisterPluginWidget(pluginId);
}
// Destroy Main instance if any
if (plugin.mainInstance) {
plugin.mainInstance.destroy();
@@ -575,6 +599,7 @@ Singleton {
// Instance references (set after loading)
property var mainInstance: null
property var barWidget: null
property var desktopWidget: null
// IPC handlers storage
property var ipcHandlers: ({})
+77 -4
View File
@@ -8,6 +8,9 @@ import qs.Modules.DesktopWidgets.Widgets
Singleton {
id: root
// Signal emitted when plugin widgets are registered/unregistered
signal pluginWidgetRegistryUpdated
// Component definitions
property Component clockComponent: Component {
DesktopClock {}
@@ -62,6 +65,10 @@ Singleton {
}
})
// Plugin widget storage (mirroring BarWidgetRegistry pattern)
property var pluginWidgets: ({})
property var pluginWidgetMetadata: ({})
function init() {
Logger.i("DesktopWidgetRegistry", "Service started");
}
@@ -88,13 +95,79 @@ Singleton {
return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true);
}
// Check if a widget is a plugin widget (desktop widgets don't support plugins yet)
// Check if a widget is a plugin widget
function isPluginWidget(id) {
return false;
return id.startsWith("plugin:");
}
// Get list of plugin widget IDs (empty for now)
// Get list of plugin widget IDs
function getPluginWidgets() {
return [];
return Object.keys(pluginWidgets);
}
// Register a plugin desktop widget
function registerPluginWidget(pluginId, component, metadata) {
if (!pluginId || !component) {
Logger.e("DesktopWidgetRegistry", "Cannot register plugin widget: invalid parameters");
return false;
}
var widgetId = "plugin:" + pluginId;
// Create new objects to trigger QML property change detection
var newPluginWidgets = Object.assign({}, pluginWidgets);
newPluginWidgets[widgetId] = component;
pluginWidgets = newPluginWidgets;
var newPluginMetadata = Object.assign({}, pluginWidgetMetadata);
newPluginMetadata[widgetId] = metadata || {};
pluginWidgetMetadata = newPluginMetadata;
// Also add to main widgets object for unified access - reassign to trigger change
var newWidgets = Object.assign({}, widgets);
newWidgets[widgetId] = component;
widgets = newWidgets;
var newMetadata = Object.assign({}, widgetMetadata);
newMetadata[widgetId] = Object.assign({}, {
"allowUserSettings": true,
"showBackground": true
}, metadata || {});
widgetMetadata = newMetadata;
Logger.i("DesktopWidgetRegistry", "Registered plugin widget:", widgetId);
root.pluginWidgetRegistryUpdated();
return true;
}
// Unregister a plugin desktop widget
function unregisterPluginWidget(pluginId) {
var widgetId = "plugin:" + pluginId;
if (!pluginWidgets[widgetId]) {
Logger.w("DesktopWidgetRegistry", "Plugin widget not registered:", widgetId);
return false;
}
// Create new objects without the widget to trigger QML property change detection
var newPluginWidgets = Object.assign({}, pluginWidgets);
delete newPluginWidgets[widgetId];
pluginWidgets = newPluginWidgets;
var newPluginMetadata = Object.assign({}, pluginWidgetMetadata);
delete newPluginMetadata[widgetId];
pluginWidgetMetadata = newPluginMetadata;
var newWidgets = Object.assign({}, widgets);
delete newWidgets[widgetId];
widgets = newWidgets;
var newMetadata = Object.assign({}, widgetMetadata);
delete newMetadata[widgetId];
widgetMetadata = newMetadata;
Logger.i("DesktopWidgetRegistry", "Unregistered plugin widget:", widgetId);
root.pluginWidgetRegistryUpdated();
return true;
}
}