testsAndMisc/linux_configuration/fixes/fix_controller.sh
Krzysztof kuhy Rudnicki 4f2b0ccdf7 Dissolve linux_configuration/scripts/ and single_use/
Two levels of pure taxonomy carrying no information, and a 2-directory
cap leaves no room for them. Categories now sit directly under
linux_configuration/: features, fixes, utils, misc, fresh-install,
installers, diagnostics, gaming, boot_recovery, periodic_background, lib,
meta, tests. A script's own lib/ and tests/ are exempt from the cap, so
those stay put.

Depth violations 248 -> 139; roughly 40 of the remainder are inside
digital_wellbeing and hosts, deferred until the guard-lib repoint.

The moves are the easy half. What breaks is every path that counted
levels, and three classes of it had to be found by resolving paths rather
than reading diffs:

  - 52 relative `source` lines for lib/common.sh and lib/android.sh. The
    correct prefix differs per file: 40 were ../../ and 12 were ../../../,
    so a blanket replace would have fixed one set and broken the other.
  - REPO_ROOT in 7 fixes/lib/tests harnesses and setup_syncyomi_guard.sh,
    each walking up four levels from a directory now two levels shallower.
    This caused a real regression -- the fixes suite went red with
    "log_warn: command not found" -- caught by running it and comparing
    against the pre-move baseline worktree.
  - conftest.py's sys.path entries, which took the pytest gate down with
    14 collection errors, and the `# shellcheck source=` directives, which
    took shellcheck down with SC1091.

16 symlinks in $HOME broke, including the entire i3blocks bar. All
repointed, and cpu_monitor/memory/disk/time/volume were run to confirm
they emit real output rather than merely resolving.

Verified: 1798 tests pass at 100% coverage, 8 of 9 lib/tests suites pass
(the ninth needs the coverage jail, identically at baseline), pre-commit
is clean, and the two untracked secret configs are still ignored after
their gitignore paths changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 18:53:30 +02:00

