Reach every window the region picker can highlight
Warping to a target window's center selects the wrong window when a smaller one covers that center: slurp keeps highlighting the coverer, so Tab could never leave it. Navigation now warps to the most central point that resolves back to the target, and skips windows that hovering could not reach either. Unbinding the picker's transient keys by name also took a same-key binding out of the user's own config with it; the bind handles are now kept and removed individually. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
366c708e44
commit
199bd01f94
+68
-16
@@ -62,28 +62,58 @@ focused_monitor_geo() {
|
||||
hyprctl monitors -j | jq -r "${JQ_MONITOR_GEO} .[] | select(.focused == true) | format_geo"
|
||||
}
|
||||
|
||||
# slurp highlights the smallest box containing the cursor and keeps the first
|
||||
# slurp highlights the smallest box containing the point and keeps the first
|
||||
# one on a tie, so overlapping rectangles resolve the same way here (e.g.
|
||||
# floating over tiled). Reads candidates on stdin, exits 1 when none match.
|
||||
smallest_rect_at() {
|
||||
# floating over tiled). Reads candidates on stdin and leaves the answer in
|
||||
# RESOLVED_RECT; returns 1 when no candidate contains the point. Assigning to
|
||||
# a global rather than printing keeps the probing in warp_point_in fork-free.
|
||||
resolve_rect_at() {
|
||||
local x=$1 y=$2
|
||||
local rect area
|
||||
local smallest=""
|
||||
local smallest_area=0
|
||||
|
||||
RESOLVED_RECT=""
|
||||
|
||||
while IFS= read -r rect; do
|
||||
[[ $rect =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]] || continue
|
||||
((x >= BASH_REMATCH[1] && x < BASH_REMATCH[1] + BASH_REMATCH[3] && y >= BASH_REMATCH[2] && y < BASH_REMATCH[2] + BASH_REMATCH[4])) || continue
|
||||
|
||||
area=$((BASH_REMATCH[3] * BASH_REMATCH[4]))
|
||||
if [[ -z $smallest ]] || ((area < smallest_area)); then
|
||||
smallest=$rect
|
||||
if [[ -z $RESOLVED_RECT ]] || ((area < smallest_area)); then
|
||||
RESOLVED_RECT=$rect
|
||||
smallest_area=$area
|
||||
fi
|
||||
done
|
||||
|
||||
[[ -n $smallest ]] || return 1
|
||||
echo "$smallest"
|
||||
[[ -n $RESOLVED_RECT ]]
|
||||
}
|
||||
|
||||
# A rectangle whose center is covered by a smaller one cannot be selected by
|
||||
# warping to that center: slurp would go on highlighting the coverer. Probe
|
||||
# points inside the rectangle, nearest its center first, for one that resolves
|
||||
# back to it, and leave that point in WARP_X / WARP_Y. Returns 1 when the
|
||||
# rectangle is buried well enough that no point resolves to it, in which case
|
||||
# hovering could not reach it either.
|
||||
warp_point_in() {
|
||||
local rect=$1 candidates=$2
|
||||
[[ $rect =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]] || return 1
|
||||
local rect_x=${BASH_REMATCH[1]} rect_y=${BASH_REMATCH[2]}
|
||||
local rect_width=${BASH_REMATCH[3]} rect_height=${BASH_REMATCH[4]}
|
||||
local probe x y
|
||||
|
||||
for probe in "${WARP_PROBES[@]}"; do
|
||||
x=$((rect_x + rect_width * ${probe% *} / 8))
|
||||
y=$((rect_y + rect_height * ${probe#* } / 8))
|
||||
|
||||
resolve_rect_at "$x" "$y" <<<"$candidates" || continue
|
||||
[[ $RESOLVED_RECT == "$rect" ]] || continue
|
||||
|
||||
WARP_X=$x
|
||||
WARP_Y=$y
|
||||
return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Whatever slurp is highlighting under the cursor. It is fed monitor rects as
|
||||
@@ -94,9 +124,11 @@ geo_at_cursor() {
|
||||
local x=${pos%,*}
|
||||
local y=${pos#*, }
|
||||
|
||||
smallest_rect_at "$x" "$y" < <(window_rects) ||
|
||||
smallest_rect_at "$x" "$y" < <(monitor_rects) ||
|
||||
if resolve_rect_at "$x" "$y" < <(window_rects) || resolve_rect_at "$x" "$y" < <(monitor_rects); then
|
||||
echo "$RESOLVED_RECT"
|
||||
else
|
||||
focused_monitor_geo
|
||||
fi
|
||||
}
|
||||
|
||||
# Keyboard control while slurp is open: binds scoped to slurp's layer
|
||||
@@ -128,17 +160,38 @@ if [[ ${1:-} == "--select-window" ]]; then
|
||||
|
||||
candidates=$(window_rects)
|
||||
|
||||
# Eighth fractions of a rectangle's width and height, ordered by distance
|
||||
# from its center so warp_point_in prefers the most central point it can use.
|
||||
mapfile -t WARP_PROBES < <(
|
||||
for fx in {1..7}; do
|
||||
for fy in {1..7}; do
|
||||
printf '%d %d %d\n' $(((fx - 4) * (fx - 4) + (fy - 4) * (fy - 4))) "$fx" "$fy"
|
||||
done
|
||||
done | sort -n | cut -d' ' -f2-
|
||||
)
|
||||
|
||||
# Only rectangles that hovering could actually reach take part in navigation,
|
||||
# each paired with the point to warp to.
|
||||
declare -A warp_points
|
||||
reachable=""
|
||||
while IFS= read -r rect; do
|
||||
warp_point_in "$rect" "$candidates" || continue
|
||||
warp_points[$rect]="$WARP_X $WARP_Y"
|
||||
reachable+="$rect"$'\n'
|
||||
done <<<"$candidates"
|
||||
[[ -n $reachable ]] || exit 0
|
||||
|
||||
# Reading order: top-to-bottom, then left-to-right.
|
||||
rects=$(while IFS= read -r rect; do
|
||||
[[ $rect =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]] || continue
|
||||
printf '%d\t%d\t%s\n' "${BASH_REMATCH[2]}" "${BASH_REMATCH[1]}" "$rect"
|
||||
done <<<"$candidates" | sort -n -k1,1 -k2,2 | cut -f3-)
|
||||
[[ -n $rects ]] || exit 0
|
||||
done <<<"$reachable" | sort -n -k1,1 -k2,2 | cut -f3-)
|
||||
|
||||
# The selection to move from is the one slurp highlights, resolved from the
|
||||
# same list in the same order as --take-window so navigation and capture
|
||||
# never disagree. Measure from its center.
|
||||
current=$(smallest_rect_at "$origin_x" "$origin_y" <<<"$candidates")
|
||||
current=""
|
||||
resolve_rect_at "$origin_x" "$origin_y" <<<"$candidates" && current=$RESOLVED_RECT
|
||||
|
||||
if [[ $current =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]]; then
|
||||
origin_x=$((BASH_REMATCH[1] + BASH_REMATCH[3] / 2))
|
||||
@@ -211,9 +264,8 @@ if [[ ${1:-} == "--select-window" ]]; then
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $target =~ ^(-?[0-9]+),(-?[0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]]; then
|
||||
target_x=$((BASH_REMATCH[1] + BASH_REMATCH[3] / 2))
|
||||
target_y=$((BASH_REMATCH[2] + BASH_REMATCH[4] / 2))
|
||||
if [[ -n $target && -n ${warp_points[$target]} ]]; then
|
||||
read -r target_x target_y <<<"${warp_points[$target]}"
|
||||
hyprctl eval "hl.dispatch(hl.dsp.cursor.move({ x = $target_x, y = $target_y }))" >/dev/null
|
||||
fi
|
||||
exit 0
|
||||
|
||||
@@ -44,18 +44,26 @@ o.bind("SUPER + CTRL + PRINT", "Extract text (OCR) from screenshot", "omarchy-ca
|
||||
-- Keyboard control for the slurp region picker (see omarchy-capture-region).
|
||||
-- The binds live exactly as long as a selection layer is on screen (slurp
|
||||
-- opens one per monitor), so they cannot leak or get stuck.
|
||||
-- Unbinding by key would take a same-key binding out of the user's own config
|
||||
-- with it, so each handle is kept and removed individually.
|
||||
local selection_layers = 0
|
||||
local selection_binds = {}
|
||||
|
||||
hl.on("layer.opened", function(layer)
|
||||
if layer.namespace == "selection" then
|
||||
selection_layers = selection_layers + 1
|
||||
if selection_layers == 1 then
|
||||
hl.bind("RETURN", hl.dsp.exec_cmd("omarchy-capture-region --take-window"), { description = "Capture highlighted window" })
|
||||
hl.bind("CTRL + RETURN", hl.dsp.exec_cmd("omarchy-capture-region --take-fullscreen"), { description = "Capture entire screen" })
|
||||
hl.bind("TAB", hl.dsp.exec_cmd("omarchy-capture-region --select-window next"), { description = "Select next window to capture" })
|
||||
hl.bind("CTRL + TAB", hl.dsp.exec_cmd("omarchy-capture-region --select-window prev"), { description = "Select previous window to capture" })
|
||||
selection_binds = {
|
||||
hl.bind("RETURN", hl.dsp.exec_cmd("omarchy-capture-region --take-window"), { description = "Capture highlighted window" }),
|
||||
hl.bind("CTRL + RETURN", hl.dsp.exec_cmd("omarchy-capture-region --take-fullscreen"), { description = "Capture entire screen" }),
|
||||
hl.bind("TAB", hl.dsp.exec_cmd("omarchy-capture-region --select-window next"), { description = "Select next window to capture" }),
|
||||
hl.bind("CTRL + TAB", hl.dsp.exec_cmd("omarchy-capture-region --select-window prev"), { description = "Select previous window to capture" }),
|
||||
}
|
||||
for _, direction in ipairs({ "left", "right", "up", "down" }) do
|
||||
hl.bind(direction:upper(), hl.dsp.exec_cmd("omarchy-capture-region --select-window " .. direction), { description = "Select window to capture" })
|
||||
table.insert(
|
||||
selection_binds,
|
||||
hl.bind(direction:upper(), hl.dsp.exec_cmd("omarchy-capture-region --select-window " .. direction), { description = "Select window to capture" })
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -65,13 +73,10 @@ hl.on("layer.closed", function(layer)
|
||||
if layer.namespace == "selection" and selection_layers > 0 then
|
||||
selection_layers = selection_layers - 1
|
||||
if selection_layers == 0 then
|
||||
hl.unbind("RETURN")
|
||||
hl.unbind("CTRL + RETURN")
|
||||
hl.unbind("TAB")
|
||||
hl.unbind("CTRL + TAB")
|
||||
for _, direction in ipairs({ "LEFT", "RIGHT", "UP", "DOWN" }) do
|
||||
hl.unbind(direction)
|
||||
for _, keybind in ipairs(selection_binds) do
|
||||
keybind:unbind()
|
||||
end
|
||||
selection_binds = {}
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user