diff --git a/CHANGELOG.md b/CHANGELOG.md index dad16c6..442e287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable user-facing changes to QOpen are documented here. +## [Unreleased] + +### 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 + catalog on shells that sanitize plugin manifests. + ## [2.5.1] - 2026-08-28 ### Security diff --git a/QOpen.qml b/QOpen.qml index 5854d1e..fb70fd8 100644 --- a/QOpen.qml +++ b/QOpen.qml @@ -12,9 +12,29 @@ Item { property string omarchyPath: Quickshell.env("OMARCHY_PATH") property var shell: null property var manifest: null + property var pluginRegistry: null readonly property string pluginId: (manifest && manifest.id) || "qopen.launcher" - readonly property string pluginDir: (manifest && manifest.__sourceDir) || "" + // 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. + readonly property string pluginDir: { + if (manifest && manifest.__sourceDir) return String(manifest.__sourceDir) + var entryUrl = "" + if (pluginRegistry) { + try { + entryUrl = String(pluginRegistry.entryPointUrl(manifest, "menu") || "") + } catch (e) { + entryUrl = "" + } + } + if (!entryUrl) return "" + var path = entryUrl.replace(/^file:\/\//, "") + var slash = path.lastIndexOf("/") + if (slash < 0) return "" + try { return decodeURIComponent(path.slice(0, slash)) } + catch (e) { return path.slice(0, slash) } + } readonly property string backendPath: pluginDir ? pluginDir + "/bin/qopen" : "" onPluginDirChanged: { diff --git a/tests/test_backend.py b/tests/test_backend.py index 03b52bc..1ad0400 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -399,6 +399,14 @@ def test_catalog_waits_for_the_host_to_inject_the_plugin_directory(self) -> None self.assertIn("if (root.pluginDir) root.requestCatalogReload()", qopen_source) self.assertNotIn('readonly property string backendPath: pluginDir + "/bin/qopen"', qopen_source) + def test_plugin_directory_falls_back_to_entry_point_url_when_manifest_strips_source_dir(self) -> None: + qopen_source = (REPOSITORY / "QOpen.qml").read_text(encoding="utf-8") + + self.assertIn('return String(manifest.__sourceDir)', qopen_source) + self.assertIn('pluginRegistry.entryPointUrl(manifest, "menu")', qopen_source) + self.assertIn('entryUrl.replace(/^file:\\/\\//, "")', qopen_source) + self.assertIn('decodeURIComponent(path.slice(0, slash))', qopen_source) + def test_missing_catalog_requests_first_run_initialization(self) -> None: qopen_source = (REPOSITORY / "QOpen.qml").read_text(encoding="utf-8")