aurora2495d96
aurora: add runner and aurora-run, replace fuzzel
- Runner.qml: SUPER+R launcher (desktop apps + actions with icons, aurora commands, session actions, $PATH executables, =calc, >cmd, frecency) - dmenu mode over the new `dmenu` IPC; .local/bin/aurora-run is a fuzzel-compatible front end (-d -p -l --placeholder --password --index) - Fuzzy.qml / Session.qml shared with PowerMenu - scripts use aurora-run; askpass-fuzzel -> aurora-askpass, fuzzelunicode -> aurora-chars - drop fuzzel config, wal template and package
+733 −123
27 files changed
- D .config/fuzzel/fuzzel.ini +0 −31
- M .config/hypr/hyprland.lua +1 −1
- M .config/hypr/source/keybinds.lua +1 −2
- A .config/quickshell/aurora/Fuzzy.qml +25 −0
- M .config/quickshell/aurora/PowerMenu.qml +3 −46
- A .config/quickshell/aurora/Runner.qml +564 −0
- A .config/quickshell/aurora/Session.qml +35 −0
- M .config/quickshell/aurora/Theme.qml +5 −0
- M .config/quickshell/aurora/shell.qml +16 −0
- M .config/shell/profile +1 −1
- D .config/wal/templates/colors-fuzzel.ini +0 −11
- D .local/bin/askpass-fuzzel +0 −12
- A .local/bin/aurora-askpass +13 −0
- R .local/bin/fuzzelunicode → .local/bin/aurora-chars +1 −1
- A .local/bin/aurora-run +51 −0
- M .local/bin/bcn +1 −1
- M .local/bin/campus-login +1 −1
- M .local/bin/envyswitch +1 −1
- M .local/bin/hypr/hypr_screenshot.sh +1 −1
- M .local/bin/hypr/hypr_shader_menu.sh +3 −3
- M .local/bin/madd +2 −2
- M .local/bin/mounter +3 −3
- M .local/bin/movie +1 −1
- M .local/bin/unmounter +1 −1
- M .local/bin/waldl +1 −1
- M README.md +2 −2
- M packages/desktop.txt +0 −1
| @@ -1,31 +0,0 @@ | |||
| 1 | − | include=~/.cache/wal/colors-fuzzel.ini | |
| 2 | − | ||
| 3 | − | [main] | |
| 4 | − | dpi-aware=no | |
| 5 | − | lines=10 | |
| 6 | − | filter-desktop=yes | |
| 7 | − | icon-theme=Papirus-Dark | |
| 8 | − | font=JetBrains Mono Nerd Font:size=12 | |
| 9 | − | terminal=foot -e | |
| 10 | − | list-executables-in-path=yes | |
| 11 | − | password-character=* | |
| 12 | − | width=80 | |
| 13 | − | horizontal-pad=10 | |
| 14 | − | vertical-pad=10 | |
| 15 | − | inner-pad=5 | |
| 16 | − | ||
| 17 | − | # [colors] | |
| 18 | − | # background=1e1e2edd | |
| 19 | − | # text=cdd6f4ff | |
| 20 | − | # prompt=bac2deff | |
| 21 | − | # placeholder=7f849cff | |
| 22 | − | # input=cdd6f4ff | |
| 23 | − | # match=89b4faff | |
| 24 | − | # selection=585b70ff | |
| 25 | − | # selection-text=cdd6f4ff | |
| 26 | − | # selection-match=89b4faff | |
| 27 | − | # counter=7f849cff | |
| 28 | − | # border=89b4faff | |
| 29 | − | ||
| 30 | − | [key-bindings] | |
| 31 | − | execute-input=Control+Return Shift+KP_Enter | |
| @@ -1,5 +1,5 @@ | |||
| 1 | 1 | terminal = "foot" | |
| 2 | − | menu = "fuzzel" | |
| 2 | + | menu = "aurora-run" -- aurora's runner | |
| 3 | 3 | browser = "librewolf" | |
| 4 | 4 | mainMod = "SUPER" | |
| 5 | 5 | ||
| @@ -37,7 +37,7 @@ hl.bind(mainMod .. " + SHIFT + b", hl.dsp.exec_cmd("systemctl --user restart aur | |||
| 37 | 37 | hl.bind(mainMod .. " + R", hl.dsp.exec_cmd(menu)) | |
| 38 | 38 | -- bind = $mainMod SHIFT, P, pseudo, # dwindle | |
| 39 | 39 | hl.bind(mainMod .. " + Tab", hl.dsp.window.cycle_next({ next = false }), { repeating = true }) | |
| 40 | − | hl.bind(mainMod .. " + grave", hl.dsp.exec_cmd("fuzzelunicode")) | |
| 40 | + | hl.bind(mainMod .. " + grave", hl.dsp.exec_cmd("aurora-chars")) | |
| 41 | 41 | hl.bind(mainMod .. " + escape", hl.dsp.exec_cmd("qs -c aurora ipc call shell power")) | |
| 42 | 42 | ||
| 43 | 43 | -- Applications | |
| @@ -63,7 +63,6 @@ hl.bind(mainMod .. " + k", hl.dsp.window.resize({ x = 0, y = -30, relative = tru | |||
| 63 | 63 | hl.bind(mainMod .. " + j", hl.dsp.window.resize({ x = 0, y = 30, relative = true }), { repeating = true }) | |
| 64 | 64 | ||
| 65 | 65 | -- Clipboard menu (cliphist) | |
| 66 | − | -- hl.bind(mainMod .. " + v", hl.dsp.exec_cmd("sh -c \"cliphist list | fuzzel --dmenu --with-nth 2 | cliphist decode | wl-copy\"")) | |
| 67 | 66 | hl.bind(mainMod .. " + v", hl.dsp.exec_cmd("qs -c aurora ipc call shell clip")) | |
| 68 | 67 | ||
| 69 | 68 | -- ----------------------------------------------------- | |
| @@ -0,0 +1,25 @@ | |||
| 1 | + | pragma Singleton | |
| 2 | + | import QtQuick | |
| 3 | + | import Quickshell | |
| 4 | + | ||
| 5 | + | // fuzzel-style matching shared by PowerMenu and Runner. | |
| 6 | + | Singleton { | |
| 7 | + | // query chars must appear in order; prefix > word start > contiguous > scattered. q must be lowercase. | |
| 8 | + | function score(label, q) { return scoreLow(label.toLowerCase(), q) } | |
| 9 | + | // same, for an already lowercased s (big dmenu lists lowercase once, not per keystroke) | |
| 10 | + | function scoreLow(s, q) { | |
| 11 | + | if (q === "") return 1 | |
| 12 | + | if (s.indexOf(q) === 0) return 1000 | |
| 13 | + | var w = s.indexOf(" " + q) | |
| 14 | + | if (w >= 0) return 500 | |
| 15 | + | if (s.indexOf(q) >= 0) return 300 | |
| 16 | + | var pos = 0, gaps = 0 | |
| 17 | + | for (var i = 0; i < q.length; i++) { | |
| 18 | + | var f = s.indexOf(q[i], pos) | |
| 19 | + | if (f < 0) return 0 | |
| 20 | + | gaps += f - pos | |
| 21 | + | pos = f + 1 | |
| 22 | + | } | |
| 23 | + | return 100 - gaps | |
| 24 | + | } | |
| 25 | + | } | |
| @@ -27,16 +27,7 @@ PanelWindow { | |||
| 27 | 27 | onCleared: win.closeRequested() | |
| 28 | 28 | } | |
| 29 | 29 | ||
| 30 | − | readonly property var actions: [ | |
| 31 | − | { id: "lock", icon: Theme.iLock, label: "lock", confirm: false }, | |
| 32 | − | { id: "leave", icon: Theme.iLogout, label: "leave Hyprland", confirm: true }, | |
| 33 | − | { id: "renew", icon: Theme.iRefresh, label: "renew Hyprland", confirm: false }, | |
| 34 | − | { id: "hibernate", icon: Theme.iHibernate, label: "hibernate", confirm: true }, | |
| 35 | − | { id: "reboot", icon: Theme.iReboot, label: "reboot", confirm: true }, | |
| 36 | − | { id: "shutdown", icon: Theme.iPower, label: "shutdown", confirm: true }, | |
| 37 | − | { id: "sleep", icon: Theme.iSleep, label: "sleep", confirm: false }, | |
| 38 | − | { id: "display", icon: Theme.iMonitor, label: "display off", confirm: false } | |
| 39 | − | ] | |
| 30 | + | readonly property var actions: Session.actions | |
| 40 | 31 | ||
| 41 | 32 | property int current: 0 | |
| 42 | 33 | property string armed: "" | |
| @@ -50,27 +41,10 @@ PanelWindow { | |||
| 50 | 41 | filter.forceActiveFocus() | |
| 51 | 42 | } | |
| 52 | 43 | ||
| 53 | − | // fuzzel-style: query chars must appear in order; prefix > word start > contiguous > scattered | |
| 54 | − | function score(label, q) { | |
| 55 | − | if (q === "") return 1 | |
| 56 | − | var s = label.toLowerCase() | |
| 57 | − | if (s.indexOf(q) === 0) return 1000 | |
| 58 | − | var w = s.indexOf(" " + q) | |
| 59 | − | if (w >= 0) return 500 | |
| 60 | − | if (s.indexOf(q) >= 0) return 300 | |
| 61 | − | var pos = 0, gaps = 0 | |
| 62 | − | for (var i = 0; i < q.length; i++) { | |
| 63 | − | var f = s.indexOf(q[i], pos) | |
| 64 | − | if (f < 0) return 0 | |
| 65 | − | gaps += f - pos | |
| 66 | − | pos = f + 1 | |
| 67 | − | } | |
| 68 | − | return 100 - gaps | |
| 69 | − | } | |
| 70 | 44 | readonly property var shown: { | |
| 71 | 45 | var q = filter.text.toLowerCase().trim(), out = [] | |
| 72 | 46 | for (var i = 0; i < actions.length; i++) { | |
| 73 | − | var sc = score(actions[i].label, q) | |
| 47 | + | var sc = Fuzzy.score(actions[i].label, q) | |
| 74 | 48 | if (sc > 0) out.push({ a: actions[i], sc: sc, i: i }) | |
| 75 | 49 | } | |
| 76 | 50 | out.sort(function (x, y) { return y.sc - x.sc || x.i - y.i }) | |
| @@ -86,24 +60,7 @@ PanelWindow { | |||
| 86 | 60 | if (a.confirm && armed !== a.id) { armed = a.id; disarm.restart(); return } | |
| 87 | 61 | armed = "" | |
| 88 | 62 | closeRequested() | |
| 89 | − | run(a.id) | |
| 90 | − | } | |
| 91 | − | function run(id) { | |
| 92 | − | switch (id) { | |
| 93 | − | case "lock": Quickshell.execDetached(["loginctl", "lock-session"]); break | |
| 94 | − | case "leave": Hypr.logout(); break | |
| 95 | − | case "renew": Quickshell.execDetached(["hyprctl", "reload"]); break | |
| 96 | − | case "hibernate": Quickshell.execDetached(["systemctl", "hibernate", "-i"]); break | |
| 97 | − | case "reboot": Quickshell.execDetached(["systemctl", "reboot", "-i"]); break | |
| 98 | − | case "shutdown": Quickshell.execDetached(["systemctl", "poweroff", "-i"]); break | |
| 99 | − | case "sleep": Quickshell.execDetached(["systemctl", "suspend", "-i"]); break | |
| 100 | − | case "display": dpmsDelay.restart(); break // let the Enter key-up pass first, it would wake the screen | |
| 101 | − | } | |
| 102 | − | } | |
| 103 | − | Timer { | |
| 104 | − | id: dpmsDelay | |
| 105 | − | interval: 400 | |
| 106 | − | onTriggered: Quickshell.execDetached(["hyprctl", "dispatch", 'hl.dsp.dpms({action="off"})']) | |
| 63 | + | Session.run(a.id) | |
| 107 | 64 | } | |
| 108 | 65 | ||
| 109 | 66 | Rectangle { | |
| @@ -0,0 +1,564 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | import Quickshell | |
| 4 | + | import Quickshell.Io | |
| 5 | + | import Quickshell.Wayland | |
| 6 | + | import Quickshell.Hyprland | |
| 7 | + | ||
| 8 | + | // App / command runner (replaced fuzzel on SUPER+R). One fuzzy list of desktop apps (+ their actions), | |
| 9 | + | // aurora commands and every executable on $PATH, ranked by match and launch count. | |
| 10 | + | // `=expr` calculator (Enter copies), `>cmd` runs a shell command. Ctrl+Enter runs in the terminal. | |
| 11 | + | // dmenu mode (`aurora-run -d`, `dmenu` IPC): plain list from the script's stdin, the choice goes back over a fifo. | |
| 12 | + | PanelWindow { | |
| 13 | + | id: win | |
| 14 | + | property bool opened: false | |
| 15 | + | property var shell // shell.qml root: toggleOverlay / openTab / barShown | |
| 16 | + | signal closeRequested() | |
| 17 | + | signal openRequested() // a dmenu request is loaded and wants the window | |
| 18 | + | ||
| 19 | + | readonly property int rowH: 36 | |
| 20 | + | readonly property int rows: 10 | |
| 21 | + | readonly property int maxShown: 60 | |
| 22 | + | readonly property string home: Quickshell.env("HOME") | |
| 23 | + | ||
| 24 | + | // ---- dmenu mode ---- | |
| 25 | + | property string mode: "run" // run | dmenu | |
| 26 | + | property var dmItems: [] // [{kind:"dm", name, low, i}] | |
| 27 | + | property string dmPrompt: "" | |
| 28 | + | property string dmPlaceholder: "" | |
| 29 | + | property int dmLines: -1 // -1 default, 0 = input only (no list) | |
| 30 | + | property bool dmPassword: false | |
| 31 | + | property bool dmIndex: false | |
| 32 | + | property string dmFifo: "" // pending request's reply fifo, "" when none | |
| 33 | + | readonly property bool dmenu: mode === "dmenu" | |
| 34 | + | readonly property int rowsShown: !dmenu ? rows | |
| 35 | + | : dmLines === 0 ? 0 : Math.min(dmLines > 0 ? dmLines : rows, 15, Math.max(1, dmItems.length)) | |
| 36 | + | ||
| 37 | + | // dir holds `opts` (key=value lines) and `items` (stdin of the script), see ~/.local/bin/aurora-run | |
| 38 | + | function startDmenu(dir) { | |
| 39 | + | if (dmFifo !== "") reply(false, "") // a newer request replaces a pending one | |
| 40 | + | loadProc.dir = dir | |
| 41 | + | loadProc.command = ["sh", "-c", 'cat "$1/opts"; printf "\\036\\n"; [ -f "$1/items" ] && cat "$1/items"', "sh", dir] | |
| 42 | + | loadProc.running = true | |
| 43 | + | } | |
| 44 | + | Process { | |
| 45 | + | id: loadProc | |
| 46 | + | property string dir | |
| 47 | + | stdout: StdioCollector { | |
| 48 | + | onStreamFinished: { | |
| 49 | + | var sep = text.indexOf("\x1e\n") | |
| 50 | + | if (sep < 0) return | |
| 51 | + | var o = {}, ol = text.substring(0, sep).split("\n") | |
| 52 | + | for (var i = 0; i < ol.length; i++) { | |
| 53 | + | var eq = ol[i].indexOf("=") | |
| 54 | + | if (eq > 0) o[ol[i].substring(0, eq)] = ol[i].substring(eq + 1) | |
| 55 | + | } | |
| 56 | + | var items = [], l = text.substring(sep + 2).split("\n") | |
| 57 | + | for (var j = 0; j < l.length; j++) | |
| 58 | + | if (l[j] !== "") items.push({ kind: "dm", key: "", name: l[j], low: l[j].toLowerCase(), i: j, | |
| 59 | + | sub: "", icon: "", glyph: "", tag: "" }) | |
| 60 | + | win.dmPrompt = o.prompt || "" | |
| 61 | + | win.dmPlaceholder = o.placeholder || "" | |
| 62 | + | win.dmLines = o.lines !== undefined && o.lines !== "" ? parseInt(o.lines) : -1 | |
| 63 | + | win.dmPassword = o.password === "1" | |
| 64 | + | win.dmIndex = o.index === "1" | |
| 65 | + | win.dmItems = items | |
| 66 | + | win.dmFifo = loadProc.dir + "/reply" | |
| 67 | + | win.mode = "dmenu" | |
| 68 | + | filter.text = "" | |
| 69 | + | win.current = 0 | |
| 70 | + | win.openRequested() | |
| 71 | + | filter.forceActiveFocus() | |
| 72 | + | } | |
| 73 | + | } | |
| 74 | + | } | |
| 75 | + | // "<0|1>\n<value>\0" to the fifo through stdin, so a password never shows up in a process's argv | |
| 76 | + | function reply(ok, value) { | |
| 77 | + | replyProc.payload = (ok ? "0" : "1") + "\n" + value + "\0" | |
| 78 | + | replyProc.command = ["sh", "-c", 'cat > "$1"', "sh", dmFifo] | |
| 79 | + | replyProc.stdinEnabled = true | |
| 80 | + | replyProc.running = true | |
| 81 | + | dmFifo = "" | |
| 82 | + | } | |
| 83 | + | Process { | |
| 84 | + | id: replyProc | |
| 85 | + | property string payload | |
| 86 | + | onStarted: { write(payload); payload = ""; stdinEnabled = false } | |
| 87 | + | } | |
| 88 | + | function endDmenu() { | |
| 89 | + | mode = "run" | |
| 90 | + | dmItems = [] | |
| 91 | + | dmPassword = false | |
| 92 | + | filter.text = "" | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | visible: opened | |
| 96 | + | implicitWidth: dmenu ? 640 : 560 | |
| 97 | + | implicitHeight: 24 + 36 + rowsShown * rowH + 22 | |
| 98 | + | exclusiveZone: 0 | |
| 99 | + | color: "transparent" | |
| 100 | + | WlrLayershell.layer: WlrLayer.Overlay | |
| 101 | + | WlrLayershell.namespace: "aurora-runner" | |
| 102 | + | WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None | |
| 103 | + | ||
| 104 | + | HyprlandFocusGrab { | |
| 105 | + | windows: [win] | |
| 106 | + | active: win.opened | |
| 107 | + | onCleared: win.closeRequested() | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | property int current: 0 | |
| 111 | + | property string armed: "" | |
| 112 | + | Timer { id: disarm; interval: 3000; onTriggered: win.armed = "" } | |
| 113 | + | ||
| 114 | + | onOpenedChanged: { | |
| 115 | + | if (!opened) { | |
| 116 | + | // closed without a choice (esc, outside click, another overlay): the script gets "cancelled" | |
| 117 | + | if (dmFifo !== "") reply(false, "") | |
| 118 | + | if (dmenu) endDmenu() | |
| 119 | + | return | |
| 120 | + | } | |
| 121 | + | armed = "" | |
| 122 | + | filter.forceActiveFocus() | |
| 123 | + | if (dmenu) return // startDmenu already reset the input | |
| 124 | + | filter.text = "" | |
| 125 | + | current = 0 | |
| 126 | + | if (Date.now() - binsAt > 60000) binProc.running = true | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | // ---- sources ---- | |
| 130 | + | function basename(p) { p = String(p); return p.substring(p.lastIndexOf("/") + 1) } | |
| 131 | + | ||
| 132 | + | // desktop entries + their actions ("Librewolf: New Private Window") | |
| 133 | + | property var apps: [] | |
| 134 | + | function loadApps() { | |
| 135 | + | var out = [], l = DesktopEntries.applications.values | |
| 136 | + | for (var i = 0; i < l.length; i++) { | |
| 137 | + | var e = l[i] | |
| 138 | + | if (e.noDisplay) continue | |
| 139 | + | var ic = e.icon ? Quickshell.iconPath(e.icon, true) : "" | |
| 140 | + | var bin = e.command && e.command.length ? basename(e.command[0]) : "" | |
| 141 | + | out.push({ kind: "app", key: "app:" + e.id, name: e.name, sub: e.genericName || e.comment || "", | |
| 142 | + | icon: ic, glyph: Theme.iApp, tag: "app", entry: e, action: null, bin: bin, | |
| 143 | + | hay: [e.genericName || "", (e.keywords || []).join(" "), bin] }) | |
| 144 | + | var acts = e.actions || [] | |
| 145 | + | for (var j = 0; j < acts.length; j++) | |
| 146 | + | out.push({ kind: "act", key: "act:" + e.id + ":" + acts[j].id, name: e.name + ": " + acts[j].name, sub: "", | |
| 147 | + | icon: ic, glyph: Theme.iApp, tag: "app", entry: e, action: acts[j], bin: "", hay: [] }) | |
| 148 | + | } | |
| 149 | + | apps = out | |
| 150 | + | } | |
| 151 | + | Component.onCompleted: loadApps() | |
| 152 | + | Connections { | |
| 153 | + | target: DesktopEntries | |
| 154 | + | function onApplicationsChanged() { win.loadApps() } | |
| 155 | + | } | |
| 156 | + | ||
| 157 | + | // executables on $PATH (first one wins, like the shell). Apps whose binary is already listed are skipped. | |
| 158 | + | property var bins: [] | |
| 159 | + | property double binsAt: 0 | |
| 160 | + | Process { | |
| 161 | + | id: binProc | |
| 162 | + | command: ["sh", "-c", 'set -f; IFS=:; for d in $PATH; do [ -d "$d" ] && find -L "$d" -maxdepth 1 -type f -executable -printf "%f\\t%p\\n" 2>/dev/null; done'] | |
| 163 | + | stdout: StdioCollector { | |
| 164 | + | onStreamFinished: { | |
| 165 | + | var seen = {}, out = [], l = text.split("\n") | |
| 166 | + | for (var i = 0; i < l.length; i++) { | |
| 167 | + | var t = l[i].indexOf("\t") | |
| 168 | + | if (t <= 0) continue | |
| 169 | + | var n = l[i].substring(0, t) | |
| 170 | + | if (seen[n]) continue | |
| 171 | + | seen[n] = true | |
| 172 | + | out.push({ kind: "bin", key: "bin:" + n, name: n, sub: l[i].substring(t + 1), | |
| 173 | + | icon: "", glyph: Theme.iConsole, tag: "bin", hay: [] }) | |
| 174 | + | } | |
| 175 | + | win.bins = out | |
| 176 | + | win.binsAt = Date.now() | |
| 177 | + | } | |
| 178 | + | } | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | // aurora's own actions. Session ones come from Session (same confirm rules as the power menu). | |
| 182 | + | readonly property var commands: { | |
| 183 | + | var c = [ | |
| 184 | + | { id: "panel", glyph: Theme.iHome, name: "control panel" }, | |
| 185 | + | { id: "audio", glyph: Theme.iVolHigh, name: "audio settings" }, | |
| 186 | + | { id: "net", glyph: Theme.iWifi, name: "network settings" }, | |
| 187 | + | { id: "bt", glyph: Theme.iBt, name: "bluetooth settings" }, | |
| 188 | + | { id: "sys", glyph: Theme.iChip, name: "system info" }, | |
| 189 | + | { id: "notif", glyph: Theme.iBell, name: "notifications" }, | |
| 190 | + | { id: "settings", glyph: Theme.iCog, name: "aurora settings" }, | |
| 191 | + | { id: "wall", glyph: Theme.iImage, name: "wallpaper picker" }, | |
| 192 | + | { id: "wallrnd", glyph: Theme.iShuffle, name: "random wallpaper" }, | |
| 193 | + | { id: "clip", glyph: Theme.iClipboard, name: "clipboard history" }, | |
| 194 | + | { id: "overview", glyph: Theme.iWindow, name: "window overview" }, | |
| 195 | + | { id: "cal", glyph: Theme.iCal, name: "calendar" }, | |
| 196 | + | { id: "power", glyph: Theme.iPower, name: "power menu" }, | |
| 197 | + | { id: "caffeine", glyph: Theme.iCoffee, name: "toggle caffeine" }, | |
| 198 | + | { id: "dnd", glyph: Theme.iBellOff, name: "toggle do not disturb" }, | |
| 199 | + | { id: "bar", glyph: Theme.iMonitor, name: "toggle bar" }, | |
| 200 | + | { id: "picker", glyph: Theme.iPicker, name: "colour picker" }, | |
| 201 | + | { id: "shot", glyph: Theme.iCrop, name: "screenshot region" }, | |
| 202 | + | { id: "ocr", glyph: Theme.iCrop, name: "screenshot ocr" } | |
| 203 | + | ] | |
| 204 | + | if (Rec.available) c.push({ id: "rec", glyph: Theme.iRec, name: Rec.busy ? "stop recording" : "record screen" }) | |
| 205 | + | var out = [] | |
| 206 | + | for (var i = 0; i < c.length; i++) | |
| 207 | + | out.push({ kind: "aurora", key: "aur:" + c[i].id, id: c[i].id, name: c[i].name, sub: "", icon: "", | |
| 208 | + | glyph: c[i].glyph, tag: "aurora", confirm: false, hay: [] }) | |
| 209 | + | var s = Session.actions | |
| 210 | + | for (var j = 0; j < s.length; j++) | |
| 211 | + | out.push({ kind: "session", key: "ses:" + s[j].id, id: s[j].id, name: s[j].label, sub: "session", icon: "", | |
| 212 | + | glyph: s[j].icon, tag: "aurora", confirm: s[j].confirm, hay: [] }) | |
| 213 | + | return out | |
| 214 | + | } | |
| 215 | + | ||
| 216 | + | // ---- frecency: launch counts in ~/.cache/aurora/runner.json ---- | |
| 217 | + | property var counts: ({}) | |
| 218 | + | FileView { | |
| 219 | + | id: store | |
| 220 | + | path: win.home + "/.cache/aurora/runner.json" | |
| 221 | + | printErrors: false | |
| 222 | + | onLoaded: { try { win.counts = JSON.parse(text()) } catch (e) { win.counts = {} } } | |
| 223 | + | } | |
| 224 | + | function bump(key) { | |
| 225 | + | var c = {} | |
| 226 | + | for (var k in counts) c[k] = counts[k] | |
| 227 | + | c[key] = (c[key] || 0) + 1 | |
| 228 | + | counts = c | |
| 229 | + | store.setText(JSON.stringify(c)) | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | // ---- calculator ---- | |
| 233 | + | readonly property var mathFns: ["sqrt", "cbrt", "abs", "sin", "cos", "tan", "asin", "acos", "atan", | |
| 234 | + | "log", "log2", "log10", "exp", "floor", "ceil", "round", "min", "max", "pow"] | |
| 235 | + | function calc(expr) { | |
| 236 | + | if (!/^[0-9a-z\s.+\-*\/%^(),]+$/i.test(expr) || !/\d|pi/i.test(expr)) return "" | |
| 237 | + | var bad = false | |
| 238 | + | var js = expr.replace(/\^/g, "**").replace(/[a-z][a-z0-9]*/gi, function (w) { | |
| 239 | + | var l = w.toLowerCase() | |
| 240 | + | if (l === "pi") return "Math.PI" | |
| 241 | + | if (l === "e") return "Math.E" | |
| 242 | + | if (l === "ln") return "Math.log" | |
| 243 | + | if (win.mathFns.indexOf(l) >= 0) return "Math." + l | |
| 244 | + | bad = true | |
| 245 | + | return w | |
| 246 | + | }) | |
| 247 | + | if (bad) return "" | |
| 248 | + | try { | |
| 249 | + | var r = (new Function("return (" + js + ")"))() | |
| 250 | + | if (typeof r !== "number" || !isFinite(r)) return "" | |
| 251 | + | return String(Number(r.toPrecision(12))) | |
| 252 | + | } catch (e) { return "" } | |
| 253 | + | } | |
| 254 | + | ||
| 255 | + | // ---- ranking ---- | |
| 256 | + | function rank(it, q) { | |
| 257 | + | var s = Fuzzy.score(it.name, q) | |
| 258 | + | for (var i = 0; i < it.hay.length; i++) | |
| 259 | + | if (it.hay[i] !== "") s = Math.max(s, Fuzzy.score(it.hay[i], q) * 0.6) | |
| 260 | + | if (s <= 0) return 0 | |
| 261 | + | var w = it.kind === "app" ? 1 : it.kind === "bin" ? 0.6 : 0.85 | |
| 262 | + | return s * w + 60 * Math.log(1 + (counts[it.key] || 0)) | |
| 263 | + | } | |
| 264 | + | readonly property var shown: { | |
| 265 | + | var raw = filter.text, q = raw.toLowerCase().trim(), res = [] | |
| 266 | + | if (dmenu) { | |
| 267 | + | if (dmLines === 0) return res | |
| 268 | + | var cap = 1000, dl = dmItems, di | |
| 269 | + | if (q === "") return dl.length > cap ? dl.slice(0, cap) : dl | |
| 270 | + | var hits = [] | |
| 271 | + | for (di = 0; di < dl.length; di++) { | |
| 272 | + | var dsc = Fuzzy.scoreLow(dl[di].low, q) | |
| 273 | + | if (dsc > 0) hits.push({ it: dl[di], sc: dsc }) | |
| 274 | + | } | |
| 275 | + | hits.sort(function (x, y) { return y.sc - x.sc || x.it.i - y.it.i }) | |
| 276 | + | for (di = 0; di < hits.length && di < cap; di++) res.push(hits[di].it) | |
| 277 | + | return res | |
| 278 | + | } | |
| 279 | + | if (q.charAt(0) === ">") { | |
| 280 | + | var cmd = raw.trim().substring(1).trim() | |
| 281 | + | if (cmd !== "") res.push({ kind: "sh", key: "", name: cmd, sub: "run in sh", icon: "", glyph: Theme.iShell, tag: "sh" }) | |
| 282 | + | return res | |
| 283 | + | } | |
| 284 | + | if (q.charAt(0) === "=") { | |
| 285 | + | var r = calc(q.substring(1)) | |
| 286 | + | res.push({ kind: "calc", key: "", name: r !== "" ? r : "...", sub: q.substring(1).trim(), icon: "", | |
| 287 | + | glyph: Theme.iCalc, tag: "=", value: r }) | |
| 288 | + | return res | |
| 289 | + | } | |
| 290 | + | if (/[\d)]\s*[+\-*\/%^]\s*[\d(.]/.test(q)) { | |
| 291 | + | var v = calc(q) | |
| 292 | + | if (v !== "") res.push({ kind: "calc", key: "", name: v, sub: q, icon: "", glyph: Theme.iCalc, tag: "=", value: v }) | |
| 293 | + | } | |
| 294 | + | ||
| 295 | + | var all = [], i | |
| 296 | + | var appBins = {} | |
| 297 | + | for (i = 0; i < apps.length; i++) { all.push(apps[i]); if (apps[i].bin) appBins[apps[i].bin] = true } | |
| 298 | + | for (i = 0; i < commands.length; i++) all.push(commands[i]) | |
| 299 | + | var scored = [] | |
| 300 | + | if (q === "") { | |
| 301 | + | // most used first, then apps A-Z; no actions / bins / commands unless used before | |
| 302 | + | for (i = 0; i < bins.length; i++) if (counts[bins[i].key]) all.push(bins[i]) | |
| 303 | + | for (i = 0; i < all.length; i++) { | |
| 304 | + | var n = counts[all[i].key] || 0 | |
| 305 | + | if (n > 0 || all[i].kind === "app") scored.push({ it: all[i], sc: n }) | |
| 306 | + | } | |
| 307 | + | scored.sort(function (x, y) { return y.sc - x.sc || x.it.name.localeCompare(y.it.name) }) | |
| 308 | + | } else { | |
| 309 | + | for (i = 0; i < bins.length; i++) if (!appBins[bins[i].name]) all.push(bins[i]) | |
| 310 | + | for (i = 0; i < all.length; i++) { | |
| 311 | + | var sc = rank(all[i], q) | |
| 312 | + | if (sc > 0) scored.push({ it: all[i], sc: sc }) | |
| 313 | + | } | |
| 314 | + | scored.sort(function (x, y) { return y.sc - x.sc || x.it.name.length - y.it.name.length }) | |
| 315 | + | } | |
| 316 | + | for (i = 0; i < scored.length && res.length < maxShown; i++) res.push(scored[i].it) | |
| 317 | + | // nothing matched: offer to run the text as a command, like fuzzel's execute-input | |
| 318 | + | if (res.length === 0 && q !== "") | |
| 319 | + | res.push({ kind: "sh", key: "", name: raw.trim(), sub: "run in sh", icon: "", glyph: Theme.iShell, tag: "sh" }) | |
| 320 | + | return res | |
| 321 | + | } | |
| 322 | + | ||
| 323 | + | // ---- launching ---- | |
| 324 | + | function exec(cmd, term) { | |
| 325 | + | Quickshell.execDetached({ command: term ? ["foot", "-e"].concat(cmd) : cmd, workingDirectory: home }) | |
| 326 | + | } | |
| 327 | + | function trigger(i, term) { | |
| 328 | + | if (dmenu) { | |
| 329 | + | // like fuzzel: the selected line, or the typed text when nothing matches / Ctrl+Enter | |
| 330 | + | var sel = term ? null : shown[i] | |
| 331 | + | if (dmIndex) reply(true, sel ? String(sel.i) : "-1") | |
| 332 | + | else reply(true, sel ? sel.name : filter.text) | |
| 333 | + | closeRequested() | |
| 334 | + | return | |
| 335 | + | } | |
| 336 | + | var it = shown[i] | |
| 337 | + | if (!it) return | |
| 338 | + | current = i | |
| 339 | + | if (it.confirm && armed !== it.key) { armed = it.key; disarm.restart(); return } | |
| 340 | + | armed = "" | |
| 341 | + | if (it.key !== "") bump(it.key) | |
| 342 | + | closeRequested() | |
| 343 | + | switch (it.kind) { | |
| 344 | + | case "app": | |
| 345 | + | if (term || it.entry.runInTerminal) exec(it.entry.command, true) | |
| 346 | + | else it.entry.execute() | |
| 347 | + | break | |
| 348 | + | case "act": it.action.execute(); break | |
| 349 | + | case "bin": exec([it.name], term); break | |
| 350 | + | case "sh": exec(["sh", "-c", it.name], term); break | |
| 351 | + | case "calc": if (it.value !== "") Quickshell.execDetached(["wl-copy", "--", it.value]); break | |
| 352 | + | case "session": Session.run(it.id); break | |
| 353 | + | case "aurora": command(it.id); break | |
| 354 | + | } | |
| 355 | + | } | |
| 356 | + | function command(id) { | |
| 357 | + | switch (id) { | |
| 358 | + | case "panel": shell.openTab("home"); break | |
| 359 | + | case "audio": case "net": case "bt": case "sys": case "notif": case "settings": | |
| 360 | + | shell.openTab(id); break | |
| 361 | + | case "wall": case "clip": case "overview": case "cal": case "power": | |
| 362 | + | shell.toggleOverlay(id); break | |
| 363 | + | case "wallrnd": Wall.random(); break | |
| 364 | + | case "caffeine": Toggles.caffeine = !Toggles.caffeine; break | |
| 365 | + | case "dnd": Notifs.dnd = !Notifs.dnd; break | |
| 366 | + | case "bar": shell.barShown = !shell.barShown; break | |
| 367 | + | case "picker": Toggles.pickColour(); break | |
| 368 | + | case "shot": Shot.take("region"); break | |
| 369 | + | case "ocr": Shot.take("ocr"); break | |
| 370 | + | case "rec": Rec.toggle("screen"); break | |
| 371 | + | } | |
| 372 | + | } | |
| 373 | + | ||
| 374 | + | // ---- UI ---- | |
| 375 | + | Rectangle { | |
| 376 | + | anchors.fill: parent | |
| 377 | + | color: Theme.bgA | |
| 378 | + | border.width: 2 | |
| 379 | + | border.color: Theme.accent | |
| 380 | + | ||
| 381 | + | ColumnLayout { | |
| 382 | + | anchors.fill: parent | |
| 383 | + | anchors.margins: 12 | |
| 384 | + | spacing: 2 | |
| 385 | + | ||
| 386 | + | Rectangle { | |
| 387 | + | Layout.fillWidth: true | |
| 388 | + | Layout.bottomMargin: 6 | |
| 389 | + | implicitHeight: 30 | |
| 390 | + | color: Theme.surface | |
| 391 | + | border.width: 1 | |
| 392 | + | border.color: filter.activeFocus ? Theme.accent : Theme.line | |
| 393 | + | Text { | |
| 394 | + | id: promptText | |
| 395 | + | anchors.left: parent.left | |
| 396 | + | anchors.leftMargin: 10 | |
| 397 | + | anchors.verticalCenter: parent.verticalCenter | |
| 398 | + | text: win.dmenu && win.dmPrompt !== "" ? win.dmPrompt : (win.dmPassword ? Theme.iLock : Theme.iSearch) | |
| 399 | + | color: win.dmenu && win.dmPrompt !== "" ? Theme.accent : Theme.dim | |
| 400 | + | font.family: Theme.font | |
| 401 | + | font.pixelSize: Theme.fontSize | |
| 402 | + | } | |
| 403 | + | Text { | |
| 404 | + | anchors.left: filter.left | |
| 405 | + | anchors.verticalCenter: parent.verticalCenter | |
| 406 | + | visible: filter.text === "" && win.dmenu && win.dmPlaceholder !== "" | |
| 407 | + | text: win.dmPlaceholder | |
| 408 | + | color: Theme.dim | |
| 409 | + | font.family: Theme.font | |
| 410 | + | font.pixelSize: Theme.fontSize | |
| 411 | + | } | |
| 412 | + | TextInput { | |
| 413 | + | id: filter | |
| 414 | + | anchors.fill: parent | |
| 415 | + | anchors.leftMargin: Math.max(32, promptText.implicitWidth + 20) | |
| 416 | + | anchors.rightMargin: 10 | |
| 417 | + | verticalAlignment: TextInput.AlignVCenter | |
| 418 | + | echoMode: win.dmPassword ? TextInput.Password : TextInput.Normal | |
| 419 | + | passwordCharacter: "*" | |
| 420 | + | color: Theme.fg | |
| 421 | + | selectionColor: Theme.accent | |
| 422 | + | selectedTextColor: Theme.bg | |
| 423 | + | font.family: Theme.font | |
| 424 | + | font.pixelSize: Theme.fontSize | |
| 425 | + | clip: true | |
| 426 | + | onTextChanged: { win.current = 0; win.armed = "" } | |
| 427 | + | Keys.onEscapePressed: win.closeRequested() | |
| 428 | + | Keys.onReturnPressed: event => win.trigger(win.current, event.modifiers & Qt.ControlModifier) | |
| 429 | + | Keys.onEnterPressed: event => win.trigger(win.current, event.modifiers & Qt.ControlModifier) | |
| 430 | + | Keys.onPressed: event => { | |
| 431 | + | var n = win.shown.length | |
| 432 | + | if (!n) return | |
| 433 | + | var step = 0 | |
| 434 | + | if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) step = 1 | |
| 435 | + | else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) step = -1 | |
| 436 | + | else if (event.key === Qt.Key_PageDown) step = Math.max(2, win.rowsShown) | |
| 437 | + | else if (event.key === Qt.Key_PageUp) step = -Math.max(2, win.rowsShown) | |
| 438 | + | else if ((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_N || event.key === Qt.Key_J)) step = 1 | |
| 439 | + | else if ((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_P || event.key === Qt.Key_K)) step = -1 | |
| 440 | + | if (step === 0) return | |
| 441 | + | if (Math.abs(step) === 1) win.current = (win.current + step + n) % n | |
| 442 | + | else win.current = Math.max(0, Math.min(n - 1, win.current + step)) | |
| 443 | + | win.armed = "" | |
| 444 | + | event.accepted = true | |
| 445 | + | } | |
| 446 | + | } | |
| 447 | + | } | |
| 448 | + | ||
| 449 | + | ListView { | |
| 450 | + | id: list | |
| 451 | + | Layout.fillWidth: true | |
| 452 | + | Layout.fillHeight: true | |
| 453 | + | visible: win.rowsShown > 0 | |
| 454 | + | clip: true | |
| 455 | + | spacing: 0 | |
| 456 | + | boundsBehavior: Flickable.StopAtBounds | |
| 457 | + | model: win.shown | |
| 458 | + | currentIndex: win.current | |
| 459 | + | highlightFollowsCurrentItem: false | |
| 460 | + | onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain) | |
| 461 | + | delegate: Rectangle { | |
| 462 | + | id: row | |
| 463 | + | required property var modelData | |
| 464 | + | required property int index | |
| 465 | + | readonly property bool selected: win.current === index | |
| 466 | + | readonly property bool isArmed: win.armed !== "" && win.armed === modelData.key | |
| 467 | + | width: list.width | |
| 468 | + | height: win.rowH | |
| 469 | + | color: isArmed ? Theme.err : (selected ? Theme.surfaceHi : (rm.containsMouse ? Theme.surface : "transparent")) | |
| 470 | + | border.width: selected ? 1 : 0 | |
| 471 | + | border.color: isArmed ? Theme.err : Theme.accent | |
| 472 | + | ||
| 473 | + | Rectangle { // selection marker, like ClipWindow | |
| 474 | + | visible: row.selected | |
| 475 | + | width: 2 | |
| 476 | + | height: parent.height | |
| 477 | + | color: row.isArmed ? Theme.bg : Theme.accent | |
| 478 | + | } | |
| 479 | + | Item { | |
| 480 | + | id: icoBox | |
| 481 | + | anchors.left: parent.left | |
| 482 | + | anchors.leftMargin: row.modelData.kind === "dm" ? 2 : 10 | |
| 483 | + | anchors.verticalCenter: parent.verticalCenter | |
| 484 | + | visible: row.modelData.kind !== "dm" | |
| 485 | + | width: visible ? 24 : 0 | |
| 486 | + | height: 24 | |
| 487 | + | Image { | |
| 488 | + | id: img | |
| 489 | + | anchors.fill: parent | |
| 490 | + | visible: row.modelData.icon !== "" && status === Image.Ready | |
| 491 | + | source: row.modelData.icon | |
| 492 | + | sourceSize.width: 48 | |
| 493 | + | sourceSize.height: 48 | |
| 494 | + | asynchronous: true | |
| 495 | + | smooth: true | |
| 496 | + | } | |
| 497 | + | Text { | |
| 498 | + | anchors.centerIn: parent | |
| 499 | + | visible: !img.visible | |
| 500 | + | text: row.modelData.glyph | |
| 501 | + | color: row.isArmed ? Theme.bg : Theme.accent | |
| 502 | + | font.family: Theme.font | |
| 503 | + | font.pixelSize: Theme.fontSize + 4 | |
| 504 | + | } | |
| 505 | + | } | |
| 506 | + | Text { | |
| 507 | + | id: tag | |
| 508 | + | anchors.right: parent.right | |
| 509 | + | anchors.rightMargin: 10 | |
| 510 | + | anchors.verticalCenter: parent.verticalCenter | |
| 511 | + | text: row.isArmed ? "sure?" : row.modelData.tag | |
| 512 | + | color: row.isArmed ? Theme.bg : Theme.dim | |
| 513 | + | font.family: Theme.font | |
| 514 | + | font.pixelSize: Theme.fontSize - 2 | |
| 515 | + | } | |
| 516 | + | Text { | |
| 517 | + | id: nameText | |
| 518 | + | anchors.left: icoBox.right | |
| 519 | + | anchors.leftMargin: 10 | |
| 520 | + | anchors.verticalCenter: parent.verticalCenter | |
| 521 | + | width: Math.min(implicitWidth, tag.x - x - 12) | |
| 522 | + | elide: Text.ElideRight | |
| 523 | + | text: row.modelData.name | |
| 524 | + | color: row.isArmed ? Theme.bg : Theme.fg | |
| 525 | + | font.family: Theme.font | |
| 526 | + | font.pixelSize: Theme.fontSize | |
| 527 | + | font.bold: row.modelData.kind === "calc" | |
| 528 | + | } | |
| 529 | + | Text { | |
| 530 | + | anchors.left: nameText.right | |
| 531 | + | anchors.leftMargin: 10 | |
| 532 | + | anchors.right: tag.left | |
| 533 | + | anchors.rightMargin: 12 | |
| 534 | + | anchors.verticalCenter: parent.verticalCenter | |
| 535 | + | elide: Text.ElideRight | |
| 536 | + | text: row.modelData.sub | |
| 537 | + | color: row.isArmed ? Theme.bg : Theme.dim | |
| 538 | + | font.family: Theme.font | |
| 539 | + | font.pixelSize: Theme.fontSize - 2 | |
| 540 | + | } | |
| 541 | + | MouseArea { | |
| 542 | + | id: rm | |
| 543 | + | anchors.fill: parent | |
| 544 | + | hoverEnabled: true | |
| 545 | + | cursorShape: Qt.PointingHandCursor | |
| 546 | + | onEntered: if (win.current !== row.index) { win.current = row.index; win.armed = "" } | |
| 547 | + | onClicked: win.trigger(row.index, false) | |
| 548 | + | } | |
| 549 | + | } | |
| 550 | + | } | |
| 551 | + | ||
| 552 | + | Text { | |
| 553 | + | Layout.fillWidth: true | |
| 554 | + | Layout.topMargin: 6 | |
| 555 | + | text: !win.dmenu ? (win.shown.length ? "enter run ctrl+enter terminal =calc >cmd esc close" : "no match") | |
| 556 | + | : win.rowsShown === 0 ? "enter ok esc cancel" | |
| 557 | + | : win.shown.length ? "enter select ctrl+enter typed text esc cancel" : "no match enter typed text esc cancel" | |
| 558 | + | color: win.shown.length || win.dmenu ? Theme.dim : Theme.err | |
| 559 | + | font.family: Theme.font | |
| 560 | + | font.pixelSize: Theme.fontSize - 2 | |
| 561 | + | } | |
| 562 | + | } | |
| 563 | + | } | |
| 564 | + | } | |
| @@ -0,0 +1,35 @@ | |||
| 1 | + | pragma Singleton | |
| 2 | + | import QtQuick | |
| 3 | + | import Quickshell | |
| 4 | + | ||
| 5 | + | // Session / power actions shared by PowerMenu and Runner. `confirm` ones need the two-press "sure?" arm. | |
| 6 | + | Singleton { | |
| 7 | + | readonly property var actions: [ | |
| 8 | + | { id: "lock", icon: Theme.iLock, label: "lock", confirm: false }, | |
| 9 | + | { id: "leave", icon: Theme.iLogout, label: "leave Hyprland", confirm: true }, | |
| 10 | + | { id: "renew", icon: Theme.iRefresh, label: "renew Hyprland", confirm: false }, | |
| 11 | + | { id: "hibernate", icon: Theme.iHibernate, label: "hibernate", confirm: true }, | |
| 12 | + | { id: "reboot", icon: Theme.iReboot, label: "reboot", confirm: true }, | |
| 13 | + | { id: "shutdown", icon: Theme.iPower, label: "shutdown", confirm: true }, | |
| 14 | + | { id: "sleep", icon: Theme.iSleep, label: "sleep", confirm: false }, | |
| 15 | + | { id: "display", icon: Theme.iMonitor, label: "display off", confirm: false } | |
| 16 | + | ] | |
| 17 | + | ||
| 18 | + | function run(id) { | |
| 19 | + | switch (id) { | |
| 20 | + | case "lock": Quickshell.execDetached(["loginctl", "lock-session"]); break | |
| 21 | + | case "leave": Hypr.logout(); break | |
| 22 | + | case "renew": Quickshell.execDetached(["hyprctl", "reload"]); break | |
| 23 | + | case "hibernate": Quickshell.execDetached(["systemctl", "hibernate", "-i"]); break | |
| 24 | + | case "reboot": Quickshell.execDetached(["systemctl", "reboot", "-i"]); break | |
| 25 | + | case "shutdown": Quickshell.execDetached(["systemctl", "poweroff", "-i"]); break | |
| 26 | + | case "sleep": Quickshell.execDetached(["systemctl", "suspend", "-i"]); break | |
| 27 | + | case "display": dpmsDelay.restart(); break // let the Enter key-up pass first, it would wake the screen | |
| 28 | + | } | |
| 29 | + | } | |
| 30 | + | Timer { | |
| 31 | + | id: dpmsDelay | |
| 32 | + | interval: 400 | |
| 33 | + | onTriggered: Quickshell.execDetached(["hyprctl", "dispatch", 'hl.dsp.dpms({action="off"})']) | |
| 34 | + | } | |
| 35 | + | } | |
| @@ -159,6 +159,11 @@ Singleton { | |||
| 159 | 159 | readonly property string iStop: cp(0xF04DB) | |
| 160 | 160 | readonly property string iDock: cp(0xF10A9) | |
| 161 | 161 | readonly property string iCog: cp(0xF0493) | |
| 162 | + | readonly property string iApps: cp(0xF003B) | |
| 163 | + | readonly property string iApp: cp(0xF08C6) | |
| 164 | + | readonly property string iConsole: cp(0xF018D) | |
| 165 | + | readonly property string iShell: cp(0xF07B7) | |
| 166 | + | readonly property string iCalc: cp(0xF00EC) | |
| 162 | 167 | ||
| 163 | 168 | // workspace icons: the exact Font Awesome glyphs from your waybar config. | |
| 164 | 169 | // 1 browser, 2 code, 3 book, 4 cog, 5 chat, 6 file, 7 folder, 8 terminal, 9 flask, 10 image | |
| @@ -131,6 +131,20 @@ ShellRoot { | |||
| 131 | 131 | onCloseRequested: root.closeOverlay() | |
| 132 | 132 | } | |
| 133 | 133 | ||
| 134 | + | Runner { | |
| 135 | + | id: runner | |
| 136 | + | screen: root.overlayScreen | |
| 137 | + | opened: root.overlay === "runner" | |
| 138 | + | shell: root | |
| 139 | + | onCloseRequested: root.closeOverlay() | |
| 140 | + | onOpenRequested: { | |
| 141 | + | root.close() | |
| 142 | + | if (root.overlay === "runner") return // already open (run mode): it just switched to the list | |
| 143 | + | root.overlayScreen = root.focusedScreen() | |
| 144 | + | root.overlay = "runner" | |
| 145 | + | } | |
| 146 | + | } | |
| 147 | + | ||
| 134 | 148 | NotifPopups { screen: root.focusedScr; suppressed: root.opened } | |
| 135 | 149 | ||
| 136 | 150 | Osd { id: osd; screen: root.focusedScr; suppressed: root.opened } | |
| @@ -160,6 +174,8 @@ ShellRoot { | |||
| 160 | 174 | function clip(): void { root.toggleOverlay("clip") } | |
| 161 | 175 | function wall(): void { root.toggleOverlay("wall") } | |
| 162 | 176 | function power(): void { root.toggleOverlay("power") } | |
| 177 | + | function run(): void { root.toggleOverlay("runner") } | |
| 178 | + | function dmenu(dir: string): void { runner.startDmenu(dir) } // used by ~/.local/bin/aurora-run -d | |
| 163 | 179 | function bar(): void { root.barShown = !root.barShown } | |
| 164 | 180 | function wallrandom(): void { Wall.random() } | |
| 165 | 181 | function wallset(path: string): void { Wall.apply(path) } // absolute path (setwall resolves it) | |
| @@ -33,7 +33,7 @@ export MYSQL_HISTFILE="$XDG_CACHE_HOME/.mariadb_history" | |||
| 33 | 33 | export CUDA_CACHE_PATH="$XDG_CACHE_HOME/nv" | |
| 34 | 34 | ||
| 35 | 35 | # Other program settings | |
| 36 | − | export SUDO_ASKPASS="$HOME/.local/bin/askpass-fuzzel" | |
| 36 | + | export SUDO_ASKPASS="$HOME/.local/bin/aurora-askpass" | |
| 37 | 37 | export FZF_DEFAULT_OPTS="--layout=reverse --height 40%" | |
| 38 | 38 | export ANDROID_SDK_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/android" | |
| 39 | 39 | export ANDROID_HOME="$HOME/Android/Sdk/" | |
| @@ -1,11 +0,0 @@ | |||
| 1 | − | [colors] | |
| 2 | − | background={background.strip}ff | |
| 3 | − | prompt={foreground.strip}ff | |
| 4 | − | text={foreground.strip}ff | |
| 5 | − | placeholder={foreground.strip}ff | |
| 6 | − | input={foreground.strip}ff | |
| 7 | − | match={foreground.strip}ff | |
| 8 | − | selection={color1.strip}ff | |
| 9 | − | selection-text={foreground.strip}ff | |
| 10 | − | selection-match={background.strip}ff | |
| 11 | − | border={color1.strip}ff | |
| @@ -1,12 +0,0 @@ | |||
| 1 | − | #!/bin/bash | |
| 2 | − | ||
| 3 | − | prompt="${1:-Password:}" | |
| 4 | − | ||
| 5 | − | password=$(fuzzel --dmenu --password --prompt="" --placeholder="$prompt" --width=60 --lines=0 --keyboard-focus=on-demand --no-exit-on-keyboard-focus-loss --cache /dev/null) | |
| 6 | − | ||
| 7 | − | if [ $? -eq 0 ]; then | |
| 8 | − | echo "$password" | |
| 9 | − | exit 0 | |
| 10 | − | else | |
| 11 | − | exit 1 | |
| 12 | − | fi | |
| @@ -0,0 +1,13 @@ | |||
| 1 | + | #!/bin/bash | |
| 2 | + | # SUDO_ASKPASS helper: password prompt in aurora's runner. | |
| 3 | + | ||
| 4 | + | prompt="${1:-Password:}" | |
| 5 | + | ||
| 6 | + | password=$(aurora-run --dmenu --password --prompt="" --placeholder="$prompt" --lines=0) | |
| 7 | + | ||
| 8 | + | if [ $? -eq 0 ]; then | |
| 9 | + | echo "$password" | |
| 10 | + | exit 0 | |
| 11 | + | else | |
| 12 | + | exit 1 | |
| 13 | + | fi | |
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | # The famous "get a menu of emojis to copy" script. | |
| 4 | 4 | ||
| 5 | 5 | # Get user selection via dmenu from emoji file. | |
| 6 | − | chosen=$(cut -d ';' -f1 ~/.local/share/chars/* | fuzzel -d -i -l 30 | sed "s/ .*//") | |
| 6 | + | chosen=$(cut -d ';' -f1 ~/.local/share/chars/* | aurora-run -d -l 15 -p "char" | sed "s/ .*//") | |
| 7 | 7 | ||
| 8 | 8 | # Exit if none chosen. | |
| 9 | 9 | [ -z "$chosen" ] && exit | |
| @@ -0,0 +1,51 @@ | |||
| 1 | + | #!/bin/bash | |
| 2 | + | # aurora-run: fuzzel-compatible front end for aurora's runner (needs aurora running). | |
| 3 | + | # aurora-run open the app runner (same as SUPER+R) | |
| 4 | + | # aurora-run -d [opts] dmenu: lines on stdin, the choice on stdout, exit 1 when cancelled | |
| 5 | + | # dmenu opts (fuzzel/dmenu spelling): -p/--prompt TEXT, -l/--lines N (0 = input only), --placeholder TEXT, | |
| 6 | + | # --password, --index (print the line number instead). Other fuzzel flags (-i, --width, -fn, ...) are ignored. | |
| 7 | + | ||
| 8 | + | dmenu=0 prompt="" placeholder="" lines="" password=0 index=0 | |
| 9 | + | while [ $# -gt 0 ]; do | |
| 10 | + | case $1 in | |
| 11 | + | -d | --dmenu) dmenu=1 ;; | |
| 12 | + | -p | --prompt) prompt=$2; shift ;; | |
| 13 | + | --prompt=*) prompt=${1#*=} ;; | |
| 14 | + | -l | --lines) lines=$2; shift ;; | |
| 15 | + | --lines=*) lines=${1#*=} ;; | |
| 16 | + | --placeholder) placeholder=$2; shift ;; | |
| 17 | + | --placeholder=*) placeholder=${1#*=} ;; | |
| 18 | + | --password | --password=*) password=1 ;; | |
| 19 | + | --index) index=1 ;; | |
| 20 | + | # ignored flags that take a separate value | |
| 21 | + | -fn | -f | --font | -w | --width | --cache | --with-nth | --nth-delimiter | -a | --anchor | -o | --output) shift ;; | |
| 22 | + | *) ;; | |
| 23 | + | esac | |
| 24 | + | shift | |
| 25 | + | done | |
| 26 | + | ||
| 27 | + | pid=$(pgrep -nf 'quickshell.*/aurora$|qs -c aurora$') | |
| 28 | + | [ -n "$pid" ] || { echo "aurora-run: aurora is not running" >&2; exit 2; } | |
| 29 | + | ||
| 30 | + | if [ "$dmenu" = 0 ]; then | |
| 31 | + | exec qs -c aurora ipc call shell run | |
| 32 | + | fi | |
| 33 | + | ||
| 34 | + | # private dir on tmpfs: items, opts and the reply fifo (a password only ever passes through the fifo) | |
| 35 | + | dir=$(mktemp -d "${XDG_RUNTIME_DIR:-/tmp}/aurora-run.XXXXXX") || exit 2 | |
| 36 | + | trap 'rm -rf "$dir"' EXIT | |
| 37 | + | [ -t 0 ] || cat > "$dir/items" | |
| 38 | + | printf 'prompt=%s\nplaceholder=%s\nlines=%s\npassword=%s\nindex=%s\n' \ | |
| 39 | + | "${prompt//$'\n'/ }" "${placeholder//$'\n'/ }" "$lines" "$password" "$index" > "$dir/opts" | |
| 40 | + | mkfifo "$dir/reply" | |
| 41 | + | exec 3<> "$dir/reply" # read-write: never blocks aurora's writer, and lets us time out below | |
| 42 | + | ||
| 43 | + | err=$(qs -c aurora ipc call shell dmenu "$dir" 2>&1) | |
| 44 | + | [ -z "$err" ] || { echo "aurora-run: $err" >&2; exit 2; } | |
| 45 | + | ||
| 46 | + | # reply is "<0|1>\n<value>\0"; give up if aurora goes away while we wait | |
| 47 | + | until IFS= read -r -d '' -t 1 -u 3 reply; do | |
| 48 | + | kill -0 "$pid" 2>/dev/null || { echo "aurora-run: aurora exited" >&2; exit 2; } | |
| 49 | + | done | |
| 50 | + | [ "${reply%%$'\n'*}" = 0 ] || exit 1 | |
| 51 | + | printf '%s\n' "${reply#*$'\n'}" | |
| @@ -15,7 +15,7 @@ else | |||
| 15 | 15 | names=$(printf '%s\n' "${devices[@]}" | sed 's/^Device [^ ]* //') | |
| 16 | 16 | ||
| 17 | 17 | selected=$(printf '%s\n' "$names" | | |
| 18 | − | fuzzel --dmenu -l 10 -fn Monospace-15) | |
| 18 | + | aurora-run --dmenu -l 10) | |
| 19 | 19 | ||
| 20 | 20 | [[ -z "$selected" ]] && exit 0 | |
| 21 | 21 | ||
| @@ -68,7 +68,7 @@ else | |||
| 68 | 68 | fi | |
| 69 | 69 | ||
| 70 | 70 | # --- pick a browser --------------------------------------------------------- | |
| 71 | − | # $BROWSER is often unset when launched from fuzzel/rofi, so fall back. | |
| 71 | + | # $BROWSER is often unset when launched from a menu (aurora-run, rofi), so fall back. | |
| 72 | 72 | launch=() | |
| 73 | 73 | if [[ -n "${BROWSER:-}" ]]; then | |
| 74 | 74 | read -ra launch <<< "$BROWSER" | |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | #!/bin/env bash | |
| 2 | 2 | ||
| 3 | − | mode="$(printf "Integrated\nHybrid\nNvidia" | fuzzel --dmenu -p "Select the graphics mode:")" | |
| 3 | + | mode="$(printf "Integrated\nHybrid\nNvidia" | aurora-run --dmenu -p "Select the graphics mode:")" | |
| 4 | 4 | ||
| 5 | 5 | # Exit if no selection was made | |
| 6 | 6 | [ -z "$mode" ] && exit 1 | |
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | ocr_cmd="wl-copy" | |
| 4 | 4 | ||
| 5 | − | case "$(printf "a selected area (copy)\ncurrent window (copy)\nfull screen (copy)\na selected area\na selected area (tmp dir)\ncurrent window\nfull screen\na selected area (OCR)" | fuzzel -d -l 7 -i -p "Screenshot which area?")" in | |
| 5 | + | case "$(printf "a selected area (copy)\ncurrent window (copy)\nfull screen (copy)\na selected area\na selected area (tmp dir)\ncurrent window\nfull screen\na selected area (OCR)" | aurora-run -d -l 8 -p "Screenshot which area?")" in | |
| 6 | 6 | "a selected area (copy)") hyprshot -m region --clipboard-only ;; | |
| 7 | 7 | "current window (copy)") hyprshot -m window --clipboard-only ;; | |
| 8 | 8 | "full screen (copy)") hyprshot -m output --clipboard-only ;; | |
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | set -euo pipefail | |
| 3 | 3 | ||
| 4 | 4 | # Check deps | |
| 5 | − | command -v fuzzel >/dev/null || { echo "fuzzel not found"; exit 1; } | |
| 5 | + | command -v aurora-run >/dev/null || { echo "aurora-run not found"; exit 1; } | |
| 6 | 6 | command -v hyprshade >/dev/null || { echo "hyprshade not found"; exit 1; } | |
| 7 | 7 | ||
| 8 | 8 | # Get shader list | |
| @@ -11,9 +11,9 @@ mapfile -t SHADERS < <(hyprshade ls) | |||
| 11 | 11 | # Add "off" at top | |
| 12 | 12 | SHADERS=("off" "${SHADERS[@]}") | |
| 13 | 13 | ||
| 14 | − | # Show in fuzzel | |
| 14 | + | # Show in aurora's runner (|| true: cancelling must not trip set -e) | |
| 15 | 15 | CHOICE=$(printf "%s\n" "${SHADERS[@]}" \ | |
| 16 | − | | fuzzel --dmenu --prompt "Hyprshade > " \ | |
| 16 | + | | { aurora-run --dmenu --prompt "Hyprshade > " || true; } \ | |
| 17 | 17 | | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
| 18 | 18 | ||
| 19 | 19 | # User cancelled | |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | #!/bin/sh | |
| 2 | 2 | # mpd-add: fuzzy-pick a track from the MPD library and play it right away. | |
| 3 | − | # Works in a terminal (fzf) or launched from a menu (fuzzel). | |
| 3 | + | # Works in a terminal (fzf) or launched from a menu (aurora-run). | |
| 4 | 4 | # | |
| 5 | 5 | # Usage: mpd-add [-q] | |
| 6 | 6 | # -q queue the track right after the current one, but don't jump to it | |
| @@ -34,7 +34,7 @@ fi | |||
| 34 | 34 | if [ -t 0 ]; then | |
| 35 | 35 | filename=$(printf '%s\n' "$library" | fzf) | |
| 36 | 36 | else | |
| 37 | − | filename=$(printf '%s\n' "$library" | fuzzel -d -l 30 -p 'play: ') | |
| 37 | + | filename=$(printf '%s\n' "$library" | aurora-run -d -l 30 -p 'play: ') | |
| 38 | 38 | fi | |
| 39 | 39 | ||
| 40 | 40 | # Cancelled (Esc): nothing to do. | |
| @@ -8,16 +8,16 @@ | |||
| 8 | 8 | getmount() { \ | |
| 9 | 9 | [ -z "$chosen" ] && exit 1 | |
| 10 | 10 | # shellcheck disable=SC2086 | |
| 11 | − | mp="$(find $1 2>/dev/null | fuzzel -d -i -p "Type in mount point.")" || exit 1 | |
| 11 | + | mp="$(find $1 2>/dev/null | aurora-run -d -p "Type in mount point.")" || exit 1 | |
| 12 | 12 | test -z "$mp" && exit 1 | |
| 13 | 13 | if [ ! -d "$mp" ]; then | |
| 14 | − | mkdiryn=$(printf "No\\nYes" | fuzzel -d -i -p "$mp does not exist. Create it?") || exit 1 | |
| 14 | + | mkdiryn=$(printf "No\\nYes" | aurora-run -d -p "$mp does not exist. Create it?") || exit 1 | |
| 15 | 15 | [ "$mkdiryn" = "Yes" ] && (mkdir -p "$mp" || sudo -A mkdir -p "$mp") | |
| 16 | 16 | fi | |
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | 19 | mountusb() { \ | |
| 20 | − | chosen="$(echo "$usbdrives" | fuzzel -d -i -p "Mount which drive?")" || exit 1 | |
| 20 | + | chosen="$(echo "$usbdrives" | aurora-run -d -p "Mount which drive?")" || exit 1 | |
| 21 | 21 | chosen="$(echo "$chosen" | awk '{print $1}')" | |
| 22 | 22 | sudo -A mount "$chosen" 2>/dev/null && notify-send "💻 USB mounting" "$chosen mounted." && exit 0 | |
| 23 | 23 | alreadymounted=$(lsblk -nrpo "name,type,mountpoint" | awk '$3!~/\/boot|\/home$|SWAP/&&length($3)>1{printf "-not ( -path *%s -prune ) ",$3}') | |
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | if [ -t 0 ]; then | |
| 5 | 5 | filename="$(find ~/remote/movies/ -type f -regex ".*\.\(mkv\|mp4\|mpeg\|avi\|mov\|webm\)" -printf "%f\n" | sort | fzf)" | |
| 6 | 6 | else | |
| 7 | − | filename="$(find ~/remote/movies/ -type f -regex ".*\.\(mkv\|mp4\|mpeg\|avi\|mov\|webm\)" -printf "%f\n" | sort | fuzzel -d -l 25)" | |
| 7 | + | filename="$(find ~/remote/movies/ -type f -regex ".*\.\(mkv\|mp4\|mpeg\|avi\|mov\|webm\)" -printf "%f\n" | sort | aurora-run -d -l 25)" | |
| 8 | 8 | fi | |
| 9 | 9 | ||
| 10 | 10 | filepath="$(find ~/remote/movies/ -type f -regex ".*\.\(mkv\|mp4\|mpeg\|avi\|mov\|webm\)")" | |
| @@ -6,7 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | 7 | unmountusb() { | |
| 8 | 8 | [ -z "$drives" ] && exit | |
| 9 | − | chosen="$(echo "$drives" | fuzzel -d -i -p "Unmount which drive?")" || exit 1 | |
| 9 | + | chosen="$(echo "$drives" | aurora-run -d -p "Unmount which drive?")" || exit 1 | |
| 10 | 10 | chosen="$(echo "$chosen" | awk '{print $1}')" | |
| 11 | 11 | [ -z "$chosen" ] && exit | |
| 12 | 12 | sudo -A umount "$chosen" && notify-send "💻 USB unmounting" "$chosen unmounted." | |
| @@ -34,7 +34,7 @@ atleast=1920x1080 | |||
| 34 | 34 | # the menu command used when no query is provided | |
| 35 | 35 | sh_menu () { | |
| 36 | 36 | # : | dmenu -p "search wallhaven: " | |
| 37 | − | fuzzel --lines=0 --dmenu -p "search wallhaven: " | |
| 37 | + | aurora-run --lines=0 --dmenu -p "search wallhaven: " | |
| 38 | 38 | # ROFI: comment the previous line and uncomment the next line for rofi | |
| 39 | 39 | # rofi -dmenu -l 0 -p "search wallpapers" | |
| 40 | 40 | } | |
| @@ -22,7 +22,7 @@ You don't need the installer for that. Browse the repo and copy what you like, f | |||
| 22 | 22 | - `.config/quickshell/aurora/`: the shell (needs Quickshell; it reads its colours from `~/.cache/wal/colors.json`) | |
| 23 | 23 | - `.config/hypr/`: Hyprland in the new Lua config format | |
| 24 | 24 | - `.local/bin/`: small scripts (`setwall`, `brictl`, `sysact`, `tms`, ...) | |
| 25 | − | - `.config/wal/templates/`: pywal16 templates for Hyprland, fuzzel, zathura, cava | |
| 25 | + | - `.config/wal/templates/`: pywal16 templates for Hyprland, zathura, cava | |
| 26 | 26 | ||
| 27 | 27 | ## Installing (fresh Arch only) | |
| 28 | 28 | ```bash | |
| @@ -67,7 +67,7 @@ The lists live in `packages/*.txt`: one package per line, `#` for comments, and | |||
| 67 | 67 | | Theming | pywal16: picking a wallpaper in aurora (or `setwall`) re-themes everything | | |
| 68 | 68 | | Terminal / shell | foot, zsh | | |
| 69 | 69 | | Editor | Neovim (LazyVim) | | |
| 70 | − | | Launcher / menus | fuzzel | | |
| 70 | + | | Launcher / menus | aurora's runner (`aurora-run`, also a dmenu replacement for scripts) | | |
| 71 | 71 | | Files | lf, pcmanfm | | |
| 72 | 72 | | Music / video | mpd + ncmpcpp, mpv | | |
| 73 | 73 | | Font / icons | JetBrainsMono Nerd Font, Yaru | | |
| @@ -33,7 +33,6 @@ wireplumber | |||
| 33 | 33 | ||
| 34 | 34 | # Apps the configs/keybinds assume | |
| 35 | 35 | foot | |
| 36 | − | fuzzel | |
| 37 | 36 | kanshi | |
| 38 | 37 | nsxiv | |
| 39 | 38 | xdg-utils | |