mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:07:04 +01:00
g29 shifter: detect PS4 mode (046D:C260) instead of failing silently
On the 2026-09-15 and 2026-09-17 boots the wheel enumerated as 046D:C260 (PS3/PS4 selector in PS4 position, bound by hid-generic). The BPF udev rule, the ensure unit and its hotplug rule all keyed on C24F, so the unit failed once with "no G29 found" and nothing was said; 1st/3rd stayed on the firmware's bad decode until the selector was flipped a day later. - fixes/g29_wheel_mode.sh (new, sourced): classify the HID bus as native/ps4/compat/absent; root-safe desktop notification via /run/user/*/bus + sudo -u. - --ensure: absent -> exit 0; ps4/compat -> exit 1 + critical notification; native -> attach. --status prints the mode first. - g29-shifter-bpf.service: Restart=on-failure, RestartSec=5min so a notification lost before the session bus exists at boot is re-sent. - 99-g29-shifter-ensure.rules: also fires on C260. - tests/test_fix_g29_shifter.bats: 13 tests against a fake sysfs tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NVfC2T6DyvCFqse9A99zef
This commit is contained in:
parent
b3f3f55863
commit
3ae53499e6
@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "G29 shifter fix: detect PS4 mode instead of failing silently",
|
||||
"objective": "On two consecutive boots the wheel enumerated as 046D:C260 (PS4 selector), hid-generic bound it, and the HID-BPF shifter fix, its udev rule and its ensure unit all looked for C24F only - the unit failed once with 'no G29 found' and 1st/3rd stayed broken all day. Make every layer recognise the mode, tell the desktop, and keep retrying until the selector is flipped.",
|
||||
"acceptance_criteria": [
|
||||
"g29_wheel_mode.sh classifies the HID bus as native/ps4/compat/absent from sysfs and is testable against a fake tree",
|
||||
"fix_g29_shifter.sh --ensure exits 0 when no wheel is present, exits 1 and sends a critical desktop notification when the wheel is in PS4/compat mode, attaches when native",
|
||||
"g29-shifter-bpf.service retries on failure every 5 minutes; 99-g29-shifter-ensure.rules also fires on C260",
|
||||
"--status prints the wheel mode first",
|
||||
"tests/test_fix_g29_shifter.bats passes (13) and every file is under the 250-line cap"
|
||||
],
|
||||
"out_of_scope": [
|
||||
"making the BPF program work in PS4 mode (different report layout, unmeasured; no kernel driver binds C260)",
|
||||
"utils/toggle_wheel.sh, which also keys on c24f (separate power-toggle utility)"
|
||||
],
|
||||
"verifier": "bats linux_configuration/tests/test_fix_g29_shifter.bats; linux_configuration/fixes/fix_g29_shifter.sh --status"
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
{
|
||||
"intent": "The G29 shifter fix stops failing silently when the wheel is powered on with its selector in the PS4 position: the ensure unit names the mode, sends a critical desktop notification, and retries every 5 minutes until the selector is flipped; a wheel that is merely absent no longer fails the unit.",
|
||||
"scope": [
|
||||
"linux_configuration/fixes/g29_wheel_mode.sh (new, sourced): mode detection native/ps4/compat/absent, explanation strings, root-safe desktop notification",
|
||||
"linux_configuration/fixes/fix_g29_shifter.sh: find_device/--ensure/--status use the helper; udev reload moved after the ensure rule is installed",
|
||||
"linux_configuration/fixes/systemd/g29-shifter-bpf.service: Restart=on-failure, RestartSec=5min, StartLimitIntervalSec=0",
|
||||
"linux_configuration/fixes/systemd/99-g29-shifter-ensure.rules: also fires on 046D:C260",
|
||||
"linux_configuration/tests/test_fix_g29_shifter.bats (new)",
|
||||
"linux_configuration/docs/DOCS-g29-shifter.md",
|
||||
"non-goal: supporting PS4 mode itself (C260 has a different report layout and no kernel driver; only the physical selector leaves it)"
|
||||
],
|
||||
"changes": [
|
||||
"Root cause from the journal: boots on 2026-09-15 and 2026-09-17 enumerated the wheel as 046D:C260 (PS4 mode, hid-generic). Every layer keyed on C24F, so the unit exited 1 with 'no G29 found' once at boot and nothing was said; 1st/3rd stayed on the firmware decode until the selector was flipped a day later (C260 -> disconnect -> C294 -> C24F).",
|
||||
"g29_wheel_mode() classifies the HID bus; --ensure exits 0 when absent, exits 1 + notify-send -u critical (fixed id, replaces) when ps4/compat, attaches when native.",
|
||||
"Unit retries on failure every 5 min so a notification lost before the session bus exists at boot is re-sent; ensure udev rule matches C260 so a wheel powered on in PS4 mode is reported at hotplug too."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "bats linux_configuration/tests/test_fix_g29_shifter.bats",
|
||||
"result": "pass",
|
||||
"evidence": "1..13, all ok (mode classification, notify via sudo -u, --ensure exit codes 0/1 with fake sysfs, --status mode line)"
|
||||
},
|
||||
{
|
||||
"command": "linux_configuration/fixes/fix_g29_shifter.sh (install mode, on the host)",
|
||||
"result": "pass",
|
||||
"evidence": "rebuilt + installed; systemctl cat shows Restart=on-failure/RestartSec=5min; rule contains C260; udevadm trigger re-ran the unit: 'already attached; nothing to do'; --status: wheel mode: native, attached now: yes"
|
||||
},
|
||||
{
|
||||
"command": "sudo bash -c 'source g29_wheel_mode.sh; g29_notify_desktop ...' (root context, real /run/user/1000/bus)",
|
||||
"result": "pass",
|
||||
"evidence": "sent as uid 0 via sudo -u kuhy; on-screen appearance is user-confirmed"
|
||||
},
|
||||
{
|
||||
"command": "pre-commit run --files <the six changed files>",
|
||||
"result": "pass",
|
||||
"evidence": "shellcheck clean after replacing printf-stub with quoted heredoc and '! grep' with 'run ! grep'; 250-line cap, shell-library-has-tests, codespell all pass"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"PS4-mode notification repeats every 5 minutes until the selector is flipped (same notification id, so it replaces rather than stacks) - intended, but noisy if PS4 mode is ever wanted on purpose",
|
||||
"vmbox deliberately skipped: the guest has no G29, so a sandbox pass proves nothing for a physical-input fix"
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit, then re-run linux_configuration/fixes/fix_g29_shifter.sh to reinstall the previous unit + rule",
|
||||
"validate with fix_g29_shifter.sh --status and systemctl cat g29-shifter-bpf.service"
|
||||
]
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
# Logitech G29 Driving Force Shifter: 1st/3rd not engaging (HID-BPF fix)
|
||||
|
||||
Files: `fixes/g29_shifter.bpf.c` (the fix), `fixes/fix_g29_shifter.sh`
|
||||
(build/attach/install), `fixes/g29_shifter_capture.py` (grades a raw capture).
|
||||
(build/attach/install), `fixes/g29_wheel_mode.sh` (PS3/PS4 mode detection +
|
||||
desktop notification), `fixes/g29_shifter_capture.py` (grades a raw capture).
|
||||
Force feedback (a separate problem, SDL-side): `DOCS-g29-ffb.md`.
|
||||
|
||||
## Symptom
|
||||
@ -48,6 +49,22 @@ The Arch `udev-hid-bpf` package ships no headers; the script shallow-clones the
|
||||
matching upstream tag into `~/.cache/g29_shifter/` and compiles with the same
|
||||
clang flags as upstream's `meson.build`.
|
||||
|
||||
## The PS3/PS4 selector (found 2026-09-18)
|
||||
|
||||
The wheel has a physical PS3/PS4 switch and the USB identity follows it:
|
||||
|
||||
| selector | enumerates as | bound by | shifter fix |
|
||||
| -------- | ------------------------- | ------------------------ | ----------- |
|
||||
| PS3 | `046D:C294` → `046D:C24F` | hid-logitech (new-lg4ff) | attaches |
|
||||
| PS4 | `046D:C260` | hid-generic | **cannot** |
|
||||
|
||||
`C260` has a different report layout, no kernel driver knows it (new-lg4ff
|
||||
0.5.0's table stops at `c24f`), and there is no software way out — only the
|
||||
switch. On the 2026-09-15 and 2026-09-17 boots the wheel came up as `C260`;
|
||||
every layer below looked for `C24F` only, so the unit failed once with
|
||||
"no G29 found", nothing was said, and 1st/3rd were dead until the selector was
|
||||
flipped ~1 day later (the journal shows `C260` → disconnect → `C294` → `C24F`).
|
||||
|
||||
## Surviving reboots
|
||||
|
||||
Three layers, because a missed attach is silent — the wheel still works, just
|
||||
@ -57,18 +74,27 @@ with the bad decode, which is indistinguishable from "the fix stopped working":
|
||||
`/etc/udev/rules.d/99-hid-bpf-g29_shifter.rules`, which attaches on `add`.
|
||||
2. `g29-shifter-bpf.service` (`systemd/`), enabled at `multi-user.target`, runs
|
||||
`fix_g29_shifter.sh --ensure`: a no-op when attached, re-attaches when not.
|
||||
No wheel → exit 0 (layer 3 re-runs it on plug-in). Wheel present in PS4 or
|
||||
compatibility mode → exit 1 **and a critical desktop notification** to every
|
||||
user with a session bus (`g29_notify_desktop`, via `sudo -u`, because the
|
||||
unit is a root oneshot with no `$DISPLAY`); `Restart=on-failure` +
|
||||
`RestartSec=5min` keeps re-checking and re-notifying (same notification id,
|
||||
so it replaces rather than stacks) until the selector is flipped.
|
||||
3. `/etc/udev/rules.d/99-g29-shifter-ensure.rules` pulls that unit in on every
|
||||
re-enumeration, so hotplug is covered as well as boot.
|
||||
re-enumeration of `C24F` **or `C260`**, so a wheel powered on in the wrong
|
||||
mode is reported at once, and hotplug is covered as well as boot.
|
||||
|
||||
`--status` reports all of it. Verified 2026-09-12 by detaching and starting the
|
||||
unit: it re-attached and exited cleanly. That test also caught the unit failing
|
||||
with `HOME: unbound variable` — systemd has no `$HOME`, so the cache path now
|
||||
falls back.
|
||||
`--status` reports all of it, mode first. Verified 2026-09-12 by detaching and
|
||||
starting the unit: it re-attached and exited cleanly. That test also caught the
|
||||
unit failing with `HOME: unbound variable` — systemd has no `$HOME`, so the
|
||||
cache path now falls back. The mode/notification logic is covered by
|
||||
`tests/test_fix_g29_shifter.bats` against a fake sysfs tree.
|
||||
|
||||
## If the symptom returns after a boot
|
||||
|
||||
Check `--status` first. If it says attached and gears still misbehave, the
|
||||
sensor's zero has drifted and the thresholds need recalibrating — the fixed
|
||||
Check `--status` first. `wheel mode: ps4` → flip the selector. If it says
|
||||
native + attached and gears still misbehave, the sensor's zero has drifted and
|
||||
the thresholds need recalibrating — the fixed
|
||||
`Y_TOP_RELEASE` (140) assumes the stick rests near Y≈105. A drift upward of
|
||||
~+40 would park the rest position _above_ the release line, so a gear would
|
||||
engage and never let go (the car stays in gear). Measure the rest value:
|
||||
|
||||
@ -26,7 +26,6 @@ readonly CACHE_DIR="${XDG_CACHE_HOME:-${HOME:-/var/cache}/.cache}/g29_shifter"
|
||||
readonly HEADERS_DIR="$CACHE_DIR/udev-hid-bpf"
|
||||
readonly BUILD_DIR="$CACHE_DIR/build"
|
||||
readonly OBJ="$BUILD_DIR/g29_shifter.bpf.o"
|
||||
readonly VID_PID="0003:046D:C24F"
|
||||
readonly INSTALLED_OBJ="/etc/udev-hid-bpf/g29_shifter.bpf.o"
|
||||
readonly INSTALLED_RULE="/etc/udev/rules.d/99-hid-bpf-g29_shifter.rules"
|
||||
readonly UNIT_NAME="g29-shifter-bpf.service"
|
||||
@ -34,6 +33,9 @@ readonly UNIT_SRC_DIR="$SCRIPT_DIR/systemd"
|
||||
readonly ENSURE_RULE="/etc/udev/rules.d/99-g29-shifter-ensure.rules"
|
||||
readonly PACKAGES=(udev-hid-bpf bpf clang libbpf git)
|
||||
|
||||
# shellcheck source=g29_wheel_mode.sh
|
||||
source "$SCRIPT_DIR/g29_wheel_mode.sh"
|
||||
|
||||
MODE="install"
|
||||
|
||||
usage() {
|
||||
@ -77,16 +79,14 @@ build() {
|
||||
log "built $OBJ"
|
||||
}
|
||||
|
||||
# The wheel exposes two HID interfaces; only the joystick one has a descriptor.
|
||||
# Native-mode device only; any other mode is named, not just "not found".
|
||||
find_device() {
|
||||
local dev
|
||||
for dev in /sys/bus/hid/devices/"$VID_PID".*; do
|
||||
if [[ -s "$dev/report_descriptor" ]]; then
|
||||
echo "$dev"
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo "Error: no G29 ($VID_PID) with a report descriptor found in sysfs" >&2
|
||||
if dev="$(g29_sysfs_device "$G29_PID_NATIVE")"; then
|
||||
echo "$dev"
|
||||
return
|
||||
fi
|
||||
echo "Error: $(g29_mode_explanation "$(g29_wheel_mode)")" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@ -101,15 +101,19 @@ install_safety_net() {
|
||||
sed "s#__SCRIPT__#$SCRIPT_DIR/$SCRIPT_NAME#" "$UNIT_SRC_DIR/$UNIT_NAME" |
|
||||
sudo install -m 644 /dev/stdin "$unit"
|
||||
sudo install -m 644 "$UNIT_SRC_DIR/$(basename "$ENSURE_RULE")" "$ENSURE_RULE"
|
||||
sudo udevadm control --reload
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable "$UNIT_NAME"
|
||||
log "safety net installed: $unit + $(basename "$ENSURE_RULE")"
|
||||
}
|
||||
|
||||
show_status() {
|
||||
local dev
|
||||
dev="$(find_device)"
|
||||
log "device: $dev"
|
||||
local mode
|
||||
mode="$(g29_wheel_mode)"
|
||||
log "wheel mode: $mode ($(g29_mode_explanation "$mode"))"
|
||||
if [[ "$mode" == native ]]; then
|
||||
log "device: $(find_device)"
|
||||
fi
|
||||
if [[ -f "$INSTALLED_OBJ" && -f "$INSTALLED_RULE" ]]; then
|
||||
log "persistent: $INSTALLED_OBJ + $(basename "$INSTALLED_RULE")"
|
||||
else
|
||||
@ -138,6 +142,23 @@ main() {
|
||||
log "already attached; nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
case "$(g29_wheel_mode)" in
|
||||
native) ;;
|
||||
absent)
|
||||
# Not a failure: the udev rule re-runs this unit when it appears.
|
||||
log "$(g29_mode_explanation absent); nothing to attach to"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
# The wheel still "works" here -- with the bad 1st/3rd decode this
|
||||
# fix exists to remove -- so say it where it is seen. Exit 1 makes
|
||||
# the unit retry (and re-notify) until the selector is flipped.
|
||||
log "$(g29_mode_explanation "$(g29_wheel_mode)")" >&2
|
||||
g29_notify_desktop "G29 shifter fix NOT active" \
|
||||
"$(g29_mode_explanation "$(g29_wheel_mode)")"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
dev="$(find_device)"
|
||||
if [[ -f "$INSTALLED_OBJ" ]]; then
|
||||
sudo udev-hid-bpf add "$dev" "$INSTALLED_OBJ"
|
||||
@ -165,11 +186,10 @@ main() {
|
||||
fetch_headers
|
||||
build
|
||||
sudo udev-hid-bpf install --force "$OBJ"
|
||||
sudo udevadm control --reload
|
||||
dev="$(find_device)"
|
||||
# Re-trigger so the new rule attaches without a re-plug.
|
||||
sudo udevadm trigger --action=add "$dev"
|
||||
install_safety_net
|
||||
dev="$(find_device)"
|
||||
# Re-trigger so the new rules attach without a re-plug.
|
||||
sudo udevadm trigger --action=add "$dev"
|
||||
log "installed to /etc/udev-hid-bpf/ and attached to $dev"
|
||||
show_status
|
||||
;;
|
||||
|
||||
79
linux_configuration/fixes/g29_wheel_mode.sh
Executable file
79
linux_configuration/fixes/g29_wheel_mode.sh
Executable file
@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Logitech G29: which mode is the wheel in, and how to tell the desktop.
|
||||
#
|
||||
# Sourced by fix_g29_shifter.sh and tests/test_fix_g29_shifter.bats.
|
||||
#
|
||||
# The G29 has a physical PS3/PS4 selector. Only the PS3 position ends up as
|
||||
# the native 046D:C24F device that hid-logitech (new-lg4ff) binds and the
|
||||
# HID-BPF shifter fix targets. The PS4 position enumerates as 046D:C260:
|
||||
# hid-generic, a different report layout, no kernel driver, no shifter fix.
|
||||
# Nothing used to notice -- on the 2026-09-15 and 2026-09-17 boots the wheel
|
||||
# came up in PS4 mode and 1st/3rd were dead all day while every layer of the
|
||||
# safety net looked for C24F only. Details: ../docs/DOCS-g29-shifter.md.
|
||||
# ============================================================================
|
||||
|
||||
[[ -n "${_G29_WHEEL_MODE_LOADED:-}" ]] && return 0
|
||||
_G29_WHEEL_MODE_LOADED=1
|
||||
|
||||
# Overridable so tests can point at a fake sysfs tree / run dir.
|
||||
G29_HID_SYSFS="${G29_HID_SYSFS:-/sys/bus/hid/devices}"
|
||||
G29_RUN_USER_DIR="${G29_RUN_USER_DIR:-/run/user}"
|
||||
readonly G29_PID_NATIVE="0003:046D:C24F"
|
||||
readonly G29_PID_PS4="0003:046D:C260"
|
||||
readonly G29_PID_COMPAT="0003:046D:C294"
|
||||
readonly G29_NOTIFY_ID=29029 # fixed notify-send id: repeats replace, never stack
|
||||
|
||||
# Print the sysfs path of the first $1 device that has a report descriptor
|
||||
# (the wheel exposes two HID interfaces; only the joystick one has one).
|
||||
g29_sysfs_device() {
|
||||
local dev
|
||||
for dev in "$G29_HID_SYSFS/$1".*; do
|
||||
if [[ -s "$dev/report_descriptor" ]]; then
|
||||
echo "$dev"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print one of: native | ps4 | compat | absent
|
||||
g29_wheel_mode() {
|
||||
if g29_sysfs_device "$G29_PID_NATIVE" >/dev/null; then
|
||||
echo native
|
||||
elif g29_sysfs_device "$G29_PID_PS4" >/dev/null; then
|
||||
echo ps4
|
||||
elif g29_sysfs_device "$G29_PID_COMPAT" >/dev/null; then
|
||||
echo compat
|
||||
else
|
||||
echo absent
|
||||
fi
|
||||
}
|
||||
|
||||
# One line a human can act on, for any non-native mode.
|
||||
g29_mode_explanation() {
|
||||
case "$1" in
|
||||
ps4) echo "G29 is in PS4 mode (046D:C260): flip the PS3/PS4 selector on the wheel to PS3. The shifter fix, hid-logitech and FFB only work in PS3 mode." ;;
|
||||
compat) echo "G29 is stuck in compatibility mode (046D:C294): hid-logitech did not switch it to native. Check 'modinfo hid_logitech', then replug." ;;
|
||||
absent) echo "no G29 on the HID bus" ;;
|
||||
native) echo "G29 in native mode (046D:C24F)" ;;
|
||||
*) echo "unknown G29 mode '$1'" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Critical desktop notification to every user with a session bus. This runs
|
||||
# from a root oneshot at boot/hotplug, so $DISPLAY / $DBUS_SESSION_BUS_ADDRESS
|
||||
# are not set -- it walks /run/user/*/bus instead. Never fails the caller.
|
||||
g29_notify_desktop() {
|
||||
local title="$1" body="$2" bus uid user
|
||||
for bus in "$G29_RUN_USER_DIR"/*/bus; do
|
||||
[[ -e "$bus" ]] || continue
|
||||
uid="${bus#"$G29_RUN_USER_DIR"/}"
|
||||
uid="${uid%/bus}"
|
||||
user="$(id -nu "$uid" 2>/dev/null)" || continue
|
||||
sudo -u "$user" DBUS_SESSION_BUS_ADDRESS="unix:path=$bus" \
|
||||
notify-send -u critical -r "$G29_NOTIFY_ID" -a g29-shifter \
|
||||
"$title" "$body" 2>/dev/null || true
|
||||
done
|
||||
}
|
||||
@ -1,3 +1,6 @@
|
||||
# Run the safety-net unit whenever the G29 is (re)enumerated, so a hotplug is
|
||||
# covered as well as boot. Installed by fixes/fix_g29_shifter.sh.
|
||||
ACTION=="add", SUBSYSTEM=="hid", ENV{MODALIAS}=="hid:b0003g*v0000046Dp0000C24F", TAG+="systemd", ENV{SYSTEMD_WANTS}+="g29-shifter-bpf.service"
|
||||
# covered as well as boot. Matches the native PID (C24F, PS3 selector) AND the
|
||||
# PS4-mode PID (C260) so a wheel powered on in the wrong mode is reported at
|
||||
# once instead of silently falling back to the firmware's bad 1st/3rd decode.
|
||||
# Installed by fixes/fix_g29_shifter.sh.
|
||||
ACTION=="add", SUBSYSTEM=="hid", ENV{MODALIAS}=="hid:b0003g*v0000046Dp0000C24F|hid:b0003g*v0000046Dp0000C260", TAG+="systemd", ENV{SYSTEMD_WANTS}+="g29-shifter-bpf.service"
|
||||
|
||||
@ -2,14 +2,20 @@
|
||||
# Safety net for the G29 shifter HID-BPF fix (see docs/DOCS-g29-shifter.md).
|
||||
# udev-hid-bpf's own rule attaches the program on "add"; this unit re-checks
|
||||
# after boot and on every hotplug, and re-attaches if that rule ever misses.
|
||||
# It exits 0 with no wheel present, and FAILS when the wheel is present in a
|
||||
# mode the fix cannot attach to (PS4 selector -> 046D:C260); the retry below
|
||||
# keeps re-checking and re-notifying the desktop until the selector is flipped.
|
||||
# Installed by fixes/fix_g29_shifter.sh; __SCRIPT__ is substituted there.
|
||||
Description=Ensure the G29 shifter HID-BPF fix is attached
|
||||
After=systemd-udevd.service
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=no
|
||||
ExecStart=__SCRIPT__ --ensure
|
||||
Restart=on-failure
|
||||
RestartSec=5min
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
134
linux_configuration/tests/test_fix_g29_shifter.bats
Executable file
134
linux_configuration/tests/test_fix_g29_shifter.bats
Executable file
@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bats
|
||||
# Tests for fixes/g29_wheel_mode.sh and the --ensure / --status paths of
|
||||
# fixes/fix_g29_shifter.sh, against a fake sysfs tree.
|
||||
#
|
||||
# Requires: bats (extra/bats). Run with:
|
||||
# bats linux_configuration/tests/test_fix_g29_shifter.bats
|
||||
#
|
||||
# The BPF build/attach itself needs the real wheel and root, so it is not
|
||||
# covered here. What IS covered is the logic that failed silently on the
|
||||
# 2026-09-15 and 2026-09-17 boots: telling "wheel in PS4 mode" apart from
|
||||
# "no wheel", and turning that into an exit code plus a desktop notification.
|
||||
|
||||
setup() {
|
||||
REPO_DIR="$(cd -- "$(dirname -- "$BATS_TEST_FILENAME")/.." && pwd)"
|
||||
SCRIPT="$REPO_DIR/fixes/fix_g29_shifter.sh"
|
||||
export G29_HID_SYSFS="$BATS_TEST_TMPDIR/hid"
|
||||
export G29_RUN_USER_DIR="$BATS_TEST_TMPDIR/run"
|
||||
mkdir -p "$G29_HID_SYSFS" "$G29_RUN_USER_DIR"
|
||||
# sudo stub: records every invocation, never attached, never fails.
|
||||
STUB_LOG="$BATS_TEST_TMPDIR/sudo.log"
|
||||
export STUB_LOG
|
||||
mkdir -p "$BATS_TEST_TMPDIR/bin"
|
||||
cat >"$BATS_TEST_TMPDIR/bin/sudo" <<'STUB'
|
||||
#!/bin/bash
|
||||
echo "$*" >>"$STUB_LOG"
|
||||
STUB
|
||||
chmod +x "$BATS_TEST_TMPDIR/bin/sudo"
|
||||
export PATH="$BATS_TEST_TMPDIR/bin:$PATH"
|
||||
# shellcheck source=/dev/null
|
||||
source "$REPO_DIR/fixes/g29_wheel_mode.sh"
|
||||
}
|
||||
|
||||
# $1 = PID (e.g. 0003:046D:C24F), $2 = instance, $3 = "desc" to give it a
|
||||
# report descriptor. The wheel enumerates two interfaces; only one has one.
|
||||
fake_hid() {
|
||||
local dir="$G29_HID_SYSFS/$1.$2"
|
||||
mkdir -p "$dir"
|
||||
if [[ "${3:-}" == desc ]]; then
|
||||
printf 'x' >"$dir/report_descriptor"
|
||||
else
|
||||
: >"$dir/report_descriptor"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- g29_wheel_mode --------------------------------------------------------
|
||||
|
||||
@test "mode: empty sysfs is absent" {
|
||||
[ "$(g29_wheel_mode)" = absent ]
|
||||
}
|
||||
|
||||
@test "mode: native C24F with a descriptor" {
|
||||
fake_hid 0003:046D:C24F 0008
|
||||
fake_hid 0003:046D:C24F 0009 desc
|
||||
[ "$(g29_wheel_mode)" = native ]
|
||||
[ "$(g29_sysfs_device "$G29_PID_NATIVE")" = "$G29_HID_SYSFS/0003:046D:C24F.0009" ]
|
||||
}
|
||||
|
||||
@test "mode: an interface with an empty descriptor does not count" {
|
||||
fake_hid 0003:046D:C24F 0001
|
||||
[ "$(g29_wheel_mode)" = absent ]
|
||||
}
|
||||
|
||||
@test "mode: C260 is ps4 (the 2026-09-17 boot)" {
|
||||
fake_hid 0003:046D:C260 0001 desc
|
||||
[ "$(g29_wheel_mode)" = ps4 ]
|
||||
}
|
||||
|
||||
@test "mode: C294 is compat" {
|
||||
fake_hid 0003:046D:C294 0008 desc
|
||||
[ "$(g29_wheel_mode)" = compat ]
|
||||
}
|
||||
|
||||
@test "mode: native wins when a stale C294 node is also present" {
|
||||
fake_hid 0003:046D:C294 0008 desc
|
||||
fake_hid 0003:046D:C24F 0009 desc
|
||||
[ "$(g29_wheel_mode)" = native ]
|
||||
}
|
||||
|
||||
@test "explanation: ps4 names the selector and the PID" {
|
||||
run g29_mode_explanation ps4
|
||||
[[ "$output" == *"PS4 mode"* && "$output" == *"C260"* && "$output" == *"PS3"* ]]
|
||||
}
|
||||
|
||||
# --- g29_notify_desktop ----------------------------------------------------
|
||||
|
||||
@test "notify: reaches every user with a session bus, via sudo -u" {
|
||||
mkdir -p "$G29_RUN_USER_DIR/$(id -u)"
|
||||
: >"$G29_RUN_USER_DIR/$(id -u)/bus"
|
||||
g29_notify_desktop "title" "body"
|
||||
grep -q -- "-u $(id -nu) DBUS_SESSION_BUS_ADDRESS=unix:path=$G29_RUN_USER_DIR/$(id -u)/bus notify-send -u critical -r $G29_NOTIFY_ID" "$STUB_LOG"
|
||||
}
|
||||
|
||||
@test "notify: no session bus means no call and no error" {
|
||||
g29_notify_desktop "title" "body"
|
||||
[ ! -e "$STUB_LOG" ]
|
||||
}
|
||||
|
||||
# --- fix_g29_shifter.sh --ensure / --status --------------------------------
|
||||
|
||||
@test "ensure: no wheel exits 0 without notifying" {
|
||||
run "$SCRIPT" --ensure
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"no G29 on the HID bus"* ]]
|
||||
run ! grep -q notify-send "$STUB_LOG"
|
||||
}
|
||||
|
||||
@test "ensure: PS4 mode exits 1 and notifies the desktop" {
|
||||
fake_hid 0003:046D:C260 0001 desc
|
||||
mkdir -p "$G29_RUN_USER_DIR/$(id -u)"
|
||||
: >"$G29_RUN_USER_DIR/$(id -u)/bus"
|
||||
run "$SCRIPT" --ensure
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"PS4 mode"* ]]
|
||||
grep -q "notify-send -u critical .*G29 shifter fix NOT active" "$STUB_LOG"
|
||||
}
|
||||
|
||||
@test "ensure: already attached is a no-op even in PS4 mode" {
|
||||
fake_hid 0003:046D:C260 0001 desc
|
||||
cat >"$BATS_TEST_TMPDIR/bin/sudo" <<'STUB'
|
||||
#!/bin/bash
|
||||
[[ "$*" == *list-loaded* ]] && echo g29_shifter_bpf
|
||||
STUB
|
||||
run "$SCRIPT" --ensure
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"already attached"* ]]
|
||||
}
|
||||
|
||||
@test "status: reports the wheel mode first" {
|
||||
fake_hid 0003:046D:C260 0001 desc
|
||||
run "$SCRIPT" --status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"wheel mode: ps4"* ]]
|
||||
[[ "$output" != *"device:"* ]]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user