Add Hyprland screen focus watcher scripts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Taylor Beeston 2026-04-11 15:12:31 -07:00
parent 6e535804b1
commit 1b7f27bf7c
3 changed files with 325 additions and 0 deletions

View File

@ -0,0 +1,142 @@
#!/bin/bash
set -e
WAYVRCTL="${WAYVRCTL:-wayvrctl}"
STATE_DIR="/tmp/wayvr-hypr-focus"
PID_FILE="$STATE_DIR/watch.pid"
SCREEN_FILE="$STATE_DIR/screen_name"
SOCKET_PATH="${XDG_RUNTIME_DIR}/hypr/${HYPRLAND_INSTANCE_SIGNATURE}/.socket2.sock"
mkdir -p "$STATE_DIR"
run_focus_command() {
local refresh_flag="$1"
local expected_screen="${2:-}"
active_window=$(hyprctl -j activewindow)
if [ -z "$active_window" ] || [ "$active_window" = "{}" ]; then
return 1
fi
monitor_id=$(echo "$active_window" | jq -r '.monitor')
if [ -z "$monitor_id" ] || [ "$monitor_id" = "null" ]; then
return 1
fi
monitor_name=$(hyprctl -j monitors | jq -r --argjson monitor_id "$monitor_id" '.[] | select(.id == $monitor_id) | .name' | head -n 1)
monitor_json=$(hyprctl -j monitors | jq -c --argjson monitor_id "$monitor_id" '.[] | select(.id == $monitor_id)' | head -n 1)
if [ -z "$monitor_name" ] || [ "$monitor_name" = "null" ]; then
return 1
fi
if [ -n "$expected_screen" ] && [ "$monitor_name" != "$expected_screen" ]; then
return 1
fi
target_x=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'(((($active.at[0] + ($active.size[0] / 2)) - $monitor.x) / $monitor.width) | if . < 0 then 0 elif . > 1 then 1 else . end)')
target_y=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'(((($active.at[1] + ($active.size[1] / 2)) - $monitor.y) / $monitor.height) | if . < 0 then 0 elif . > 1 then 1 else . end)')
crop_x=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'((($active.at[0] - $monitor.x) / $monitor.width) | if . < 0 then 0 elif . > 1 then 1 else . end)')
crop_y=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'((($active.at[1] - $monitor.y) / $monitor.height) | if . < 0 then 0 elif . > 1 then 1 else . end)')
crop_w=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'((($active.size[0]) / $monitor.width) | if . < 0.02 then 0.02 elif . > 1 then 1 else . end)')
crop_h=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'((($active.size[1]) / $monitor.height) | if . < 0.02 then 0.02 elif . > 1 then 1 else . end)')
"$WAYVRCTL" screen-focus-at $refresh_flag \
--crop-x "$crop_x" --crop-y "$crop_y" --crop-w "$crop_w" --crop-h "$crop_h" \
"$monitor_name" "$target_x" "$target_y"
printf '%s' "$monitor_name" > "$SCREEN_FILE"
}
event_stream() {
python3 - "$SOCKET_PATH" <<'PY'
import socket
import sys
sock_path = sys.argv[1]
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(sock_path)
buf = b""
while True:
data = sock.recv(4096)
if not data:
break
buf += data
while b"\n" in buf:
line, buf = buf.split(b"\n", 1)
sys.stdout.write(line.decode("utf-8", "replace") + "\n")
sys.stdout.flush()
PY
}
should_refresh_event() {
case "$1" in
activewindowv2*|activewindow*|fullscreen*|movewindow*|movewindowv2*|changefloatingmode*|openwindow*|closewindow*)
return 0
;;
*)
return 1
;;
esac
}
if [ "${1:-}" = "--watch" ]; then
watched_screen="$2"
while [ -f "$PID_FILE" ] && [ "$(cat "$PID_FILE")" = "$$" ]; do
event_stream | while IFS= read -r line; do
if [ ! -f "$PID_FILE" ] || [ "$(cat "$PID_FILE")" != "$$" ]; then
break
fi
if should_refresh_event "$line"; then
run_focus_command --refresh-only "$watched_screen" || true
fi
done
sleep 0.2
done
exit 0
fi
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
watched_screen="$(cat "$SCREEN_FILE" 2>/dev/null || true)"
kill "$(cat "$PID_FILE")" 2>/dev/null || true
rm -f "$PID_FILE" "$SCREEN_FILE"
if [ -n "$watched_screen" ]; then
exec "$WAYVRCTL" screen-focus-toggle "$watched_screen"
fi
exit 0
fi
run_focus_command "" || {
echo "No active window found"
exit 1
}
nohup "$0" --watch "$(cat "$SCREEN_FILE")" >/dev/null 2>&1 &
printf '%s' "$!" > "$PID_FILE"

149
scripts/wayvr-hypr-local.sh Executable file
View File

