max / alloy
- Co-Authored-By
- Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 files changed,
+130 insertions,
-13 deletions
| @@ -18,7 +18,12 @@ | |||
| 18 | 18 | # per-machine output in /etc/sway/config.d/. | |
| 19 | 19 | ||
| 20 | 20 | set $mod Mod4 | |
| 21 | - | set $term rio | |
| 21 | + | # rioterm, not rio. Fedora and Terra package the terminal as rio but install | |
| 22 | + | # its binary as /usr/bin/rioterm; there is no `rio` on the image. With no | |
| 23 | + | # launcher bound below, $mod+Return is the only way to reach a terminal from | |
| 24 | + | # the session, so this name being wrong left an installed machine with no way | |
| 25 | + | # to open one at all, and no way to edit this file to fix it, short of a VT. | |
| 26 | + | set $term rioterm | |
| 22 | 27 | ||
| 23 | 28 | # ------------------------------------------------------------------- | |
| 24 | 29 | # Input | |
| @@ -55,7 +60,7 @@ | |||
| 55 | 60 | # mode 1920x1200@60Hz | |
| 56 | 61 | # scale 1.0 | |
| 57 | 62 | # } | |
| 58 | - | output * bg #f7f4ec solid_color | |
| 63 | + | output * bg #e4ded6 solid_color | |
| 59 | 64 | include /etc/sway/config.d/* | |
| 60 | 65 | ||
| 61 | 66 | # ------------------------------------------------------------------- | |
| @@ -70,13 +75,16 @@ | |||
| 70 | 75 | font pango:IosevkaTerm Nerd Font 10 | |
| 71 | 76 | ||
| 72 | 77 | # class border background text indicator child_border | |
| 73 | - | client.focused #7f786d #7f786d #1a1a1a #7f786d #7f786d | |
| 74 | - | client.focused_inactive #dad2c7 #dad2c7 #6b6b6b #dad2c7 #dad2c7 | |
| 75 | - | client.unfocused #dad2c7 #dad2c7 #6b6b6b #dad2c7 #dad2c7 | |
| 76 | - | client.urgent #b3543f #b3543f #ffffff #b3543f #b3543f | |
| 78 | + | client.focused #7f786d #7f786d #1a1816 #7f786d #7f786d | |
| 79 | + | client.focused_inactive #dad2c7 #dad2c7 #514b45 #dad2c7 #dad2c7 | |
| 80 | + | client.unfocused #dad2c7 #dad2c7 #514b45 #dad2c7 #dad2c7 | |
| 81 | + | client.urgent #6a2828 #6a2828 #ffffff #6a2828 #6a2828 | |
| 77 | 82 | ||
| 78 | 83 | # satty floats; everything else tiles (anyrun rule dropped with anyrun). | |
| 79 | 84 | for_window [app_id="satty"] floating enable | |
| 85 | + | # The picker floats centered rather than tiling: it is a transient prompt, and | |
| 86 | + | # tiling it would reflow every window on screen for as long as it is open. | |
| 87 | + | for_window [app_id="alloy-menu"] floating enable, resize set 800 500 | |
| 80 | 88 | ||
| 81 | 89 | # ------------------------------------------------------------------- | |
| 82 | 90 | # Startup | |
| @@ -112,10 +120,19 @@ | |||
| 112 | 120 | # No multiplexer bind. Tabs, splits and panes are sway's job, and a | |
| 113 | 121 | # multiplexer inside a tiling compositor is the same feature twice; see | |
| 114 | 122 | # docs/STACK.md#terminal. | |
| 115 | - | # Launcher removed with anyrun. Bind a TUI launcher here when chosen, | |
| 116 | - | # e.g. bindsym $mod+d exec $term -e <picker> | |
| 117 | - | # bindsym $mod+d exec ... | |
| 123 | + | # Launcher: a fuzzy picker in a terminal, not a graphical surface | |
| 124 | + | # (docs/STACK.md#launcher). $mod+Return is for typing commands; this is for | |
| 125 | + | # the graphical apps whose binary name nobody remembers. | |
| 126 | + | bindsym $mod+d exec $term --app-id=alloy-menu -e /usr/bin/alloy-menu | |
| 127 | + | # Clipboard history, read half. The two wl-paste watchers above have filled it | |
| 128 | + | # since the stack landed; this is what gets anything back out. | |
| 129 | + | bindsym $mod+Shift+v exec $term --app-id=alloy-menu -e /usr/bin/alloy-clipmenu | |
| 118 | 130 | bindsym $mod+Ctrl+l exec swaylock | |
| 131 | + | # Reload. Sway's own default, and absent here until now: without it the only | |
| 132 | + | # way to apply a config edit is `swaymsg reload` from a terminal, which is | |
| 133 | + | # circular in the one case that matters: a config that broke the terminal | |
| 134 | + | # bind. That is not hypothetical; it is how this file's $term was found wrong. | |
| 135 | + | bindsym $mod+Shift+c reload | |
| 119 | 136 | bindsym $mod+Shift+e exit | |
| 120 | 137 | ||
| 121 | 138 | # Window ops | |
| @@ -210,9 +227,9 @@ | |||
| 210 | 227 | font pango:IosevkaTerm Nerd Font 10 | |
| 211 | 228 | status_command while date +'%Y-%m-%d %H:%M'; do sleep 20; done | |
| 212 | 229 | colors { | |
| 213 | - | background #f7f4ec | |
| 214 | - | statusline #1a1a1a | |
| 215 | - | focused_workspace #7f786d #7f786d #1a1a1a | |
| 216 | - | inactive_workspace #dad2c7 #dad2c7 #6b6b6b | |
| 230 | + | background #e4ded6 | |
| 231 | + | statusline #1a1816 | |
| 232 | + | focused_workspace #7f786d #7f786d #1a1816 | |
| 233 | + | inactive_workspace #dad2c7 #dad2c7 #514b45 | |
| 217 | 234 | } | |
| 218 | 235 | } |
| @@ -1,0 +1,20 @@ | |||
| 1 | + | #!/bin/sh | |
| 2 | + | # alloy-clipmenu — pick something out of the clipboard history and copy it back. | |
| 3 | + | # | |
| 4 | + | # The read half of docs/STACK.md#clipboard-history. The sway config has run two | |
| 5 | + | # `wl-paste --watch cliphist store` watchers since the stack landed, so the | |
| 6 | + | # history was being filled from the first login and there was no way to get | |
| 7 | + | # anything out of it: no bind, no picker, no verb. STACK.md named this exact | |
| 8 | + | # pipeline ("cliphist list piped to a TUI picker") and it was never wired. | |
| 9 | + | # | |
| 10 | + | # cliphist list emits `<id>\t<preview>`; decode takes the id back to the real | |
| 11 | + | # bytes, which is what makes this work for images and for text with newlines. | |
| 12 | + | ||
| 13 | + | set -eu | |
| 14 | + | ||
| 15 | + | cliphist list | fzf \ | |
| 16 | + | --delimiter='\t' --with-nth=2.. \ | |
| 17 | + | --prompt='paste ' --pointer='>' --marker=' ' \ | |
| 18 | + | --layout=reverse --no-multi --no-sort --height=100% --border=none --info=inline \ | |
| 19 | + | | cliphist decode \ | |
| 20 | + | | wl-copy |
| @@ -1,0 +1,80 @@ | |||
| 1 | + | #!/bin/sh | |
| 2 | + | # alloy-menu — pick an installed application and run it. | |
| 3 | + | # | |
| 4 | + | # The launcher for a terminal-first system: a fuzzy picker in a terminal, not a | |
| 5 | + | # graphical surface. docs/STACK.md#launcher rejects graphical launchers on | |
| 6 | + | # thesis and names this shape; this is that. | |
| 7 | + | # | |
| 8 | + | # The split with $mod+Return is the whole design. Commands are typed in a | |
| 9 | + | # shell, which is one keystroke away. This exists for the other case -- the | |
| 10 | + | # graphical apps that have a .desktop file and an icon and a name nobody | |
| 11 | + | # remembers the binary for. So it lists desktop entries and nothing else; | |
| 12 | + | # adding every binary on PATH would bury Firefox under coreutils. | |
| 13 | + | # | |
| 14 | + | # Launched apps are handed to sway rather than run as children of this script, | |
| 15 | + | # so the terminal hosting the picker can close immediately and the app is not | |
| 16 | + | # killed with it. | |
| 17 | + | ||
| 18 | + | set -eu | |
| 19 | + | ||
| 20 | + | FIELD_CODES=' ?%[fFuUickdDnNvm]' | |
| 21 | + | ||
| 22 | + | dirs="/usr/share/applications | |
| 23 | + | ${XDG_DATA_HOME:-$HOME/.local/share}/applications | |
| 24 | + | /var/lib/flatpak/exports/share/applications | |
| 25 | + | ${XDG_DATA_HOME:-$HOME/.local/share}/flatpak/exports/share/applications" | |
| 26 | + | ||
| 27 | + | entries() { | |
| 28 | + | printf '%s\n' "$dirs" | while IFS= read -r dir; do | |
| 29 | + | [ -d "$dir" ] || continue | |
| 30 | + | find "$dir" -maxdepth 2 -name '*.desktop' 2>/dev/null | |
| 31 | + | done | while IFS= read -r file; do | |
| 32 | + | # Only the [Desktop Entry] group. Actions ([Desktop Action ...]) carry | |
| 33 | + | # their own Name/Exec and would otherwise be read as separate apps. | |
| 34 | + | awk -v codes="$FIELD_CODES" ' | |
| 35 | + | /^\[Desktop Entry\]/ { group = 1; next } | |
| 36 | + | /^\[/ { group = 0 } | |
| 37 | + | !group { next } | |
| 38 | + | /^Name=/ && name == "" { name = substr($0, 6) } | |
| 39 | + | /^Exec=/ && cmd == "" { cmd = substr($0, 6) } | |
| 40 | + | /^Type=/ { type = substr($0, 6) } | |
| 41 | + | /^NoDisplay=[Tt]rue/ { hide = 1 } | |
| 42 | + | /^Hidden=[Tt]rue/ { hide = 1 } | |
| 43 | + | # Terminal=true means the entry is a TUI and has no window of its own. | |
| 44 | + | # On a TUI-first system this is not the rare case: helix, yazi and | |
| 45 | + | # gammastep all ship one. Launched without a terminal they exit | |
| 46 | + | # immediately with no output, which reads as the launcher being broken. | |
| 47 | + | /^Terminal=[Tt]rue/ { needs_term = 1 } | |
| 48 | + | END { | |
| 49 | + | if (hide || type != "Application" || name == "" || cmd == "") exit | |
| 50 | + | gsub(codes, "", cmd) | |
| 51 | + | printf "%s\t%d\t%s\n", name, needs_term, cmd | |
| 52 | + | } | |
| 53 | + | ' "$file" | |
| 54 | + | done | sort -u -k1,1 -t "$(printf '\t')" | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | choice=$(entries | fzf \ | |
| 58 | + | --delimiter='\t' --with-nth=1 \ | |
| 59 | + | --prompt='run ' --pointer='>' --marker=' ' \ | |
| 60 | + | --layout=reverse --no-multi --height=100% --border=none --info=inline) || exit 0 | |
| 61 | + | ||
| 62 | + | [ -n "$choice" ] || exit 0 | |
| 63 | + | needs_term=$(printf '%s' "$choice" | cut -f2) | |
| 64 | + | cmd=$(printf '%s' "$choice" | cut -f3-) | |
| 65 | + | [ -n "$cmd" ] || exit 0 | |
| 66 | + | ||
| 67 | + | # TUI entries get a terminal to live in. $TERMINAL so this stays true if the | |
| 68 | + | # terminal pick ever changes; rioterm is the STACK.md default. | |
| 69 | + | if [ "$needs_term" = "1" ]; then | |
| 70 | + | cmd="${TERMINAL:-rioterm} -e $cmd" | |
| 71 | + | fi | |
| 72 | + | ||
| 73 | + | # swaymsg runs it through sh, so a full Exec line keeps its arguments. Outside | |
| 74 | + | # a sway session (a bare tty, or another compositor) fall back to detaching it | |
| 75 | + | # here, so the picker is not silently useless. | |
| 76 | + | if [ -n "${SWAYSOCK:-}" ] && command -v swaymsg >/dev/null 2>&1; then | |
| 77 | + | swaymsg exec "$cmd" >/dev/null | |
| 78 | + | else | |
| 79 | + | setsid sh -c "$cmd" >/dev/null 2>&1 & | |
| 80 | + | fi |