#!/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 "$@"