Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ All notable user-facing changes to QOpen are documented here.
### Fixed

- Recovered the plugin directory from the plugin's own entry point URL when
omarchy 4.0.4+ strips `__sourceDir` from third-party manifests, restoring the
Omarchy 4.0.3+ strips `__sourceDir` from third-party manifests, restoring the
catalog on shells that sanitize plugin manifests.
- Derived the fallback plugin root by removing the complete menu entry-point
path, so backend lookup remains correct if the entry point moves into a
subdirectory.

## [2.5.1] - 2026-08-28

Expand Down
23 changes: 19 additions & 4 deletions QOpen.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ Item {
property var pluginRegistry: null

readonly property string pluginId: (manifest && manifest.id) || "qopen.launcher"
// omarchy >= 4.0.4 hands third-party plugins a sanitized manifest without
// __sourceDir. Recover the plugin directory from the own entry point URL
// (file:///.../qopen.launcher/QOpen.qml -> .../qopen.launcher) instead.
// omarchy >= 4.0.3 hands third-party plugins a sanitized manifest without
// __sourceDir. Recover the plugin directory from the own entry point URL.
// Strip the full manifest entry-point path so this keeps working if the
// entry point later moves below the plugin root (for example ui/QOpen.qml).
readonly property string pluginDir: {
if (manifest && manifest.__sourceDir) return String(manifest.__sourceDir)
var entryUrl = ""
Expand All @@ -28,7 +29,21 @@ Item {
entryUrl = ""
}
}
if (!entryUrl) return ""
if (!entryUrl || entryUrl.indexOf("file://") !== 0) return ""

var entryPoint = manifest && manifest.entryPoints
? String(manifest.entryPoints.menu || "") : ""
if (entryPoint) {
var encodedEntryPoint = entryPoint.split("/").map(encodeURIComponent).join("/")
var suffix = "/" + encodedEntryPoint
if (entryUrl.slice(-suffix.length) === suffix) {
var rootUrl = entryUrl.slice(0, -suffix.length)
try { return decodeURIComponent(rootUrl.slice("file://".length)) }
catch (e) { return "" }
}
}

// Compatibility fallback if a host ever hides entryPoints as well.
var path = entryUrl.replace(/^file:\/\//, "")
var slash = path.lastIndexOf("/")
if (slash < 0) return ""
Expand Down
42 changes: 42 additions & 0 deletions tests/test_plugin_path_fallback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from pathlib import Path
import unittest


REPOSITORY = Path(__file__).resolve().parents[1]


class PluginDirectoryFallbackSourceTests(unittest.TestCase):
def test_fallback_strips_the_complete_manifest_entry_point(self) -> None:
qopen_source = (REPOSITORY / "QOpen.qml").read_text(encoding="utf-8")

self.assertIn("omarchy >= 4.0.3", qopen_source)
self.assertIn("var entryPoint = manifest && manifest.entryPoints", qopen_source)
self.assertIn(
'entryPoint.split("/").map(encodeURIComponent).join("/")',
qopen_source,
)
self.assertIn('var suffix = "/" + encodedEntryPoint', qopen_source)
self.assertIn(
"if (entryUrl.slice(-suffix.length) === suffix)",
qopen_source,
)
self.assertIn(
"var rootUrl = entryUrl.slice(0, -suffix.length)",
qopen_source,
)
self.assertIn(
'decodeURIComponent(rootUrl.slice("file://".length))',
qopen_source,
)

def test_fallback_rejects_non_file_entry_urls(self) -> None:
qopen_source = (REPOSITORY / "QOpen.qml").read_text(encoding="utf-8")

self.assertIn(
'if (!entryUrl || entryUrl.indexOf("file://") !== 0) return ""',
qopen_source,
)


if __name__ == "__main__":
unittest.main()