Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/99-valve-index-reboot.rules
Original file line number Diff line number Diff line change
@@ -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"
88 changes: 61 additions & 27 deletions src/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

# ---------------------------------------------------------------------------
Expand All @@ -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=""
Expand All @@ -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
Expand All @@ -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"
Comment on lines +82 to +96

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

cleanup tmp files with trap

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 15febe9. Added a _TMP_FILES array that records each temp path immediately after mktemp, and an EXIT trap calling _cleanup_tmp to rm -f all tracked files when the script exits (normally or via die).

}

# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -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/tty

info "Done! The udev rule was installed via the AUR package."
info "Done! The files were installed via the AUR package."
}

# ---------------------------------------------------------------------------
Expand All @@ -178,19 +197,34 @@ install_aur() {
install_manual() {
setup_sudo

local rule_src
rule_src="$(find_rule_file)"
[[ -f "$rule_src" ]] || die "Rule file not found: $rule_src"
step "Rule file found: $rule_src"
if ! command -v systemctl &>/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
Comment on lines +222 to 225

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

  • Detect systemctl and show a clear error if not present
  • Skip systemd steps when systemctl is missing

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit ffefdba. Added a systemctl check at the top of install_manual() — if systemctl is not found, the script dies early with a clear error message explaining that systemd is required (due to SYSTEMD_WANTS in the udev rule), which also skips all systemd-dependent steps.


info "Done! Reconnect your Valve Index to apply the fix."
info "Done! Reconnect your Valve Index or resume from sleep to apply the fix."
}

# ---------------------------------------------------------------------------
Expand All @@ -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/tty
read -rp " Install manually anyway? [y/N] " yn </dev/tty
echo
case "$yn" in
[Yy]*) install_manual ;;
Expand Down
8 changes: 8 additions & 0 deletions src/valve-index-hid-reboot
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
case "$1" in
post)
rm -f /run/.valve-index-rebooted
sleep 2
/usr/local/sbin/valve-index-hid-reboot.sh
;;
esac
28 changes: 28 additions & 0 deletions src/valve-index-hid-reboot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh
set -eu

VID="28de"
PID="2300"
FLAG="/run/.valve-index-rebooted"

for dev in /dev/hidraw*; do
[ -e "$dev" ] || continue
sys="/sys/class/hidraw/$(basename "$dev")/device"

v="$(cat "$sys/idVendor" 2>/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
8 changes: 8 additions & 0 deletions src/valve-index-hid-reboot@.service
Original file line number Diff line number Diff line change
@@ -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'