mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-02 11:32:42 +00:00
feat(cli): add multi‑profile scratchpad toggle with resident app launch and monitor handling
This commit is contained in:
1 parent
e8fc0775b5
commit
989fac13d1
1 file changed
+100
-22
@@ -1,45 +1,123 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Toggle the scratchpad, starting the kitty/herdr scratch terminal when it holds no windows.
|
# omarchy:summary=Toggle a scratch workspace (scratchpad, fastmail, beeper), starting its resident apps when any is missing.
|
||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
NAME="scratchpad"
|
# Usage: omarchy-toggle-scratch [scratchpad|fastmail|beeper|youtube]
|
||||||
|
#
|
||||||
|
# Each profile is a special workspace, the monitor it opens on, and its
|
||||||
|
# resident apps (class pattern -> launch command; the pattern is an anchored
|
||||||
|
# ERE, so a plain class name matches itself). hypr/hyprland.lua pins every
|
||||||
|
# class to its workspace, so only the launch and the reveal are left here. A
|
||||||
|
# resident is launched only when no window on the workspace matches it, which
|
||||||
|
# is what keeps a launcher that always opens a fresh window (the Chromium
|
||||||
|
# web apps) from piling up duplicates. On first start the master layout gives
|
||||||
|
# the master slot to whichever window appears first (a terminal beats an
|
||||||
|
# Electron app); swap them afterwards if the arrangement matters.
|
||||||
|
#
|
||||||
|
# HOME_MONITOR is a connector name or an EDID description (the latter, as in
|
||||||
|
# the workspace rules, survives a different DP connector after re-docking).
|
||||||
|
# When it is empty or that monitor is absent, the workspace opens on the
|
||||||
|
# focused one.
|
||||||
|
HOME_MONITOR=""
|
||||||
|
case ${1:-scratchpad} in
|
||||||
|
scratchpad)
|
||||||
|
NAME="scratchpad"
|
||||||
|
HOME_MONITOR="Apple Computer Inc StudioDisplay 0x984262A7"
|
||||||
|
MEMBERS=(
|
||||||
|
"com.anthropic.Claude|claude-desktop"
|
||||||
|
"kitty-herdr|kitty --class=kitty-herdr --config $HOME/.config/kitty/herdr.conf"
|
||||||
|
)
|
||||||
|
;;
|
||||||
|
fastmail)
|
||||||
|
NAME="fastmail"
|
||||||
|
MEMBERS=("com.fastmail.Fastmail|gtk-launch fastmail")
|
||||||
|
;;
|
||||||
|
beeper)
|
||||||
|
NAME="beeper"
|
||||||
|
MEMBERS=("Beeper|gtk-launch beeper")
|
||||||
|
;;
|
||||||
|
youtube)
|
||||||
|
NAME="youtube"
|
||||||
|
# Chromium derives the class from the app URL, and the same web app has
|
||||||
|
# shown up under more than one host spelling (youtube.com vs
|
||||||
|
# www.youtube.com) once a video is playing, so match the family.
|
||||||
|
MEMBERS=("brave-(www\\.)?youtube\\.com__.*|gtk-launch YouTube")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: ${0##*/} [scratchpad|fastmail|beeper|youtube]" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
WORKSPACE="special:$NAME"
|
WORKSPACE="special:$NAME"
|
||||||
CLASS="kitty-herdr"
|
|
||||||
LAUNCH="kitty --class=$CLASS --config $HOME/.config/kitty/herdr.conf"
|
|
||||||
|
|
||||||
# Windows living on the scratchpad, whether it is shown or hidden.
|
# Classes of the windows living on the scratchpad, shown or hidden.
|
||||||
scratch_windows() {
|
scratch_classes() {
|
||||||
hyprctl clients -j | jq --arg ws "$WORKSPACE" '[.[] | select(.workspace.name == $ws)] | length'
|
hyprctl clients -j | jq -r --arg ws "$WORKSPACE" '.[] | select(.workspace.name == $ws) | .class'
|
||||||
|
}
|
||||||
|
|
||||||
|
scratch_windows() {
|
||||||
|
scratch_classes | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
# Name of the monitor currently displaying the scratchpad, if any.
|
||||||
|
scratch_monitor() {
|
||||||
|
hyprctl monitors -j | jq -r --arg ws "$WORKSPACE" '.[] | select(.specialWorkspace.name == $ws) | .name'
|
||||||
}
|
}
|
||||||
|
|
||||||
# True when some monitor currently displays the scratchpad.
|
|
||||||
scratch_visible() {
|
scratch_visible() {
|
||||||
hyprctl monitors -j | jq -e --arg ws "$WORKSPACE" 'any(.[]; .specialWorkspace.name == $ws)' >/dev/null
|
[[ -n $(scratch_monitor) ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Hyprland's lua config takes lua expressions on `hyprctl dispatch`; the
|
# Hyprland's lua config takes lua expressions on `hyprctl dispatch`; the
|
||||||
# classic dispatcher is the pre-lua fallback (same shape as omarchy-cycle).
|
# classic dispatcher is the pre-lua fallback (same shape as omarchy-cycle).
|
||||||
toggle() {
|
focus_monitor() {
|
||||||
|
hyprctl dispatch "hl.dsp.focus({ monitor = \"$1\" })" >/dev/null 2>&1 ||
|
||||||
|
hyprctl dispatch focusmonitor "$1" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# togglespecialworkspace acts on the focused monitor, so focus decides where
|
||||||
|
# the scratchpad appears (or which copy hides).
|
||||||
|
toggle_on() {
|
||||||
|
focus_monitor "$1"
|
||||||
hyprctl dispatch "hl.dsp.workspace.toggle_special(\"$NAME\")" >/dev/null 2>&1 ||
|
hyprctl dispatch "hl.dsp.workspace.toggle_special(\"$NAME\")" >/dev/null 2>&1 ||
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null
|
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($(scratch_windows) > 0)); then
|
# The profile's home monitor when connected, else whatever is focused.
|
||||||
toggle
|
target_monitor() {
|
||||||
|
hyprctl monitors -j | jq -r --arg m "$HOME_MONITOR" \
|
||||||
|
'first(.[] | select(.name == $m or .description == $m)) // first(.[] | select(.focused)) | .name'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Showing: just hide, even if a member is gone. Missing members come back on
|
||||||
|
# the next reveal.
|
||||||
|
shown=$(scratch_monitor)
|
||||||
|
if [[ -n $shown ]]; then
|
||||||
|
toggle_on "$shown"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Empty scratchpad: there is nothing to toggle into view, so start the herdr
|
present=$(scratch_classes)
|
||||||
# terminal instead. hypr/hyprland.lua pins kitty-herdr to the scratchpad, so
|
before=$(grep -c . <<<"$present")
|
||||||
# only the reveal is left here — and deciding it after the window lands keeps
|
launched=0
|
||||||
# this correct whether or not the rule already showed the workspace.
|
for member in "${MEMBERS[@]}"; do
|
||||||
setsid uwsm-app -- $LAUNCH >/dev/null 2>&1 &
|
pattern=${member%%|*}
|
||||||
|
grep -qE "^(${pattern})$" <<<"$present" && continue
|
||||||
for _ in {1..40}; do
|
setsid uwsm-app -- ${member#*|} >/dev/null 2>&1 &
|
||||||
sleep 0.1
|
launched=1
|
||||||
(($(scratch_windows) > 0)) && break
|
|
||||||
done
|
done
|
||||||
|
|
||||||
scratch_visible || toggle
|
if ((launched)); then
|
||||||
|
# Reveal once the first newcomer lands; later ones (Claude takes a few
|
||||||
|
# seconds) drop straight onto the now-visible scratchpad. Deciding the
|
||||||
|
# reveal after the window lands keeps this correct whether or not the rule
|
||||||
|
# already showed the workspace.
|
||||||
|
for _ in {1..80}; do
|
||||||
|
sleep 0.1
|
||||||
|
(($(scratch_windows) > before)) && break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
scratch_visible || toggle_on "$(target_monitor)"
|
||||||
Reference in new issue
Block a user