Move bar-settings and background-switcher into the shell
This commit is contained in:
@@ -1,13 +1,21 @@
|
||||
import QtQuick
|
||||
import QtQml.Models
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
import "plugins/bar"
|
||||
import "services" as Services
|
||||
import "services"
|
||||
|
||||
ShellRoot {
|
||||
id: shell
|
||||
|
||||
// Shared service instances. Plugins receive these via property injection
|
||||
// rather than re-importing them as singletons — relative-path imports do
|
||||
// not share singleton state, which silently leaves consumers with their
|
||||
// own empty copies.
|
||||
property PluginRegistry pluginRegistry: PluginRegistry { }
|
||||
property BarWidgetRegistry barWidgetRegistry: BarWidgetRegistry { }
|
||||
|
||||
property string home: Quickshell.env("HOME")
|
||||
|
||||
// The omarchy-shell host is the long-running entry point. Plugins live in
|
||||
@@ -30,16 +38,181 @@ ShellRoot {
|
||||
"omarchyPath=" + shell.omarchyPath,
|
||||
"shellDir=" + Quickshell.shellDir,
|
||||
"firstPartyPluginsDir=" + shell.firstPartyPluginsDir)
|
||||
Services.PluginRegistry.firstPartyDir = shell.firstPartyPluginsDir
|
||||
pluginRegistry.firstPartyDir = shell.firstPartyPluginsDir
|
||||
// PluginRegistry.ensureUserDir() runs in its own Component.onCompleted and
|
||||
// chains rescan() once the directory exists. We also kick a scan here in
|
||||
// case the user dir already existed at startup.
|
||||
Services.PluginRegistry.rescan()
|
||||
pluginRegistry.rescan()
|
||||
}
|
||||
|
||||
Bar {
|
||||
id: bar
|
||||
omarchyPath: shell.omarchyPath
|
||||
barWidgetRegistry: shell.barWidgetRegistry
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------- on-demand panels
|
||||
|
||||
// openPanelIds is a plain object treated as a set. A plugin id maps to
|
||||
// `true` while the panel is summoned; deleting the key (well, building a new
|
||||
// object without it) hides it. Reassigning the whole object is required for
|
||||
// QML to notice the change.
|
||||
property var openPanelIds: ({})
|
||||
property var panelCache: ({})
|
||||
|
||||
function isPanelOpen(id) { return openPanelIds[id] === true }
|
||||
|
||||
// Pending payloads to deliver to a plugin's open() once its loader resolves.
|
||||
// Keyed by plugin id; consumed by the Loader.onLoaded handler below.
|
||||
property var pendingPayloads: ({})
|
||||
|
||||
function summon(pluginId, payloadJson) {
|
||||
var id = String(pluginId || "")
|
||||
if (!id) return false
|
||||
var plugins = shell.pluginRegistry.installedPlugins
|
||||
if (!plugins[id]) {
|
||||
console.warn("summon: unknown plugin", id)
|
||||
return false
|
||||
}
|
||||
var next = ({})
|
||||
for (var k in openPanelIds) next[k] = openPanelIds[k]
|
||||
next[id] = true
|
||||
openPanelIds = next
|
||||
|
||||
// Stash payload so the Loader.onLoaded handler can hand it to open().
|
||||
var pending = ({})
|
||||
for (var p in pendingPayloads) pending[p] = pendingPayloads[p]
|
||||
pending[id] = payloadJson || ""
|
||||
pendingPayloads = pending
|
||||
|
||||
// If the plugin is keepLoaded and already mounted, deliver immediately.
|
||||
deliverIfLoaded(id)
|
||||
return true
|
||||
}
|
||||
|
||||
function hide(pluginId) {
|
||||
var id = String(pluginId || "")
|
||||
if (!id) return false
|
||||
invokeIfLoaded(id, "close", null)
|
||||
if (!openPanelIds[id]) return true
|
||||
var next = ({})
|
||||
for (var k in openPanelIds) if (k !== id) next[k] = openPanelIds[k]
|
||||
openPanelIds = next
|
||||
return true
|
||||
}
|
||||
|
||||
function toggle(pluginId, payloadJson) {
|
||||
var id = String(pluginId || "")
|
||||
return openPanelIds[id] ? hide(id) : summon(id, payloadJson)
|
||||
}
|
||||
|
||||
// Map of pluginId -> Loader, populated by the Instantiator delegate below.
|
||||
property var panelLoaders: ({})
|
||||
|
||||
function registerPanelLoader(pluginId, loader) {
|
||||
var next = ({})
|
||||
for (var k in panelLoaders) next[k] = panelLoaders[k]
|
||||
next[pluginId] = loader
|
||||
panelLoaders = next
|
||||
deliverIfLoaded(pluginId)
|
||||
}
|
||||
|
||||
function unregisterPanelLoader(pluginId) {
|
||||
if (!panelLoaders[pluginId]) return
|
||||
var next = ({})
|
||||
for (var k in panelLoaders) if (k !== pluginId) next[k] = panelLoaders[k]
|
||||
panelLoaders = next
|
||||
}
|
||||
|
||||
function deliverIfLoaded(pluginId) {
|
||||
var loader = panelLoaders[pluginId]
|
||||
if (!loader || !loader.item) return
|
||||
var payload = pendingPayloads[pluginId]
|
||||
if (payload === undefined) return
|
||||
if (typeof loader.item.open === "function") {
|
||||
try { loader.item.open(payload) } catch (e) {
|
||||
console.warn("plugin " + pluginId + " open() threw:", e)
|
||||
}
|
||||
}
|
||||
var next = ({})
|
||||
for (var k in pendingPayloads) if (k !== pluginId) next[k] = pendingPayloads[k]
|
||||
pendingPayloads = next
|
||||
}
|
||||
|
||||
function invokeIfLoaded(pluginId, method, arg) {
|
||||
var loader = panelLoaders[pluginId]
|
||||
if (!loader || !loader.item) return
|
||||
if (typeof loader.item[method] !== "function") return
|
||||
try { loader.item[method](arg) } catch (e) {
|
||||
console.warn("plugin " + pluginId + " " + method + "() threw:", e)
|
||||
}
|
||||
}
|
||||
|
||||
// One Loader per discoverable panel/overlay/menu plugin. Active when the
|
||||
// host marks it open. The Loader holds onto the instance while active so the
|
||||
// plugin's FloatingWindow + state survive between summons within a session.
|
||||
property var panelEntries: []
|
||||
|
||||
function computePanelEntries() {
|
||||
var out = []
|
||||
var plugins = shell.pluginRegistry.installedPlugins
|
||||
var panelKinds = ["panel", "overlay", "menu"]
|
||||
for (var id in plugins) {
|
||||
var m = plugins[id]
|
||||
if (!m || !Array.isArray(m.kinds)) continue
|
||||
var matched = false
|
||||
for (var i = 0; i < panelKinds.length; i++)
|
||||
if (m.kinds.indexOf(panelKinds[i]) !== -1) { matched = true; break }
|
||||
if (!matched) continue
|
||||
if (!shell.pluginRegistry.isEnabled(id)) continue
|
||||
var kind = m.kinds.indexOf("panel") !== -1 ? "panel"
|
||||
: (m.kinds.indexOf("overlay") !== -1 ? "overlay" : "menu")
|
||||
out.push({ id: id, manifest: m, kind: kind, keepLoaded: m.keepLoaded === true })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: shell.pluginRegistry
|
||||
function onPluginsChanged() { shell.panelEntries = shell.computePanelEntries() }
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
model: shell.panelEntries
|
||||
active: true
|
||||
|
||||
delegate: QtObject {
|
||||
id: panelEntry
|
||||
required property var modelData
|
||||
readonly property string pluginId: modelData.id
|
||||
readonly property var manifest: modelData.manifest
|
||||
readonly property string entryKind: modelData.kind
|
||||
readonly property bool keepLoaded: modelData.keepLoaded === true
|
||||
readonly property string sourceUrl: shell.pluginRegistry.entryPointUrl(manifest, entryKind)
|
||||
|
||||
property Loader panelLoader: Loader {
|
||||
source: panelEntry.sourceUrl
|
||||
active: panelEntry.sourceUrl !== "" && (panelEntry.keepLoaded || shell.openPanelIds[panelEntry.pluginId] === true)
|
||||
asynchronous: true
|
||||
onLoaded: {
|
||||
if (!item) return
|
||||
if ("omarchyPath" in item) item.omarchyPath = shell.omarchyPath
|
||||
if ("shell" in item) item.shell = shell
|
||||
if ("manifest" in item) item.manifest = panelEntry.manifest
|
||||
if ("barWidgetRegistry" in item) item.barWidgetRegistry = shell.barWidgetRegistry
|
||||
if ("pluginRegistry" in item) item.pluginRegistry = shell.pluginRegistry
|
||||
shell.registerPanelLoader(panelEntry.pluginId, this)
|
||||
}
|
||||
onStatusChanged: {
|
||||
if (status === Loader.Error) {
|
||||
console.warn("panel plugin " + panelEntry.pluginId + " failed:",
|
||||
sourceComponent ? sourceComponent.errorString() : "")
|
||||
shell.hide(panelEntry.pluginId)
|
||||
}
|
||||
}
|
||||
Component.onDestruction: shell.unregisterPanelLoader(panelEntry.pluginId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------- plugin loader
|
||||
@@ -48,32 +221,32 @@ ShellRoot {
|
||||
// Each enabled plugin with kind "bar-widget" gets a Component created from
|
||||
// its manifest entry point and registered under the id "plugin:<id>".
|
||||
Connections {
|
||||
target: Services.PluginRegistry
|
||||
target: shell.pluginRegistry
|
||||
function onPluginsChanged() { shell.syncPluginWidgets() }
|
||||
}
|
||||
|
||||
property var pluginWidgetComponents: ({})
|
||||
|
||||
function syncPluginWidgets() {
|
||||
var plugins = Services.PluginRegistry.installedPlugins
|
||||
var plugins = shell.pluginRegistry.installedPlugins
|
||||
var seen = ({})
|
||||
|
||||
for (var pluginId in plugins) {
|
||||
var manifest = plugins[pluginId]
|
||||
if (!manifest || !manifest.kinds || manifest.kinds.indexOf("bar-widget") === -1) continue
|
||||
if (!Services.PluginRegistry.isEnabled(pluginId)) continue
|
||||
if (!shell.pluginRegistry.isEnabled(pluginId)) continue
|
||||
|
||||
var registryKey = "plugin:" + manifest.id
|
||||
seen[registryKey] = true
|
||||
|
||||
// Already loaded with matching source — leave it alone.
|
||||
var existing = pluginWidgetComponents[registryKey]
|
||||
var url = Services.PluginRegistry.entryPointUrl(manifest, "barWidget")
|
||||
var url = shell.pluginRegistry.entryPointUrl(manifest, "barWidget")
|
||||
if (!url) {
|
||||
console.warn("Plugin " + manifest.id + " has no barWidget entry point")
|
||||
continue
|
||||
}
|
||||
if (existing && existing.url === url && Services.BarWidgetRegistry.has(registryKey)) continue
|
||||
if (existing && existing.url === url && shell.barWidgetRegistry.has(registryKey)) continue
|
||||
|
||||
var meta = manifest.barWidget || {}
|
||||
meta = {
|
||||
@@ -91,12 +264,12 @@ ShellRoot {
|
||||
}
|
||||
|
||||
// Drop registrations for plugins that are no longer present or enabled.
|
||||
var allIds = Services.BarWidgetRegistry.availableIds()
|
||||
var allIds = shell.barWidgetRegistry.availableIds()
|
||||
for (var i = 0; i < allIds.length; i++) {
|
||||
var id = allIds[i]
|
||||
if (id.indexOf("plugin:") !== 0) continue
|
||||
if (!seen[id]) {
|
||||
Services.BarWidgetRegistry.unregister(id)
|
||||
shell.barWidgetRegistry.unregister(id)
|
||||
var next = ({})
|
||||
for (var k in pluginWidgetComponents) if (k !== id) next[k] = pluginWidgetComponents[k]
|
||||
pluginWidgetComponents = next
|
||||
@@ -108,14 +281,14 @@ ShellRoot {
|
||||
var comp = Qt.createComponent(url, Component.Asynchronous)
|
||||
function finalize() {
|
||||
if (comp.status === Component.Ready) {
|
||||
Services.BarWidgetRegistry.register(registryKey, comp, meta)
|
||||
shell.barWidgetRegistry.register(registryKey, comp, meta)
|
||||
var next = ({})
|
||||
for (var k in pluginWidgetComponents) next[k] = pluginWidgetComponents[k]
|
||||
next[registryKey] = { url: url, component: comp }
|
||||
pluginWidgetComponents = next
|
||||
} else if (comp.status === Component.Error) {
|
||||
console.warn("Plugin widget " + registryKey + " failed: " + comp.errorString())
|
||||
Services.PluginRegistry.pluginLoadFailed(registryKey, comp.errorString())
|
||||
shell.pluginRegistry.pluginLoadFailed(registryKey, comp.errorString())
|
||||
}
|
||||
}
|
||||
if (comp.status === Component.Loading) {
|
||||
@@ -135,26 +308,38 @@ ShellRoot {
|
||||
}
|
||||
|
||||
function rescanPlugins(): void {
|
||||
Services.PluginRegistry.rescan()
|
||||
shell.pluginRegistry.rescan()
|
||||
}
|
||||
|
||||
function setPluginEnabled(id: string, enabled: string): void {
|
||||
Services.PluginRegistry.setEnabled(id, enabled === "true")
|
||||
shell.pluginRegistry.setEnabled(id, enabled === "true")
|
||||
}
|
||||
|
||||
function listPlugins(): string {
|
||||
var out = []
|
||||
var plugins = Services.PluginRegistry.installedPlugins
|
||||
var plugins = shell.pluginRegistry.installedPlugins
|
||||
for (var id in plugins) {
|
||||
out.push({
|
||||
id: id,
|
||||
name: plugins[id].name,
|
||||
kinds: plugins[id].kinds,
|
||||
enabled: Services.PluginRegistry.isEnabled(id),
|
||||
enabled: shell.pluginRegistry.isEnabled(id),
|
||||
firstParty: !!plugins[id].__isFirstParty
|
||||
})
|
||||
}
|
||||
return JSON.stringify(out)
|
||||
}
|
||||
|
||||
function summon(id: string, payloadJson: string): string {
|
||||
return shell.summon(id, payloadJson) ? "ok" : "unknown"
|
||||
}
|
||||
|
||||
function hide(id: string): void {
|
||||
shell.hide(id)
|
||||
}
|
||||
|
||||
function toggle(id: string, payloadJson: string): void {
|
||||
shell.toggle(id, payloadJson)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user