mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
PluginSystem: added support for desktop widgets.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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: ({})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user