diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index 5e4193f66..8d8e72677 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -61,24 +61,52 @@ Item { enabled: BarWidgetRegistry.isPluginWidget(root.widgetId) function onPluginWidgetRegistryUpdated() { - // Force the loader to reload by toggling active if (BarWidgetRegistry.hasWidget(root.widgetId)) { root.reloadCounter++; + // Plugin widgets use setSource, so also trigger reload directly + if (root._isPlugin && loader.active) + root._loadPluginWidget(); Logger.d("BarWidgetLoader", "Plugin widget registry updated, reloading:", root.widgetId); } } } + readonly property bool _isPlugin: BarWidgetRegistry.isPluginWidget(widgetId) + + function _loadPluginWidget() { + var comp = BarWidgetRegistry.getWidget(root.widgetId); + if (!comp) + return; + var pluginId = root.widgetId.replace("plugin:", ""); + var api = PluginService.getPluginAPI(pluginId); + loader.setSource(comp.url, api ? { + "pluginApi": api + } : {}); + } + Loader { id: loader anchors.fill: parent asynchronous: true active: root.checkWidgetExists() && (root.reloadCounter >= 0) - sourceComponent: { - // Depend on reloadCounter to force re-fetch of component - var _ = root.reloadCounter; - return root.checkWidgetExists() ? BarWidgetRegistry.getWidget(root.widgetId) : null; + // Core widgets use sourceComponent; plugin widgets use setSource() + // so pluginApi is available from the first binding evaluation. + // Binding is set imperatively to avoid sourceComponent interfering with setSource. + Component.onCompleted: { + if (root._isPlugin) { + root._loadPluginWidget(); + } else { + sourceComponent = Qt.binding(function () { + var _ = root.reloadCounter; + return root.checkWidgetExists() ? BarWidgetRegistry.getWidget(root.widgetId) : null; + }); + } + } + + onActiveChanged: { + if (active && root._isPlugin) + root._loadPluginWidget(); } // Unregister when the loaded item is destroyed (Loader deactivated or sourceComponent changed) @@ -119,17 +147,6 @@ Item { item.screen = widgetScreen; } - // Inject plugin API for plugin widgets - // The API is fully populated (settings/translations already loaded) by PluginService - if (BarWidgetRegistry.isPluginWidget(widgetId)) { - var pluginId = widgetId.replace("plugin:", ""); - var api = PluginService.getPluginAPI(pluginId); - if (api && item.hasOwnProperty("pluginApi")) { - item.pluginApi = api; - Logger.d("BarWidgetLoader", "Injected plugin API for", widgetId); - } - } - // Unregister any previous registration before registering the new instance root._unregister(); diff --git a/Modules/DesktopWidgets/DesktopWidgets.qml b/Modules/DesktopWidgets/DesktopWidgets.qml index 7f5b72e2e..fa70451fc 100644 --- a/Modules/DesktopWidgets/DesktopWidgets.qml +++ b/Modules/DesktopWidgets/DesktopWidgets.qml @@ -288,12 +288,37 @@ Variants { required property var modelData required property int index property var _maskRegion: null + readonly property bool _isPlugin: DesktopWidgetRegistry.isPluginWidget(modelData.id) - sourceComponent: { - // Access registeredWidgets and pluginReloadCounter to create reactive binding - var _ = root.pluginReloadCounter; - var widgets = root.registeredWidgets; - return widgets[modelData.id] || null; + // Core widgets use sourceComponent; plugin widgets use setSource() + // so pluginApi is available from the first binding evaluation. + // Binding is set imperatively to avoid sourceComponent interfering with setSource. + Component.onCompleted: { + if (_isPlugin) { + _loadPluginWidget(); + } else { + sourceComponent = Qt.binding(function () { + var _ = root.pluginReloadCounter; + var widgets = root.registeredWidgets; + return widgets[modelData.id] || null; + }); + } + } + + onActiveChanged: { + if (active && _isPlugin) + _loadPluginWidget(); + } + + function _loadPluginWidget() { + var comp = root.registeredWidgets[modelData.id]; + if (!comp) + return; + var pluginId = modelData.id.replace("plugin:", ""); + var api = PluginService.getPluginAPI(pluginId); + setSource(comp.url, api ? { + "pluginApi": api + } : {}); } onLoaded: { @@ -309,15 +334,6 @@ Variants { var newRegions = window._maskRegions.slice(); newRegions.push(_maskRegion); window._maskRegions = newRegions; - - // 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; - } - } } } diff --git a/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml b/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml index 94520a2d8..28f77d8ae 100644 --- a/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml +++ b/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml @@ -22,11 +22,35 @@ Item { return (item && item.visible) ? item[prop] : 0; } + readonly property bool _isPlugin: ControlCenterWidgetRegistry.isPluginWidget(widgetId) + + function _loadPluginWidget() { + var comp = ControlCenterWidgetRegistry.getWidget(widgetId); + if (!comp) + return; + var pluginId = widgetId.substring(7); // Remove "plugin:" prefix + var api = PluginService.getPluginAPI(pluginId); + loader.setSource(comp.url, api ? { + "pluginApi": api + } : {}); + } + Loader { id: loader anchors.fill: parent asynchronous: false - sourceComponent: ControlCenterWidgetRegistry.getWidget(widgetId) + + // Core widgets use sourceComponent; plugin widgets use setSource() + // so pluginApi is available from the first binding evaluation. + Component.onCompleted: { + if (root._isPlugin) { + root._loadPluginWidget(); + } else { + sourceComponent = Qt.binding(function () { + return ControlCenterWidgetRegistry.getWidget(widgetId); + }); + } + } onLoaded: { if (!item) @@ -44,12 +68,6 @@ Item { item.screen = widgetScreen; } - // Pass pluginApi for plugin widgets - if (ControlCenterWidgetRegistry.isPluginWidget(widgetId) && item.hasOwnProperty("pluginApi")) { - var pluginId = widgetId.substring(7); // Remove "plugin:" prefix - item.pluginApi = PluginService.getPluginAPI(pluginId); - } - // Call custom onLoaded if it exists if (item.hasOwnProperty("onLoaded")) { item.onLoaded(); diff --git a/Modules/Panels/Plugins/PluginPanelSlot.qml b/Modules/Panels/Plugins/PluginPanelSlot.qml index a87c6f2c8..098feacfc 100644 --- a/Modules/Panels/Plugins/PluginPanelSlot.qml +++ b/Modules/Panels/Plugins/PluginPanelSlot.qml @@ -164,16 +164,14 @@ SmartPanel { // Get plugin API var api = PluginService.getPluginAPI(pluginId); - // Activate loader and set component simultaneously + // Use setSource with initial properties so pluginApi is available + // from the first binding evaluation (before onLoaded) root.contentLoader.active = true; - root.contentLoader.sourceComponent = component; + root.contentLoader.setSource(component.url, api ? { + "pluginApi": api + } : {}); - // Immediately inject API (before any bindings evaluate) if (root.contentLoader.item) { - if (root.contentLoader.item.hasOwnProperty("pluginApi")) { - root.contentLoader.item.pluginApi = api; - } - root.pluginInstance = root.contentLoader.item; root.currentPluginId = pluginId; diff --git a/Services/Noctalia/PluginService.qml b/Services/Noctalia/PluginService.qml index 753dd6737..31de3d679 100644 --- a/Services/Noctalia/PluginService.qml +++ b/Services/Noctalia/PluginService.qml @@ -1024,8 +1024,9 @@ Singleton { // Set current language (can't use binding in Qt.createQmlObject string) api.currentLanguage = I18n.langCode; - // Set pre-loaded settings and translations (available immediately!) - api.pluginSettings = settings || {}; + // Merge manifest defaults with loaded settings (user settings take priority) + var defaults = (manifest.metadata && manifest.metadata.defaultSettings) || {}; + api.pluginSettings = Object.assign({}, defaults, settings || {}); api.pluginTranslations = translations || {}; api.pluginFallbackTranslations = fallbackTranslations || {};