diff --git a/config/omarchy/extensions/omarchy-menu.jsonc b/config/omarchy/extensions/omarchy-menu.jsonc index e17fccc9..bf32d350 100644 --- a/config/omarchy/extensions/omarchy-menu.jsonc +++ b/config/omarchy/extensions/omarchy-menu.jsonc @@ -11,7 +11,7 @@ // action Shell command to run. If omitted, the row is a submenu. // target Existing submenu id to open. Use for links/aliases. // provider Runtime provider function/command returning JSON rows. - // aliases Alternate `omarchy-menu ` routes; also searchable. + // aliases alternate `omarchy-shell-ipc menu summon ` routes; also searchable. // keywords Extra search terms beyond id/label/aliases. // description Optional subtitle and extra search text. // when Shell condition; hide row when it fails. diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index 00ffc8f8..21504d15 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -7,7 +7,7 @@ // Dotted IDs define the tree. Use provider:"name" only when the submenu // calls provider_name() or a command named "name" to return JSON rows. // Optional fields: - // aliases alternate `omarchy-menu ` routes; also searchable + // aliases alternate `omarchy-shell-ipc menu summon ` routes; also searchable // keywords extra search terms beyond id/label/aliases // when shell condition; hide row when it fails // checked shell condition; append ✓ when it succeeds diff --git a/default/quickshell/omarchy-shell/plugins/README.md b/default/quickshell/omarchy-shell/plugins/README.md index d8689d14..c4d235be 100644 --- a/default/quickshell/omarchy-shell/plugins/README.md +++ b/default/quickshell/omarchy-shell/plugins/README.md @@ -76,9 +76,12 @@ The menu definition lives outside the shell host code: - defaults: `default/omarchy/omarchy-menu.jsonc` - user extensions: `~/.config/omarchy/extensions/omarchy-menu.jsonc` -`bin/omarchy-menu` still owns the Bash action helpers and dynamic providers; -it builds the menu JSON, summons `omarchy.menu`, waits for the selection, and -runs the selected action. +The shell parses both JSONC files at startup (with `watchChanges: true` +so edits take effect without a restart), evaluates `when:` / `checked:` +bash expressions in a single batched subprocess, and executes the +selected `action:` string directly via `Quickshell.execDetached`. The +long-running shell process keeps the parsed menu in memory, so the +keybind → IPC → visible path costs ~30ms cold. ## Coming soon diff --git a/default/quickshell/omarchy-shell/plugins/menu/Menu.qml b/default/quickshell/omarchy-shell/plugins/menu/Menu.qml index a1f56333..2e3c4ba8 100644 --- a/default/quickshell/omarchy-shell/plugins/menu/Menu.qml +++ b/default/quickshell/omarchy-shell/plugins/menu/Menu.qml @@ -14,10 +14,7 @@ Item { // Plugin lifecycle hooks. The host calls open(payloadJson) after // `omarchy-shell-ipc shell summon omarchy.menu ...` and close() when hidden. - property string menuJsonFile: "" property string pendingInitialMenu: "root" - property string pendingSelectionFile: "" - property string pendingDoneFile: "" property string pendingColorsRaw: "" function decodeBase64(value) { @@ -31,30 +28,17 @@ Item { try { payload = JSON.parse(payloadJson || "{}") } catch (e) { payload = ({}) } root.pendingInitialMenu = payload.initialMenu || payload.menu || "root" - root.pendingSelectionFile = payload.selectionFile || "" - root.pendingDoneFile = payload.doneFile || "" root.pendingColorsRaw = payload.colorsRawBase64 ? root.decodeBase64(payload.colorsRawBase64) : (payload.colorsRaw || "") if (payload.fontFamily) root.fontFamily = payload.fontFamily + if (root.pendingColorsRaw) root.loadColors(root.pendingColorsRaw) - var menuJson = payload.menuJsonBase64 ? root.decodeBase64(payload.menuJsonBase64) : (payload.menuJson || "") - if (menuJson) { - root.openMenu(menuJson, root.pendingInitialMenu, root.pendingSelectionFile, root.pendingDoneFile, root.pendingColorsRaw) - return - } - - root.menuJsonFile = payload.menuJsonFile || "" - if (root.menuJsonFile) { menuJsonFileView.reload(); return } - - // Hot path: no file, no payload — use the cached items the shell loaded - // from JSONC at startup. This is what direct IPC summons land on. - root.openExistingMenu(root.pendingInitialMenu, root.pendingSelectionFile, root.pendingDoneFile) + root.openExistingMenu(root.pendingInitialMenu) } function close() { - root.closeMenu("") + root.cancel() } - property string menuBin: omarchyPath + "/bin/omarchy-menu" property string fontFamily: Quickshell.env("OMARCHY_MENU_FONT") || "monospace" property string colorsFile: Quickshell.env("OMARCHY_MENU_COLORS_FILE") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/colors.toml") property string styleFile: Quickshell.env("OMARCHY_MENU_STYLE_FILE") || (Quickshell.env("HOME") + "/.local/state/omarchy/toggles/quickshell-menu.json") @@ -65,9 +49,6 @@ Item { property string userMenuPath: Quickshell.env("HOME") + "/.config/omarchy/extensions/omarchy-menu.jsonc" property var defaultMenuItems: [] property var userMenuItems: [] - property string selectionFile: "" - property string doneFile: "" - property bool requestActive: false property bool opened: false property bool rowsLoaded: false property string activeMenu: "root" @@ -80,7 +61,6 @@ Item { property var navStack: [] property var providersLoaded: ({}) property var providerQueue: [] - property var doneFilesToRelease: [] property color accent: "#89b4fa" property color background: "#101315" property color foreground: "#cacccc" @@ -252,83 +232,6 @@ Item { if (root.opened) root.rebuildDisplay() } - // Legacy: invoked by the bash bin via the menuJsonFile payload. The new - // in-shell path uses rebuildItemsFromSources instead; this branch stays - // for the transitional period while the bin still exists. - function loadMenuJson(raw) { - var nextItems = ({}) - var nextOrder = [] - var payload = JSON.parse(raw || "{}") - var rows = payload.items || [] - - for (var i = 0; i < rows.length; i++) { - var entry = rows[i] - var id = entry.id || "" - if (!id) continue - - var order = nextItems[id] ? nextItems[id].order : nextOrder.length - if (!nextItems[id]) nextOrder.push(id) - - nextItems[id] = { - id: id, - parent: entry.parent || "", - kind: entry.kind || "action", - icon: entry.icon || "", - label: entry.label || id, - target: entry.target || "", - keywords: entry.keywords || "", - description: entry.description || "", - action: entry.action || "", - provider: entry.provider || "", - order: order - } - } - - if (!nextItems.root) { - nextItems.root = { id: "root", parent: "", kind: "menu", icon: "", label: "Go", target: "", keywords: "", description: "", order: -1 } - } - - root.items = nextItems - root.itemOrder = nextOrder - root.rowsLoaded = true - } - - function mergeProviderJson(raw, menuId) { - var payload = ({}) - try { - payload = JSON.parse(raw || "{}") - } catch (e) { - return - } - - var rows = payload.items || [] - var changed = false - - for (var i = 0; i < rows.length; i++) { - var entry = rows[i] - var id = entry.id || "" - if (!id) continue - - if (!root.items[id]) root.itemOrder.push(id) - root.items[id] = { - id: id, - parent: entry.parent || menuId, - kind: entry.kind || "action", - icon: entry.icon || "", - label: entry.label || id, - target: entry.target || "", - keywords: entry.keywords || "", - description: entry.description || "", - action: entry.action || "", - provider: entry.provider || "", - order: root.itemOrder.indexOf(id) - } - changed = true - } - - if (changed) root.rebuildDisplay() - } - // Each known provider is a tiny bash one-liner that enumerates a list and // emits one tab-delimited row per item: `label\tvalue\tcurrent`. The shell // turns those into menu items children of `menuId`. @@ -695,79 +598,21 @@ Item { } } - function releaseNextDoneFile() { - if (releaseProc.running || doneFilesToRelease.length === 0) return - - var path = doneFilesToRelease.shift() - releaseProc.command = ["bash", "-lc", ": > " + shellQuote(path)] - releaseProc.running = true - } - - function finishDoneFile(path) { - if (!path) return - doneFilesToRelease.push(path) - releaseNextDoneFile() - } - - function resetRequest() { - requestActive = false - selectionFile = "" - doneFile = "" - } - function applySelected(id, action) { if (!id) { cancel(); return } - - // Direct-IPC path: no bash bin was involved, so no tempfiles to write. - // Execute the action ourselves and close the menu. - if (!selectionFile || !doneFile) { - applySerial = requestSerial - resetRequest() - opened = false - filterText = "" - if (action) Quickshell.execDetached(["bash", "-lc", action]) - return - } - - // Legacy bash-bin path: write selection + done file, the bash watcher - // picks it up and runs the action. Stays for the transitional period - // while the old bin can still summon the menu via menuJsonFile. - var activeSelectionFile = selectionFile - var activeDoneFile = doneFile applySerial = requestSerial - resetRequest() opened = false - - applyProc.command = ["bash", "-lc", "printf '%s\\t%s\\n' " + shellQuote(id) + " " + shellQuote(action || "") + " > " + shellQuote(activeSelectionFile) + "; : > " + shellQuote(activeDoneFile)] - applyProc.running = true + filterText = "" + if (action) Quickshell.execDetached(["bash", "-lc", action]) } function cancel() { - if (requestActive) finishDoneFile(doneFile) - resetRequest() opened = false filterText = "" } - function closeMenu(nextDoneFile) { + function openExistingMenu(initialMenu) { requestSerial += 1 - - if (requestActive) finishDoneFile(doneFile) - if (nextDoneFile && nextDoneFile !== doneFile) finishDoneFile(nextDoneFile) - - resetRequest() - opened = false - filterText = "" - } - - function openExistingMenu(initialMenu, nextSelectionFile, nextDoneFile) { - requestSerial += 1 - - if (requestActive && doneFile && doneFile !== nextDoneFile) finishDoneFile(doneFile) - - selectionFile = nextSelectionFile - doneFile = nextDoneFile - requestActive = !!doneFile activeMenu = root.item(initialMenu) ? initialMenu : "root" navStack = [] filterText = "" @@ -779,14 +624,6 @@ Item { Qt.callLater(function() { keyCatcher.forceActiveFocus() }) } - function openMenu(menuJson, initialMenu, nextSelectionFile, nextDoneFile, colorsRaw) { - loadMenuJson(menuJson) - if (colorsRaw) loadColors(colorsRaw) - providersLoaded = ({}) - providerQueue = [] - openExistingMenu(initialMenu, nextSelectionFile, nextDoneFile) - } - function loadColors(raw) { var lines = String(raw || "").split("\n") for (var i = 0; i < lines.length; i++) { @@ -882,13 +719,6 @@ Item { } } - FileView { - id: menuJsonFileView - path: root.menuJsonFile - printErrors: false - onLoaded: root.openMenu(text(), root.pendingInitialMenu, root.pendingSelectionFile, root.pendingDoneFile, root.pendingColorsRaw) - } - // The JSONC sources are watched so live edits to the default file (or the // user extension at ~/.config/omarchy/extensions/omarchy-menu.jsonc) take // effect without restarting the shell. @@ -984,18 +814,6 @@ Item { onFileChanged: { reload(); root.loadStyle(text()) } } - Process { - id: applyProc - onExited: { - if (root.applySerial === root.requestSerial) root.opened = false - } - } - - Process { - id: releaseProc - onExited: root.releaseNextDoneFile() - } - PanelWindow { id: panel visible: root.opened && root.rowsLoaded