diff --git a/src/99-valve-index-reboot.rules b/src/99-valve-index-reboot.rules index cbf3dff..eb67750 100644 --- a/src/99-valve-index-reboot.rules +++ b/src/99-valve-index-reboot.rules @@ -1,6 +1,6 @@ # Valve Index HMD: fix blank EDID -# Works around blank EDID bug that makes the kernel see the HMD as a 640x480 monitor. -# Sends a HID reboot command once per boot when the device is enumerated. -# /tmp is cleared on boot so the flag resets automatically. +# Trigger systemd service on device add. -KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="28de", ATTRS{idProduct}=="2300", ACTION=="add", RUN+="/bin/sh -c '(sleep 2; test -f /tmp/.valve-index-rebooted || { printf \"\\x16\\x01\" | cat - /dev/zero | head -c 64 > /dev/%k && touch /tmp/.valve-index-rebooted; }) &'" +ACTION=="add", SUBSYSTEM=="hidraw", KERNEL=="hidraw*", \ + ATTRS{idVendor}=="28de", ATTRS{idProduct}=="2300", \ + TAG+="systemd", ENV{SYSTEMD_WANTS}+="valve-index-hid-reboot@%k.service" diff --git a/src/install.sh b/src/install.sh index 17ef012..e148150 100755 --- a/src/install.sh +++ b/src/install.sh @@ -7,8 +7,21 @@ set -euo pipefail # Config # --------------------------------------------------------------------------- RULE_FILE="99-valve-index-reboot.rules" +SCRIPT_FILE="valve-index-hid-reboot.sh" +SERVICE_FILE="valve-index-hid-reboot@.service" +HOOK_FILE="valve-index-hid-reboot" + RULE_DST="/etc/udev/rules.d/$RULE_FILE" -RULE_RAW_URL="https://raw.githubusercontent.com/MiguVT/fixvr/main/src/$RULE_FILE" +SCRIPT_DST="/usr/local/sbin/$SCRIPT_FILE" +SERVICE_DST="/etc/systemd/system/$SERVICE_FILE" +HOOK_DST="/etc/systemd/system-sleep/$HOOK_FILE" # Universal, distro-agnostic + +RAW_BASE="https://raw.githubusercontent.com/MiguVT/fixvr/main/src" +RULE_RAW_URL="$RAW_BASE/$RULE_FILE" +SCRIPT_RAW_URL="$RAW_BASE/$SCRIPT_FILE" +SERVICE_RAW_URL="$RAW_BASE/$SERVICE_FILE" +HOOK_RAW_URL="$RAW_BASE/$HOOK_FILE" + AUR_PKG="fixvr-git" # --------------------------------------------------------------------------- @@ -30,6 +43,10 @@ die() { echo -e "${RED}[✗]${NC} $*" >&2; exit 1; } # Helpers # --------------------------------------------------------------------------- SUDO="" +_TMP_FILES=() # tracks temp files downloaded by find_or_download +_cleanup_tmp() { [[ ${#_TMP_FILES[@]} -gt 0 ]] && rm -f "${_TMP_FILES[@]}"; } +trap '_cleanup_tmp' EXIT + setup_sudo() { if [[ $EUID -eq 0 ]]; then SUDO="" @@ -40,17 +57,19 @@ setup_sudo() { fi } -# Locate the rule file relative to this script (works when called from anywhere) -# Falls back to downloading from GitHub when run via curl | bash -find_rule_file() { +# Locate a file relative to this script, fallback to download from GitHub +find_or_download() { + local filename="$1" + local url="$2" + local script_dir script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" local candidates=( - "$script_dir/$RULE_FILE" # running from src/ - "$script_dir/../src/$RULE_FILE" # running from repo root - "$(pwd)/src/$RULE_FILE" # cwd is repo root - "$(pwd)/$RULE_FILE" # cwd is src/ + "$script_dir/$filename" # running from src/ + "$script_dir/../src/$filename" # running from repo root + "$(pwd)/src/$filename" # cwd is repo root + "$(pwd)/$filename" # cwd is src/ ) for f in "${candidates[@]}"; do @@ -60,21 +79,21 @@ find_rule_file() { fi done - # Not found locally (e.g. run via curl | bash) — download from GitHub - warn "Rule file not found locally, downloading from GitHub…" >&2 - local tmp_rule - tmp_rule="$(mktemp "/tmp/${RULE_FILE}.XXXXXX")" + warn "File $filename not found locally, downloading from GitHub…" >&2 + local tmp_file + tmp_file="$(mktemp "/tmp/${filename}.XXXXXX")" + _TMP_FILES+=("$tmp_file") if command -v curl &>/dev/null; then - curl -fsSL "$RULE_RAW_URL" -o "$tmp_rule" || die "Failed to download rule file from GitHub." + curl -fsSL "$url" -o "$tmp_file" || die "Failed to download $filename." elif command -v wget &>/dev/null; then - wget -qO "$tmp_rule" "$RULE_RAW_URL" || die "Failed to download rule file from GitHub." + wget -qO "$tmp_file" "$url" || die "Failed to download $filename." else - die "Rule file not found locally and neither curl nor wget is available." + die "Neither curl nor wget is available to download $filename." fi - [[ -s "$tmp_rule" ]] || die "Downloaded rule file is empty." - printf '%s\n' "$tmp_rule" + [[ -s "$tmp_file" ]] || die "Downloaded $filename is empty." + printf '%s\n' "$tmp_file" } # --------------------------------------------------------------------------- @@ -164,12 +183,12 @@ install_aur() { info "Using AUR helper: $aur_helper" step "Installing $AUR_PKG…" - + # When piped via `curl | bash`, standard input is hijacked. # We must explicitly reconnect stdin to the terminal so the user can interact. "$aur_helper" -S "$AUR_PKG" /dev/null; then + die "systemd is required for this fix (the udev rule uses SYSTEMD_WANTS).\n Please use a systemd-based distro or install the files manually." + fi + + local rule_src script_src service_src hook_src + rule_src="$(find_or_download "$RULE_FILE" "$RULE_RAW_URL")" + script_src="$(find_or_download "$SCRIPT_FILE" "$SCRIPT_RAW_URL")" + service_src="$(find_or_download "$SERVICE_FILE" "$SERVICE_RAW_URL")" + hook_src="$(find_or_download "$HOOK_FILE" "$HOOK_RAW_URL")" step "Installing udev rule to $RULE_DST…" $SUDO install -m 644 -o root -g root "$rule_src" "$RULE_DST" - step "Reloading udev rules…" + step "Installing script to $SCRIPT_DST…" + $SUDO install -m 755 -o root -g root "$script_src" "$SCRIPT_DST" + + step "Installing systemd service to $SERVICE_DST…" + $SUDO install -m 644 -o root -g root "$service_src" "$SERVICE_DST" + + step "Installing systemd sleep hook to $HOOK_DST…" + $SUDO install -m 755 -o root -g root "$hook_src" "$HOOK_DST" + + step "Reloading systemd and udev…" + $SUDO systemctl daemon-reload $SUDO udevadm control --reload-rules $SUDO udevadm trigger --action=add --subsystem-match=hidraw - info "Done! Reconnect your Valve Index to apply the fix." + info "Done! Reconnect your Valve Index or resume from sleep to apply the fix." } # --------------------------------------------------------------------------- @@ -208,10 +242,10 @@ echo if is_nixos; then warn "NixOS detected. Manual file installation won't persist across rebuilds." echo - echo -e " Add the udev rule declaratively in your NixOS configuration instead." + echo -e " Add the files declaratively in your NixOS configuration instead." echo -e " See: ${CYAN}https://fixvr.miguvt.com/install#nixos${NC}" echo - read -rp " Install manually to /etc/udev/rules.d/ anyway? [y/N] " yn /dev/null || true)" + p="$(cat "$sys/idProduct" 2>/dev/null || true)" + v="${v#0x}" + p="${p#0x}" + + if [ "$v" = "$VID" ] && [ "$p" = "$PID" ]; then + if printf '\x16\x01' | cat - /dev/zero | head -c 64 > "$dev"; then + : > "$FLAG" + exit 0 + else + echo "Failed to write reboot command to $dev" >&2 + exit 1 + fi + fi +done + +exit 0 diff --git a/src/valve-index-hid-reboot@.service b/src/valve-index-hid-reboot@.service new file mode 100644 index 0000000..54eba59 --- /dev/null +++ b/src/valve-index-hid-reboot@.service @@ -0,0 +1,8 @@ +[Unit] +Description=Valve Index HID reboot for /dev/%I +After=systemd-udevd.service +ConditionPathExists=/dev/%I + +[Service] +Type=oneshot +ExecStart=/usr/bin/flock /run/valve-index-hid-reboot.lock /bin/sh -c 'sleep 2; if ! test -f /run/.valve-index-rebooted; then /usr/local/sbin/valve-index-hid-reboot.sh; fi'