@ -0,0 +1,149 @@
#!/bin/bash
set -e
REPO_ROOT="/home/taylor/dev/open-source/real-wayvr"
WAYVRCTL="$REPO_ROOT/target/debug/wayvrctl"
STATE_DIR="/tmp/wayvr-hypr-focus"
PID_FILE="$STATE_DIR/watch.pid"
SCREEN_FILE="$STATE_DIR/screen_name"
SOCKET_PATH="${XDG_RUNTIME_DIR}/hypr/${HYPRLAND_INSTANCE_SIGNATURE}/.socket2.sock"
if [ ! -f "$WAYVRCTL" ]; then
echo "wayvrctl not found, building..."
cd "$REPO_ROOT"
cargo build --package wayvrctl
fi
mkdir -p "$STATE_DIR"
run_focus_command() {
local refresh_flag="$1"
local expected_screen="${2:-}"
active_window=$(hyprctl -j activewindow)
if [ -z "$active_window" ] || [ "$active_window" = "{}" ]; then
return 1
fi
monitor_id=$(echo "$active_window" | jq -r '.monitor')
if [ -z "$monitor_id" ] || [ "$monitor_id" = "null" ]; then
return 1
fi
monitor_name=$(hyprctl -j monitors | jq -r --argjson monitor_id "$monitor_id" '.[] | select(.id == $monitor_id) | .name' | head -n 1)
monitor_json=$(hyprctl -j monitors | jq -c --argjson monitor_id "$monitor_id" '.[] | select(.id == $monitor_id)' | head -n 1)
if [ -z "$monitor_name" ] || [ "$monitor_name" = "null" ]; then
return 1
fi
if [ -n "$expected_screen" ] && [ "$monitor_name" != "$expected_screen" ]; then
return 1
fi
target_x=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'(((($active.at[0] + ($active.size[0] / 2)) - $monitor.x) / $monitor.width) | if . < 0 then 0 elif . > 1 then 1 else . end)')
target_y=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'(((($active.at[1] + ($active.size[1] / 2)) - $monitor.y) / $monitor.height) | if . < 0 then 0 elif . > 1 then 1 else . end)')
crop_x=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'((($active.at[0] - $monitor.x) / $monitor.width) | if . < 0 then 0 elif . > 1 then 1 else . end)')
crop_y=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'((($active.at[1] - $monitor.y) / $monitor.height) | if . < 0 then 0 elif . > 1 then 1 else . end)')
crop_w=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'((($active.size[0]) / $monitor.width) | if . < 0.02 then 0.02 elif . > 1 then 1 else . end)')
crop_h=$(jq -n \
--argjson active "$active_window" \
--argjson monitor "$monitor_json" \
'((($active.size[1]) / $monitor.height) | if . < 0.02 then 0.02 elif . > 1 then 1 else . end)')
"$WAYVRCTL" screen-focus-at $refresh_flag \
--crop-x "$crop_x" --crop-y "$crop_y" --crop-w "$crop_w" --crop-h "$crop_h" \
"$monitor_name" "$target_x" "$target_y"
printf '%s' "$monitor_name" > "$SCREEN_FILE"
}
event_stream() {
python3 - "$SOCKET_PATH" <<'PY'
import socket
import sys
sock_path = sys.argv[1]
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(sock_path)
buf = b""
while True:
data = sock.recv(4096)
if not data:
break
buf += data
while b"\n" in buf:
line, buf = buf.split(b"\n", 1)
sys.stdout.write(line.decode("utf-8", "replace") + "\n")
sys.stdout.flush()
PY
}
should_refresh_event() {
case "$1" in
activewindowv2*|activewindow*|fullscreen*|movewindow*|movewindowv2*|changefloatingmode*|openwindow*|closewindow*)
return 0
;;
*)
return 1
;;
esac
}
if [ "${1:-}" = "--watch" ]; then
watched_screen="$2"
while [ -f "$PID_FILE" ] && [ "$(cat "$PID_FILE")" = "$$" ]; do
event_stream | while IFS= read -r line; do
if [ ! -f "$PID_FILE" ] || [ "$(cat "$PID_FILE")" != "$$" ]; then
break
fi
if should_refresh_event "$line"; then
run_focus_command --refresh-only "$watched_screen" || true
fi
done
sleep 0.2
done
exit 0
fi
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
watched_screen="$(cat "$SCREEN_FILE" 2>/dev/null || true)"
kill "$(cat "$PID_FILE")" 2>/dev/null || true
rm -f "$PID_FILE" "$SCREEN_FILE"
if [ -n "$watched_screen" ]; then
exec "$WAYVRCTL" screen-focus-toggle "$watched_screen"
fi
exit 0
fi
run_focus_command "" || {
echo "No active window found"
exit 1
}
nohup "$0" --watch "$(cat "$SCREEN_FILE")" >/dev/null 2>&1 &
printf '%s' "$!" > "$PID_FILE"

View File

@ -0,0 +1,34 @@
#!/bin/bash
set -e
REPO_ROOT="/home/taylor/dev/open-source/real-wayvr"
WAYVRCTL="$REPO_ROOT/target/debug/wayvrctl"
if [ ! -f "$WAYVRCTL" ]; then
echo "wayvrctl not found, building..."
cd "$REPO_ROOT"
cargo build --package wayvrctl
fi
active_window=$(hyprctl -j activewindow)
if [ -z "$active_window" ] || [ "$active_window" = "{}" ]; then
echo "No active window found"
exit 1
fi
monitor_id=$(echo "$active_window" | jq -r '.monitor')
if [ -z "$monitor_id" ] || [ "$monitor_id" = "null" ]; then
echo "Could not get active monitor id"
exit 1
fi
monitor_name=$(hyprctl -j monitors | jq -r --argjson monitor_id "$monitor_id" '.[] | select(.id == $monitor_id) | .name' | head -n 1)
if [ -z "$monitor_name" ] || [ "$monitor_name" = "null" ]; then
echo "Could not resolve monitor name for id $monitor_id"
exit 1
fi
exec "$WAYVRCTL" screen-focus-toggle "$monitor_name"