2026-08-29 17:40:48 -04:00
|
|
|
#!/bin/bash
|
|
|
|
|
|
2026-10-14 13:31:37 -04:00
|
|
|
# omarchy:summary=Toggle a scratch workspace (scratchpad, claude, fastmail, beeper, home, lmstudio), starting its resident apps when any is missing.
|
2026-08-29 17:40:48 -04:00
|
|
|
|
|
|
|
|
set -uo pipefail
|
|
|
|
|
|
2026-10-14 13:31:37 -04:00
|
|
|
# Usage: omarchy-toggle-scratch [scratchpad|claude|fastmail|beeper|home|lmstudio]
|
2026-09-21 16:34:23 -04:00
|
|
|
#
|
|
|
|
|
# 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
|
2026-10-13 18:38:03 -04:00
|
|
|
# web apps) from piling up duplicates. The scrolling layout gives each
|
|
|
|
|
# resident its own column, in the order they appear.
|
2026-09-21 16:34:23 -04:00
|
|
|
#
|
2026-10-15 14:26:14 -04:00
|
|
|
# HOME_MONITOR lists connector names or EDID descriptions, separated by |;
|
|
|
|
|
# the first one connected wins (descriptions, as in the workspace rules,
|
|
|
|
|
# survive a different DP connector after re-docking). When it is empty or
|
|
|
|
|
# none is connected, the workspace opens on the focused monitor. EXTERNAL is
|
|
|
|
|
# the main external display: the Studio Display, else the Samsung ultrawide
|
|
|
|
|
# (same order as hypr/monitors.lua).
|
|
|
|
|
EXTERNAL="Apple Computer Inc StudioDisplay 0x984262A7|Samsung Electric Company S34CG50 HNTY501103"
|
2026-09-21 16:34:23 -04:00
|
|
|
HOME_MONITOR=""
|
|
|
|
|
case ${1:-scratchpad} in
|
|
|
|
|
scratchpad)
|
|
|
|
|
NAME="scratchpad"
|
2026-10-15 14:26:14 -04:00
|
|
|
HOME_MONITOR=$EXTERNAL
|
2026-10-06 11:38:28 -04:00
|
|
|
MEMBERS=("so.stencil.tern|tern")
|
2026-09-29 17:45:26 -04:00
|
|
|
;;
|
|
|
|
|
claude)
|
|
|
|
|
NAME="claude"
|
2026-10-15 14:26:14 -04:00
|
|
|
HOME_MONITOR=$EXTERNAL
|
2026-09-29 17:45:26 -04:00
|
|
|
MEMBERS=("com.anthropic.Claude|claude-desktop")
|
2026-09-21 16:34:23 -04:00
|
|
|
;;
|
|
|
|
|
fastmail)
|
|
|
|
|
NAME="fastmail"
|
2026-10-15 14:26:14 -04:00
|
|
|
HOME_MONITOR="eDP-1"
|
2026-10-13 09:45:47 -04:00
|
|
|
MEMBERS=("com.fastmail.Fastmail|gtk-launch com.fastmail.Fastmail")
|
2026-09-21 16:34:23 -04:00
|
|
|
;;
|
|
|
|
|
beeper)
|
|
|
|
|
NAME="beeper"
|
2026-10-15 14:26:14 -04:00
|
|
|
HOME_MONITOR="eDP-1"
|
2026-09-21 16:34:23 -04:00
|
|
|
MEMBERS=("Beeper|gtk-launch beeper")
|
|
|
|
|
;;
|
2026-10-14 11:35:15 -04:00
|
|
|
home)
|
|
|
|
|
NAME="home"
|
|
|
|
|
MEMBERS=("brave-home\.google\.com__.*|gtk-launch 'Google Home'")
|
|
|
|
|
;;
|
2026-10-14 13:31:37 -04:00
|
|
|
lmstudio)
|
|
|
|
|
NAME="lmstudio"
|
|
|
|
|
MEMBERS=("ai\.elementlabs\.lmstudio|gtk-launch ai.elementlabs.lmstudio")
|
|
|
|
|
;;
|
2026-09-21 16:34:23 -04:00
|
|
|
*)
|
2026-10-14 13:31:37 -04:00
|
|
|
echo "usage: ${0##*/} [scratchpad|claude|fastmail|beeper|home|lmstudio]" >&2
|
2026-09-21 16:34:23 -04:00
|
|
|
exit 2
|
|
|
|
|
;;
|
|
|
|
|
esac
|
2026-08-29 17:40:48 -04:00
|
|
|
WORKSPACE="special:$NAME"
|
|
|
|
|
|
2026-09-21 16:34:23 -04:00
|
|
|
# Classes of the windows living on the scratchpad, shown or hidden.
|
|
|
|
|
scratch_classes() {
|
|
|
|
|
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'
|
2026-08-29 17:40:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scratch_visible() {
|
2026-09-21 16:34:23 -04:00
|
|
|
[[ -n $(scratch_monitor) ]]
|
2026-08-29 17:40:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Hyprland's lua config takes lua expressions on `hyprctl dispatch`; the
|
|
|
|
|
# classic dispatcher is the pre-lua fallback (same shape as omarchy-cycle).
|
2026-09-21 16:34:23 -04:00
|
|
|
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"
|
2026-08-29 17:40:48 -04:00
|
|
|
hyprctl dispatch "hl.dsp.workspace.toggle_special(\"$NAME\")" >/dev/null 2>&1 ||
|
|
|
|
|
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 14:26:14 -04:00
|
|
|
# The first connected home monitor, else whatever is focused.
|
2026-09-21 16:34:23 -04:00
|
|
|
target_monitor() {
|
2026-10-15 14:26:14 -04:00
|
|
|
hyprctl monitors -j | jq -r --arg m "$HOME_MONITOR" '
|
|
|
|
|
. as $monitors
|
|
|
|
|
| first(($m | split("|"))[] as $want | $monitors[] | select(.name == $want or .description == $want))
|
|
|
|
|
// first($monitors[] | select(.focused))
|
|
|
|
|
| .name'
|
2026-09-21 16:34:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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"
|
2026-08-29 17:40:48 -04:00
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
2026-09-21 16:34:23 -04:00
|
|
|
present=$(scratch_classes)
|
|
|
|
|
before=$(grep -c . <<<"$present")
|
|
|
|
|
launched=0
|
|
|
|
|
for member in "${MEMBERS[@]}"; do
|
|
|
|
|
pattern=${member%%|*}
|
|
|
|
|
grep -qE "^(${pattern})$" <<<"$present" && continue
|
2026-10-14 11:35:15 -04:00
|
|
|
# eval so a launch command can quote a desktop id with spaces.
|
|
|
|
|
eval "setsid uwsm-app -- ${member#*|}" >/dev/null 2>&1 &
|
2026-09-21 16:34:23 -04:00
|
|
|
launched=1
|
2026-08-29 17:40:48 -04:00
|
|
|
done
|
|
|
|
|
|
2026-09-21 16:34:23 -04:00
|
|
|
if ((launched)); then
|
2026-09-29 17:45:26 -04:00
|
|
|
# Reveal once the first newcomer lands (Claude takes a few seconds); any
|
|
|
|
|
# later ones drop straight onto the now-visible scratchpad. Deciding the
|
2026-09-21 16:34:23 -04:00
|
|
|
# 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)"
|