feat(input): log SDL controller GUID and active mapping #2678
Closed
github-sync
wants to merge 2 commits from
github/pr/2678 into main
pull from: github/pr/2678
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2678
Loading…
Reference in a new issue
No description provided.
Delete branch "github/pr/2678"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this is for: make controller-mapping reports such as #2546 answerable from
hookshot_log.txt, without changing the player's working bindings. What is measured: Linux game-free checks of the real SDL2/SDL3 adapter and connection-log call, with GCC 16.2.1 and Clang 23.1.3: 64 checks pass with each compiler; the same fixture fails against the original connection log. Both changed host C files also pass Windows-target syntax checks without warnings or errors. Not measured: a real controller on Windows, a game run, or a fix for the reporter's physical button mapping.Refs #2546 and #2504.
Problem
The new attachment in #2546 identifies
Core (Plus) Wired Controllerthrough SDL3 and one connected SDL controller (lines 171, 216, 221), but does not record its GUID or active mapping. The reporter has since confirmed a basic PowerA wired Switch-licensed pad with no mode switch. A device name alone cannot distinguish the mapping routes involved (reporter's comment).There is a concrete reason to collect the actual mapping rather than guess: SDL associates
20d6:a711with both the PowerA Wired Controller Plus and its GameCube-style variant (SDL device list). Its GameCube-classified HIDAPI route can account for the exact reported permutation. This PR does not assume that ID/backend for the reporter and does not install a mapping workaround.What changed
host/hos_sdl.c: optional diagnostic exports for SDL2 and SDL3. At a successful connection, obtain the borrowed joystick's hardware GUID and the active gamepad mapping, then release the mapping string through the same loaded library'sSDL_free.host/hos_sdl.handhost/pad_sdl.c: call one connection-log helper at the existing successful-connect site. Keep the controller name, gyroscope suffix and Steam Input suffix.tests/test_sdl_pad_diagnostics.py: compile the actual loader, adapter and connection call with fake library exports. Cover a known mapping, no returned mapping, zero GUID, missing optional functions, missing SDL2 allocator, SDL3 missing joystick accessor, correct allocation/free ownership, and bounded output through the production-sized log sink.--sourcecompares the same assertions with another checkout's source.docs/development.md: describe the fields, bounds, status meanings and privacy qualification.Bindings, input polling, prompt selection, controller enumeration, rumble and mapping-file loading are unchanged. There is no renderer API change and no new setting. Diagnostic queries run on connection, not every controller poll.
What the statuses mean
guid=<32 hex digits>: SDL's hardware GUID; a zero result isunknown, unavailable accessors areunavailable.mapping=available: SDL returned an active mapping string.mapping=none: no string was returned; this is not proof that the device is unmapped.mapping=unavailable: the query or required allocator export is absent.Neither success nor the mapping name identifies where the mapping originated. The SDL3 API and SDL2 API return a mapping or NULL, not a provenance flag.
Names are limited to 128 bytes. Mapping text is capped at 1024 bytes total, with a truncation marker; it follows the connection line in GUID-labelled parts of at most 512 bytes. That keeps the captured mapping intact inside
hos_log's existing 1024-byte per-message buffer rather than enlarging the global logger.Privacy
The GUID and the mapping describe a device model's button layout; no serial, path, user or account call is made. A mapping the player supplied himself, by environment or file, is logged as he wrote it, within the length limits.
SDL constructs these hardware GUIDs from bus/vendor/product/version, manufacturer/product names and driver bytes, with serial information stored separately (SDL3 GUID construction, HIDAPI serial separation). The active mapping serializer writes the GUID, supplied mapping name/body and platform metadata (SDL3, SDL2). Custom mapping text can therefore contain whatever the player put in it; clipping is not privacy redaction.
How it was tested
Arch Linux, kernel
7.2.15-1-omarchy, GCC16.2.1 20260810, Clang23.1.3, Python 3.12. No GPU, window, controller or performance measurement was used. Initial baseedaef2a99; final commite0864483on87991bc8b. The final rebase adds only an upstream team-chat script/test change; all five changed file blobs are identical to the tested pre-rebase version.PASS: 64 checks, 0 failuresFAIL: 60 checks, 31 failuresFAIL: 60 checks, 4 failuresThe failures are executable assertions, not missing-symbol compile failures. The original source still emits only the old connection message; the earlier implementation loses the tail and truncation note through the real-sized logger. Dependent chunk-byte assertions are entered only when both chunks exist, hence the different reached-check totals on the failing versions.
Example fixture output (authored device data, not the reporter's controller):
Reproduction:
Both changed host C files,
hos_sdl.candpad_sdl.c, pass Clang'sx86_64-w64-mingw32syntax-only check using llvm-mingw headers,-Wall -Wextra, with zero warnings/errors. This is a syntax check, not a Windows link or runtime test. Existing Steam-pad checks pass with both compilers, and the SDL library regression fixture passes.One full suite ran before the final log-chunk correction, on intermediate
aba3eb1a:The sole failure was
nx2/test_texture_phi_dominance.py:clang++compiling unchangedemit_spirv_image.cppwas terminated bySIGKILL. Its cause was not established; it was not a failed shader assertion, and no baseline/OOM explanation is claimed. The failed result is retained, not rerun away. Load average was 16.98 at entry and 37.45 at exit. The final chunk correction was then tested with both compilers and both Windows syntax passes, not another full suite; final focused load was 23.94.The final branch passes the mechanical PR checks on current main: author/committer identity, assistance trailers, conventional title, test included, and no game data/build output. No core dumps were found at the recorded check.
Not tested
Risks and overlaps
The new exports are optional and cannot make controller support fail to load. The mapping getter is not called if its matching allocator is unavailable. The GUID ABI mirrors SDL2/SDL3's 16-byte byte-array struct returned by value; the borrowed joystick is not closed.
The overlap scan found 44 open PRs sharing a changed file, mostly documentation, and no new textual conflicts. Shared-code hunks were read for #1543, #1600, #1872, #2252, #2459 and #2566. The scan preceded the small logger-boundary correction; the final branch's current-main merge check also passes.
#2252 needs semantic integration even though the text merges: it introduces non-SDL
pad_sw2handles and guards the existing SDL getters. If both changes land, the new diagnostic helper must likewise avoid passing those handles to SDL. That path is absent from this PR's main base; no speculative compatibility shim is included here.This is diagnostics for #2546, not a controller-map fix. The next useful reporter evidence is one startup log from a build containing the change, with the pad connected.
Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]
The fixture counted mapping requests in a variable named gets. MinGW's stdio.h still declares gets(), so the Windows checks failed to compile it ("'gets' redeclared as different kind of symbol") and the new test never ran there. The counter is named takes. Tested: the generated fixture compiled for the Windows target (clang --target=x86_64-w64-mingw32, mingw-w64 headers, -std=c11 -Wall -Wextra -Werror, syntax only): 11 errors before, none now. Linux, GCC 16.2.1 and Clang 23.1.3: 64 checks, 0 failures, as before. Not tested: the test run on Windows itself (the project's Windows job does that). Assisted-by: Claude Fable 5.2 Co-Authored-By: Claude Fable 5.2 <[email protected]>857bb9584dto0caab6bc82This has not been run with a real controller: this PC has none, and the checks here drive the connection code with stand-ins for SDL. Under the evidence rule merged last night (#2712) that is a gap, so I have asked the team's test queue for it: request T-6RQB, any controller on Windows or Linux, a one-minute check (start the game with the pad plugged in and post the
hid: controller "…" connected (SDL)… guid=…; mapping=…; mapping-name="…"line fromhookshot_log.txt).The request cannot be claimed until a maintainer adds the
testing wantedlabel here; could one of you add it? A third-party pad (PowerA, 8BitDo) is the most useful, since this line is what #2546 needs to tell a wrong mapping from a wrong prompt set.I merged this into current main and ran tests/test_sdl_pad_diagnostics.py (64 checks, 0 failures) and
run_all.py sdl pad steam controller(4 passed). CI is green.I checked the loader changes in host/hos_sdl.c.
JoystickGUID,PadMapping, SDL3'sGetJoystickand SDL2'sFreeare all looked up with plainGetProcAddress, notGET, so an older SDL without them still loads. Assigningf.GetJoystickunder SDL3 is harmless becausehos_sdl_pad_idtakes theGetGamepadIDpath whenversion == 3. LoadingSDL_freefor SDL2 doesn't change the two existingFreecalls, which are SDL3-only paths.hos_sdl_pad_log_connection(:622) frees the mapping with the same library's allocator, never closes the borrowed joystick, and keeps each log line under hos_log's 1024 bytes. A 16-byte struct returned by value has the same ABI on both sides.As you noted, if #2252 lands its non-SDL
pad_sw2handles must not reach this helper. Whichever merges second should add that guard. A real pad's log line (the requested T-6RQB) would still be good to have, but it doesn't block a diagnostics-only change.