Skip to content
Open
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
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
8 changes: 7 additions & 1 deletion bin/omacast
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/launcher-paste.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } }
Expand Down
16 changes: 15 additions & 1 deletion ui/LauncherWindow.qml
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down