Plugin cloning via menu (#6433)

* Add plugin cloning via menu

* Split plugin commands by action

* Keep plugin enablement in action commands

* Remove unused plugin edit command

* Simplify plugin rescan arguments

* Assume Omarchy shell is running for plugin commands

* Remove plugin compatibility dispatcher

* Flatten plugin clone command

* Keep only shared plugin helpers

* Remove plugin rescan wrapper

* Keep plugin commands self-contained

* Simplify plugin clone lifecycle
This commit is contained in:
David Heinemeier Hansson
2026-07-29 21:34:47 -04:00
committed by GitHub
parent fb564e3ac1
commit f8835df644
28 changed files with 1238 additions and 1012 deletions
+59 -2
View File
@@ -28,6 +28,7 @@ QtObject {
signal pluginsChanged()
signal scanFinished()
signal pluginLoadFailed(string id, string error)
signal localPluginChanged(string id)
// ---------------------------------------------------------------- helpers
@@ -141,6 +142,19 @@ QtObject {
&& config.disabledPlugins.indexOf(Util.canonicalWidgetId(String(id))) !== -1
}
function resolveEnabledId(id) {
var key = Util.canonicalWidgetId(String(id || ""))
// Callers keep using the built-in id after cloning; the enabled local
// manifest is the implementation that should receive the call.
for (var candidate in installedPlugins) {
var manifest = installedPlugins[candidate]
var metadata = manifest && Util.isPlainObject(manifest.omarchy) ? manifest.omarchy : null
if (metadata && String(metadata.clonedFrom || "") === key && isEnabled(candidate))
return candidate
}
return key
}
// A bar widget is on when it sits in the bar, whoever shipped it. That is a
// different question from isEnabled(), which decides whether the widget's
// component is loaded at all — a built-in stays loadable so it can be put
@@ -200,6 +214,8 @@ QtObject {
}
var isBarOption = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar") !== -1
var isBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1
var hasNonWidgetKind = manifest && Array.isArray(manifest.kinds)
&& manifest.kinds.some(function(kind) { return kind !== "bar-widget" })
shellConfigMutator(function(config) {
// Ensure shape exists.
if (!Util.isPlainObject(config.bar)) config.bar = { layout: { left: [], center: [], right: [] } }
@@ -241,7 +257,7 @@ QtObject {
else if (location.kind === "plugin") config.plugins.splice(location.index, 1)
// Dropping the layout entry is the whole story for a widget. Anything
// else built-in loads by default, so switching it off has to be stated.
if (isFirstParty && !isBarWidget && !isDisabled(config, key)) {
if (isFirstParty && (!isBarWidget || hasNonWidgetKind) && !isDisabled(config, key)) {
if (!Array.isArray(config.disabledPlugins)) config.disabledPlugins = []
config.disabledPlugins.push(key)
}
@@ -337,7 +353,36 @@ QtObject {
}
property Process initProcess: Process {
onExited: registry.rescan()
onExited: {
localPluginWatcher.running = true
registry.rescan()
}
}
property Process localPluginWatcher: Process {
command: [
"inotifywait",
"-m",
"-r",
"-q",
"-e",
"close_write,create,delete,move",
"--format",
"%w%f",
registry.pluginsDir
]
stdout: SplitParser {
onRead: function(path) {
var pluginId = registry.localPluginIdForPath(path)
if (pluginId) registry.localPluginChanged(pluginId)
}
}
onExited: localPluginWatcherRestart.restart()
}
property Timer localPluginWatcherRestart: Timer {
interval: 1000
onTriggered: localPluginWatcher.running = true
}
function rescan() {
@@ -379,5 +424,17 @@ QtObject {
initProcess.running = true
}
function localPluginIdForPath(filePath) {
var base = pluginsDir.replace(/\/$/, "") + "/"
var path = String(filePath || "").trim()
if (path.indexOf(base + "local.") !== 0) return ""
var relative = path.slice(base.length)
if (relative.indexOf("/.git/") !== -1 || relative.endsWith("/.git")) return ""
var slash = relative.indexOf("/")
return slash === -1 ? relative : relative.slice(0, slash)
}
Component.onCompleted: ensureUserDir()
}