197 lines
6.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Fix/diagnose Xbox One (and 360) controllers on Arch Linux over USB.
# - Detects the device, relevant kernel modules, and evdev/joystick nodes
# - Loads safe modules (xpad, joydev) if missing
# - Shows dmesg hints and permission status
# - Suggests next steps (packages to test: evtest, joystick; drivers for BT/dongle)
#
# Conventions: sudo re-exec, idempotent, log with timestamps.
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
LOG_FILE="/var/log/xbox-controller-fix.log"
timestamp() { date '+%Y-%m-%d %H:%M:%S%z'; }
log() {
local msg="$1"
echo "[$(timestamp)] $msg"
if [[ -w "$(dirname "$LOG_FILE")" ]] || [[ ! -e $LOG_FILE && -w /var/log ]]; then
echo "[$(timestamp)] $msg" >> "$LOG_FILE" || true
fi
}
require_root() {
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
echo "$SCRIPT_NAME needs root to load kernel modules and read some diagnostics. Re-executing with sudo..."
exec sudo -E bash "$0" "$@"
fi
}
print_header() {
echo "=== $1 ==="
}
detect_distro() {
if command -v pacman > /dev/null 2>&1; then
echo "arch"
else
echo "other"
fi
}
list_input_nodes() {
print_header "Input device nodes"
if [[ -d /dev/input/by-id ]]; then
# Robust listing with proper handling of special characters
local count=0
while IFS= read -r -d '' f; do
stat -c '%A %a %U:%G %N' "$f" 2> /dev/null || true
count=$((count + 1))
[[ $count -ge 120 ]] && break
done < <(find /dev/input/by-id -maxdepth 1 -mindepth 1 -print0 2> /dev/null)
else
echo "/dev/input/by-id not present"
fi
echo
if compgen -G "/dev/input/*js*" > /dev/null; then
ls -l /dev/input/js* || true
else
echo "No legacy /dev/input/js* nodes (joydev) present. That's okay for most apps using evdev."
fi
echo
}
show_lsusb() {
print_header "USB devices (filtered)"
if command -v lsusb > /dev/null 2>&1; then
lsusb | grep -Ei 'microsoft|xbox|045e:' || {
echo "No Microsoft/Xbox device found via lsusb."
true
}
else
echo "lsusb not found (usbutils). Install usbutils for richer diagnostics."
fi
echo
}
show_modules() {
print_header "Kernel modules state"
lsmod | grep -E '(^|\s)(xpad|joydev|hid_microsoft|hid_generic|hid_xpadneo|xone)(\s|$)' || echo "No matching modules currently loaded."
echo
}
modprobe_safe() {
local mod="$1"
if ! lsmod | grep -q "^${mod}\b"; then
if modprobe "$mod" 2> /dev/null; then
log "Loaded module: $mod"
else
log "Module $mod not loaded (may be built-in or unavailable)."
fi
fi
}
show_dmesg_hints() {
print_header "Recent kernel messages (xpad/xbox/hid/input)"
dmesg --color=never | grep -Ei 'xbox|xpad|045e:|Microsoft|input:.*gamepad|event.*joystick|hid.*(xbox|microsoft)' | tail -n 200 || true
echo
}
check_permissions() {
print_header "Permissions on event/joystick nodes"
local any=0
for path in /dev/input/by-id/*-event-joystick /dev/input/js*; do
if [[ -e $path ]]; then
any=1
printf '%s -> ' "$path"
local dev
dev=$(readlink -f "$path" 2> /dev/null || echo "$path")
stat -c '%A %a %U:%G %n' "$dev" 2> /dev/null || true
fi
done
if [[ $any -eq 0 ]]; then
echo "No event-joystick or js nodes found to check permissions."
fi
echo
if [[ $(detect_distro) == "arch" ]]; then
echo "On Arch, prefer TAG+\"uaccess\"-based access over adding users to the 'input' group."
echo "If access is denied in apps, install: pacman -S game-devices-udev (provides modern udev rules)."
fi
echo
}
suggest_tests() {
print_header "Next steps / tests"
echo "- Test evdev: install 'evtest' and run: evtest /dev/input/by-id/*-event-joystick"
echo "- Test joystick API: install 'joystick' (jstest) and run: jstest /dev/input/js0 (if present)"
echo "- For force feedback test (rumble): install 'linuxconsole' (fftest): fftest /dev/input/by-id/*-event-joystick"
echo
echo "Steam users: Ensure Steam Input settings match your use case. If rumble fails in SDL titles, try: SDL_JOYSTICK_HIDAPI=0"
echo
echo "If you are actually using Bluetooth: consider xpadneo (AUR: xpadneo-dkms)."
echo "If you are using the official wireless USB adapter: consider xone (AUR: xone-dkms and xone-dongle-firmware)."
echo
}
main() {
require_root "$@"
print_header "${SCRIPT_NAME} starting"
log "Kernel: $(uname -r) | Distro: $(detect_distro)"
show_lsusb
show_modules
# Load common modules safely (idempotent)
modprobe_safe usbhid
modprobe_safe xpad
modprobe_safe joydev
# If xpad failed to load and kernel says it's a module, but it's not present, hint about out-of-sync modules
if ! lsmod | grep -q '^xpad\b'; then
if command -v zcat > /dev/null 2>&1 && [[ -r /proc/config.gz ]] && zcat /proc/config.gz 2> /dev/null | grep -q '^CONFIG_JOYSTICK_XPAD=m'; then
if ! find "/lib/modules/$(uname -r)" -type f -name 'xpad*.ko*' 2> /dev/null | grep -q .; then
log "xpad is configured as a module but missing under /lib/modules/$(uname -r). Your kernel modules may be out-of-sync or incomplete."
if [[ $(detect_distro) == "arch" ]]; then
echo "Arch hint: reinstall the matching kernel package (e.g. 'sudo pacman -S linux' or your variant like linux-zen) and reboot."
else
echo "Hint: reinstall your running kernel's modules then reboot."
fi
echo
fi
fi
fi
list_input_nodes
check_permissions
show_dmesg_hints
# Simple heuristic: do we see an Xbox/Microsoft event-joystick?
if compgen -G "/dev/input/by-id/*-event-joystick" > /dev/null; then
local found_label=0
for f in /dev/input/by-id/*-event-joystick; do
[[ -e $f ]] || continue
if printf '%s' "$(basename "$f")" | grep -Eqi 'xbox|microsoft|controller|wireless'; then
found_label=1
break
fi
done
if ((found_label == 1)); then
log "Controller event device detected."
else
log "Event-joystick device(s) exist but not obviously Xbox-labelled. Still likely usable."
fi
else
log "No -event-joystick device found. If the controller vibrated but no input node exists, check the cable and try another USB port/cable."
log "Also check dmesg for descriptor errors; for Xbox 360 Play&Charge cable: note it only charges and does not carry input."
fi
suggest_tests
print_header "Done"
}
main "$@"