Teach the launcher what an operating system knows
The OS-parity batch from the vicinae plan, tasks 1 through 7. The audit came back better than the plan guessed: the calculator already links libqalculate, the built-in file index answers in under 100ms across all of home, quicklinks and snippets ship as built-in stores -- so zero new packages, and `vicinae dmenu` replaces the planned compiled extension outright. What was missing gets built: a power menu (lock, suspend, log out through uwsm, restart, power off), reminders as transient systemd timers with a pick-to-cancel list, a color picker over hyprpicker, and dmenu pick-lists for window switching, force quit, kill process, SSH hosts, and recent files -- all through one panama-pick helper. The launcher commands contract exercises the reminder parsing and every pick-list against stubs, including killing its own sacrificial sleep.
This commit is contained in:
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# List-driven launcher commands, through `vicinae dmenu`.
|
||||
#
|
||||
# panama-pick window fuzzy-switch to an open window
|
||||
# panama-pick quit-window pick a window; SIGTERM it, SIGKILL on repeat
|
||||
# panama-pick process pick a process by CPU; SIGTERM it
|
||||
# panama-pick ssh pick a Host from ~/.ssh/config; open a session
|
||||
# panama-pick recent pick a recently used file; open it
|
||||
#
|
||||
# One helper rather than five scripts because every subcommand is the same
|
||||
# sentence: build a list, let dmenu pick a line, act on the index. dmenu is
|
||||
# vicinae's own list view, so these read as launcher commands without a
|
||||
# compiled extension behind them.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Prints the picked index for the lines on stdin, or nothing on Escape.
|
||||
menu() {
|
||||
vicinae dmenu --navigation-title "$1" --section-title "$2" \
|
||||
--format index --no-section 2>/dev/null
|
||||
}
|
||||
|
||||
# Selected line (1-indexed by dmenu's 0-indexed output) from a saved list.
|
||||
line_at() {
|
||||
sed -n "$(( $1 + 1 ))p"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
window|quit-window)
|
||||
clients="$(hyprctl clients -j | jq -r '
|
||||
[.[] | select(.mapped and .workspace.id > 0)] | sort_by(.workspace.id)[]
|
||||
| [.address, .pid, "\(.title) (\(.class), workspace \(.workspace.id))"]
|
||||
| @tsv')"
|
||||
[[ -n "$clients" ]] || exit 0
|
||||
title="Switch to window"; [[ "$1" == quit-window ]] && title="Force quit window"
|
||||
idx="$(cut -f3 <<<"$clients" | menu "$title" 'Windows ({count})')" || exit 0
|
||||
[[ -n "$idx" ]] || exit 0
|
||||
picked="$(line_at "$idx" <<<"$clients")"
|
||||
address="$(cut -f1 <<<"$picked")"
|
||||
pid="$(cut -f2 <<<"$picked")"
|
||||
if [[ "$1" == window ]]; then
|
||||
exec hyprctl dispatch focuswindow "address:$address"
|
||||
fi
|
||||
# TERM first; the window still being mapped a moment later means the app
|
||||
# ignored it, which is what force quit exists for.
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
if hyprctl clients -j | jq -e --arg a "$address" '.[] | select(.address == $a)' >/dev/null 2>&1; then
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
;;
|
||||
process)
|
||||
procs="$(ps -eo pid=,pcpu=,comm= --sort=-pcpu | awk -v self=$$ '$1 != self { printf "%s\t%5.1f%% %s\n", $1, $2, $3 }' | head -60)"
|
||||
[[ -n "$procs" ]] || exit 0
|
||||
idx="$(cut -f2 <<<"$procs" | menu "Kill process" 'By CPU ({count})')" || exit 0
|
||||
[[ -n "$idx" ]] || exit 0
|
||||
pid="$(line_at "$idx" <<<"$procs" | cut -f1)"
|
||||
kill "$pid" 2>/dev/null \
|
||||
&& notify-send "Sent SIGTERM to $pid" 2>/dev/null || true
|
||||
;;
|
||||
ssh)
|
||||
config="$HOME/.ssh/config"
|
||||
hosts="$( [[ -r "$config" ]] && awk '/^[Hh]ost / { for (i = 2; i <= NF; i++) if ($i !~ /[*?]/) print $i }' "$config" | sort -u )"
|
||||
if [[ -z "$hosts" ]]; then
|
||||
notify-send "SSH Hosts" "No hosts in ~/.ssh/config" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
idx="$(menu "Open SSH session" 'Hosts ({count})' <<<"$hosts")" || exit 0
|
||||
[[ -n "$idx" ]] || exit 0
|
||||
host="$(line_at "$idx" <<<"$hosts")"
|
||||
# kitty, matching hypr/keybinds.lua -- the terminal is written down in a
|
||||
# few places and this one keeps the same value until one owner exists.
|
||||
exec kitty --detach ssh "$host"
|
||||
;;
|
||||
recent)
|
||||
xbel="$HOME/.local/share/recently-used.xbel"
|
||||
entries="$( [[ -r "$xbel" ]] && python3 - "$xbel" <<'PY'
|
||||
import sys, urllib.parse, xml.etree.ElementTree as ET
|
||||
try:
|
||||
root = ET.parse(sys.argv[1]).getroot()
|
||||
except ET.ParseError:
|
||||
sys.exit(0)
|
||||
marks = [(b.get("modified") or "", b.get("href") or "") for b in root.iter("bookmark")]
|
||||
for _, href in sorted(marks, reverse=True)[:40]:
|
||||
if href.startswith("file://"):
|
||||
path = urllib.parse.unquote(href[7:])
|
||||
print(f"{path}")
|
||||
PY
|
||||
)"
|
||||
if [[ -z "$entries" ]]; then
|
||||
notify-send "Recent Files" "Nothing recorded yet" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
idx="$(sed "s|^$HOME|~|" <<<"$entries" | menu "Open recent file" 'Recent ({count})')" || exit 0
|
||||
[[ -n "$idx" ]] || exit 0
|
||||
exec xdg-open "$(line_at "$idx" <<<"$entries")"
|
||||
;;
|
||||
*)
|
||||
echo 'usage: panama-pick window|quit-window|process|ssh|recent' >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Reminders without an app: transient user timers.
|
||||
#
|
||||
# panama-remind add "20m" "stand up" relative: 90s, 20m, 1h, 1h30m
|
||||
# panama-remind add "9:30" "standup" clock time: today, or tomorrow if past
|
||||
# panama-remind list one line per pending reminder
|
||||
# panama-remind pick choose one in the launcher; cancel it
|
||||
# panama-remind cancel <unit> cancel by unit name
|
||||
#
|
||||
# systemd-run carries the whole feature: the timer survives shell exit, fires
|
||||
# notify-send inside the user session with its activation environment intact,
|
||||
# and cleans itself up after firing. The message rides the unit Description so
|
||||
# list can show it without a state file. Everything relative is converted to
|
||||
# seconds first, so there is exactly one systemd-run form to get right.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo 'usage: panama-remind add "<20m|1h30m|9:30>" "<text>" | list | pick | cancel <unit>' >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
seconds_until() {
|
||||
local when="$1" total=0 rest num unit
|
||||
if [[ "$when" =~ ^([0-9]{1,2}):([0-9]{2})$ ]]; then
|
||||
local target now
|
||||
target="$(date -d "$when" +%s 2>/dev/null)" || return 1
|
||||
now="$(date +%s)"
|
||||
(( target <= now )) && target=$(( target + 86400 ))
|
||||
echo $(( target - now ))
|
||||
return 0
|
||||
fi
|
||||
rest="$when"
|
||||
[[ "$rest" =~ ^([0-9]+[smh])+$ ]] || return 1
|
||||
while [[ "$rest" =~ ^([0-9]+)([smh])(.*)$ ]]; do
|
||||
num="${BASH_REMATCH[1]}" unit="${BASH_REMATCH[2]}" rest="${BASH_REMATCH[3]}"
|
||||
case "$unit" in
|
||||
s) total=$(( total + num ));;
|
||||
m) total=$(( total + num * 60 ));;
|
||||
h) total=$(( total + num * 3600 ));;
|
||||
esac
|
||||
done
|
||||
(( total > 0 )) || return 1
|
||||
echo "$total"
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
case "$cmd" in
|
||||
add)
|
||||
when="${2:-}" text="${3:-}"
|
||||
[[ -n "$when" && -n "$text" ]] || usage
|
||||
if ! secs="$(seconds_until "$when")"; then
|
||||
notify-send "Reminder not set" "Could not read \"$when\" — try 20m, 1h30m, or 9:30" 2>/dev/null || true
|
||||
echo "panama-remind: could not parse \"$when\"" >&2
|
||||
exit 1
|
||||
fi
|
||||
unit="panama-remind-$(date +%s)-$RANDOM"
|
||||
systemd-run --user --collect --unit="$unit" \
|
||||
--description="$text" \
|
||||
--on-active="${secs}s" --timer-property=AccuracySec=1s \
|
||||
notify-send --urgency=critical --icon=alarm-symbolic "Reminder" "$text" \
|
||||
>/dev/null
|
||||
notify-send "Reminder set" "\"$text\" in $when" 2>/dev/null || true
|
||||
;;
|
||||
list)
|
||||
# unit <TAB> when it fires <TAB> message, one per pending reminder.
|
||||
# `next` is microseconds since the epoch.
|
||||
systemctl --user list-timers 'panama-remind-*' --output=json 2>/dev/null \
|
||||
| jq -r '.[] | [.unit, ((.next // 0) / 1000000 | floor)] | @tsv' \
|
||||
| while IFS=$'\t' read -r unit next; do
|
||||
desc="$(systemctl --user show "$unit" --property=Description --value 2>/dev/null)"
|
||||
printf '%s\t%s\t%s\n' "$unit" "$(date -d "@$next" '+%H:%M:%S' 2>/dev/null)" "$desc"
|
||||
done
|
||||
;;
|
||||
pick)
|
||||
lines="$("$0" list)"
|
||||
if [[ -z "$lines" ]]; then
|
||||
notify-send "Reminders" "Nothing pending" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
chosen="$(awk -F'\t' '{ printf "%s — %s\n", $3, $2 }' <<<"$lines" \
|
||||
| vicinae dmenu --navigation-title "Cancel a reminder" \
|
||||
--section-title 'Pending ({count})' --format index)" || exit 0
|
||||
[[ -n "$chosen" ]] || exit 0
|
||||
unit="$(sed -n "$(( chosen + 1 ))p" <<<"$lines" | cut -f1)"
|
||||
[[ -n "$unit" ]] && "$0" cancel "$unit"
|
||||
;;
|
||||
cancel)
|
||||
unit="${2:-}"; [[ -n "$unit" ]] || usage
|
||||
systemctl --user stop "${unit%.timer}.timer" 2>/dev/null || true
|
||||
notify-send "Reminder cancelled" 2>/dev/null || true
|
||||
;;
|
||||
*) usage;;
|
||||
esac
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Force Quit Window
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Pick a window; ask it to close, then make it.
|
||||
# @vicinae.keywords ["force quit", "kill window", "unresponsive", "frozen"]
|
||||
|
||||
exec "$HOME/.config/quickshell/scripts/panama-pick" quit-window
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Kill Process
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Pick a process by CPU; send it SIGTERM.
|
||||
# @vicinae.keywords ["kill", "process", "task manager", "cpu"]
|
||||
|
||||
exec "$HOME/.config/quickshell/scripts/panama-pick" process
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Pending Reminders
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description See pending reminders; pick one to cancel it.
|
||||
# @vicinae.keywords ["reminders", "timers", "pending", "cancel"]
|
||||
|
||||
exec "$HOME/.config/quickshell/scripts/panama-remind" pick
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Lock Screen
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Lock the session now.
|
||||
# @vicinae.keywords ["lock", "screen", "away", "secure"]
|
||||
|
||||
# The logind signal, same as the CTRL+ALT+L bind: hypridle is the one listener
|
||||
# and owns actually running hyprlock.
|
||||
exec loginctl lock-session
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Log Out
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description End the session and return to the login screen.
|
||||
# @vicinae.keywords ["logout", "log out", "sign out", "exit", "session"]
|
||||
|
||||
# uwsm owns the session (see the hypr README), so it gets to end it: units stop
|
||||
# in order instead of being orphaned by a bare compositor exit. The dispatch is
|
||||
# the fallback for the plain non-uwsm session.
|
||||
if command -v uwsm >/dev/null 2>&1; then
|
||||
exec uwsm stop
|
||||
fi
|
||||
exec hyprctl dispatch exit
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Pick Color
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Pick a color from the screen; the hex lands on the clipboard.
|
||||
# @vicinae.keywords ["color", "colour", "picker", "eyedropper", "hex"]
|
||||
|
||||
# -a autocopies to the clipboard; the notification is the receipt. hyprpicker
|
||||
# exits 1 on Escape, which is a choice, not a failure.
|
||||
if ! command -v hyprpicker >/dev/null 2>&1; then
|
||||
notify-send "Pick Color" "hyprpicker is not installed" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
color="$(hyprpicker -a)" || exit 0
|
||||
[[ -n "$color" ]] && notify-send --icon=color-select-symbolic "Copied $color" 2>/dev/null || true
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Power Off
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Shut the machine down.
|
||||
# @vicinae.keywords ["power off", "shutdown", "shut down", "poweroff", "halt"]
|
||||
|
||||
exec systemctl poweroff
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Restart
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Restart the machine.
|
||||
# @vicinae.keywords ["reboot", "restart"]
|
||||
|
||||
exec systemctl reboot
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Recent Files
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Open something you had open recently.
|
||||
# @vicinae.keywords ["recent", "files", "documents", "history"]
|
||||
|
||||
exec "$HOME/.config/quickshell/scripts/panama-pick" recent
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Remind Me
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Set a reminder: a delay like 20m or 1h30m, or a clock time like 9:30.
|
||||
# @vicinae.keywords ["remind", "reminder", "timer", "alarm", "in"]
|
||||
# @vicinae.argument1 { "type": "text", "placeholder": "20m / 9:30" }
|
||||
# @vicinae.argument2 { "type": "text", "placeholder": "what about" }
|
||||
|
||||
exec "$HOME/.config/quickshell/scripts/panama-remind" add "$1" "$2"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title SSH Hosts
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Open a session to a host from ~/.ssh/config.
|
||||
# @vicinae.keywords ["ssh", "host", "remote", "server"]
|
||||
|
||||
exec "$HOME/.config/quickshell/scripts/panama-pick" ssh
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Suspend
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Put the machine to sleep.
|
||||
# @vicinae.keywords ["suspend", "sleep", "standby"]
|
||||
|
||||
exec systemctl suspend
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# @vicinae.schemaVersion 1
|
||||
# @vicinae.title Switch Window
|
||||
# @vicinae.mode silent
|
||||
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
|
||||
# @vicinae.description Jump to an open window by title.
|
||||
# @vicinae.keywords ["window", "switch", "focus", "alt tab"]
|
||||
|
||||
exec "$HOME/.config/quickshell/scripts/panama-pick" window
|
||||
Reference in New Issue
Block a user