From c36602b30e6e7a10531e37fee8a5bbd6fcd89fc7 Mon Sep 17 00:00:00 2001 From: superduck00 <294879536+superduck00@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:26:09 -0500 Subject: [PATCH] Move through results with Super+J/K Super+J/K move the selection down and up in every navigation mode, in the list and in popups. Hyprland handles its own Super bindings before a client sees the key (Omarchy binds Super+J, and users often bind Super+K/L), so while the launcher is shown it dispatches hl.dsp.submap("omacast"), and "reset" when it hides. `omacast setup hyprland` now defines that submap and marks the launcher hotkey submap_universal so it still closes the launcher. The submap also holds back Omarchy's universal editing keys, so the two that mean something in the search field are handled directly: Super+V pastes into the query and Super+Backspace clears it. An omacast.lua written before this has no submap: Hyprland rejects the dispatch and stays in its default submap, so nothing changes until setup is rerun or the lines are added. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 1 + SPEC.md | 2 +- bin/omacast | 8 +++++++- tests/launcher-paste.test.js | 2 +- ui/LauncherWindow.qml | 16 +++++++++++++++- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b9d6019..9b70953 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -123,6 +123,7 @@ Works: - **Navigation:** pushed Omarchy submenus (search within them), the alias editor, back with Esc / Ctrl+[ / Backspace on an empty query / the header arrow, and Shift+Esc to pop to root. - **Keyboard:** Ctrl+K action panel (filterable, per-row shortcuts), ↵ / Ctrl+↵ / Ctrl+Shift+↵ for the first three actions, Ctrl+1–9, Ctrl+↑↓ to jump sections, PageUp/PageDown, ↑ on an empty root for search history, Ctrl+Shift+C copy, Alt+Shift+D disable, Ctrl + / − / 0 zoom. - Navigation bindings (`general.navigationBindings`): `emacs` moves down with Ctrl+N; `vim-motions` moves with Ctrl+J/K and puts the action panel on **Alt+K**. Set it with `omacast call settings.updateGeneralSettings '{"navigationBindings":"vim-motions"}'`. + - **Super+J/K** move down/up in every mode (H/L are kept for left/right once a grid view lands). While omacast is shown it dispatches `hl.dsp.submap("omacast")` (`reset` on hide); `omacast.lua` defines that submap and marks omacast's own hotkeys `submap_universal`, so Hyprland's Super binds (Super+J split, etc.) don't grab them. An `omacast.lua` without the submap just leaves Hyprland in its default submap. - **Ctrl+P** opens the root dropdown (All / Applications / Commands); emacs Ctrl+P = up only works inside popups. - **Ctrl+T** opens the toast's actions (Copy Error Message on failures, Undo after Disable Command). **Ctrl+Shift+K** opens the About panel for the selected row. - **Argument pills:** "Search Google" / "Search DuckDuckGo" take a query in a pill after the search text (Tab in, Shift+Tab out). diff --git a/SPEC.md b/SPEC.md index 4e3a823..5477cde 100644 --- a/SPEC.md +++ b/SPEC.md @@ -794,7 +794,7 @@ Repo layout [D]: - **HUD.** The HUD is Omarchy's OSD (`omarchy-shell osd show`). - **Settings.** There is no settings window yet (Phase 2); aliases are set from the action panel. - **Leftovers finished [D, 2026-10-22]:** - - **Navigation bindings.** `general.navigationBindings` = `emacs` (Ctrl+N down) or `vim-motions` (Ctrl+J/K). On Linux Ctrl replaces both ⌃ and ⌘, so vim's Ctrl+K would clash with the action panel: in vim mode the action panel moves to **Alt+K**. The search field has no text cursor, so Ctrl+B/F and Ctrl+H/L do nothing. + - **Navigation bindings.** `general.navigationBindings` = `emacs` (Ctrl+N down) or `vim-motions` (Ctrl+J/K). On Linux Ctrl replaces both ⌃ and ⌘, so vim's Ctrl+K would clash with the action panel: in vim mode the action panel moves to **Alt+K**. The search field has no text cursor, so Ctrl+B/F and Ctrl+H/L do nothing. Super+J/K move down/up in both modes, through a Hyprland submap held while the launcher is shown [D]. - **Ctrl+P dropdown.** Ctrl+P is always `toggleFilter`, so emacs Ctrl+P = up is dropped in the list (it still moves up inside popups). Raycast's root has no dropdown; ours filters root search by type (All / Applications / Commands, `RootSearch.FILTERS`), hides the Calculator and fallback sections while filtered, and resets on pop-to-root. - **Ctrl+T** opens the toast's actions. Failure toasts offer "Copy Error Message"; "Disable Command" offers "Undo". - **Ctrl+Shift+K** About panel: `rootSearch.getItemInfo` → `RootSearch.aboutFor`. diff --git a/bin/omacast b/bin/omacast index f6ab97e..c77a057 100755 --- a/bin/omacast +++ b/bin/omacast @@ -259,7 +259,13 @@ cmd_setup_hyprland() { -- Written by `omacast setup hyprland`. Safe to edit; rerunning setup keeps it. -- Alt+Space reaches the plugin's global shortcut directly, so no process -- starts per keypress. Omarchy's own menu stays on Super+Space. -o.bind("ALT + SPACE", "omacast launcher", hl.dsp.global("omacast:toggle")) +o.bind("ALT + SPACE", "omacast launcher", hl.dsp.global("omacast:toggle"), { submap_universal = true }) +-- While the launcher is open, the plugin puts Hyprland in this submap so Super+J/K +-- (and every other Super key Hyprland would grab) reach omacast as arrow keys. +-- omacast's own hotkeys are submap_universal so they still work there. +hl.define_submap("omacast", function() + hl.bind("SUPER + F24", hl.dsp.no_op()) -- a submap needs one bind to exist +end) LUA echo "wrote $file" fi diff --git a/tests/launcher-paste.test.js b/tests/launcher-paste.test.js index 68e8645..b82da23 100644 --- a/tests/launcher-paste.test.js +++ b/tests/launcher-paste.test.js @@ -23,7 +23,7 @@ function harness(text) { const window = { query: "prefix ", actionPanelOpen: false, view: { kind: "root" }, historyIndex: 0, argFocus: -1, argDefs: [], rebuild: reset => changes.push(reset), isTyping: () => false, - isActionPanelKey: () => false, direction: () => 0 + isActionPanelKey: () => false, direction: () => 0, superOnly: () => false } const actionPanel = { item: { filter: "" } } const clipboardInput = { text: "", clear() { this.text = "" }, paste() { this.text = text } } diff --git a/ui/LauncherWindow.qml b/ui/LauncherWindow.qml index 618a8ad..401c35d 100644 --- a/ui/LauncherWindow.qml +++ b/ui/LauncherWindow.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell import Quickshell.Wayland +import Quickshell.Hyprland import qs.Commons import qs.Commons as Commons import qs.Ui @@ -130,7 +131,11 @@ PanelWindow { Qt.callLater(function() { keyCatcher.forceActiveFocus() }) } + // A reload or disable while shown would otherwise leave Hyprland in the submap. + Component.onDestruction: if (window.shown) Hyprland.dispatch('hl.dsp.submap("reset")') onShownChanged: { + // The "omacast" submap (omacast.lua) lets Super+J/K past Hyprland's binds. + Hyprland.dispatch('hl.dsp.submap("' + (window.shown ? "omacast" : "reset") + '")') if (window.shown) return window.searchSerial++ window.backend.cancelSearch() @@ -533,11 +538,17 @@ PanelWindow { var key = event.key if (!ctrl && !alt && key === Qt.Key_Down) return 1 if (!ctrl && !alt && key === Qt.Key_Up) return -1 + if (window.superOnly(event)) return key === Qt.Key_J ? 1 : (key === Qt.Key_K ? -1 : 0) if (!ctrl || alt || (event.modifiers & Qt.ShiftModifier)) return 0 if (window.vim) return key === Qt.Key_J ? 1 : (key === Qt.Key_K ? -1 : 0) return key === Qt.Key_N ? 1 : (key === Qt.Key_P && inPopup ? -1 : 0) } + // Super+J/K move down/up; see the submap dispatch in onShownChanged. [D] + function superOnly(event) { + return (event.modifiers & ~Qt.KeypadModifier) === Qt.MetaModifier + } + function isActionPanelKey(ctrl, alt, shift, key) { if (key !== Qt.Key_K || shift) return false return window.vim ? alt && !ctrl : ctrl && !alt @@ -572,7 +583,8 @@ PanelWindow { if (ctrl && key === Qt.Key_BracketLeft) { window.closePopups(); window.pop(); return true } // Ctrl+V, Ctrl+Shift+V (the terminal habit) and Shift+Insert paste. var meta = (event.modifiers & Qt.MetaModifier) !== 0 - if ((key === Qt.Key_V && ctrl && !alt && !meta) || (key === Qt.Key_Insert && shift && !ctrl && !alt && !meta)) { + // Super+V too: the "omacast" submap keeps Omarchy's universal paste from turning it into Shift+Insert. + if ((key === Qt.Key_V && ((ctrl && !alt && !meta) || window.superOnly(event))) || (key === Qt.Key_Insert && shift && !ctrl && !alt && !meta)) { window.pasteClipboard() return true } @@ -637,6 +649,8 @@ PanelWindow { } if (Util.editsFilter(event, window.query)) { window.setQuery(Util.editedFilter(event, window.query)); return true } + // Super+Backspace (Omarchy's delete to line start) arrives raw under the submap: clear the query. + if (key === Qt.Key_Backspace && window.superOnly(event)) { window.setQuery(""); return true } if (key === Qt.Key_Backspace && !window.query && !window.atRoot) { window.pop(); return true } if (window.isTyping(event)) { window.setQuery(window.query + event.text); return true } return false