Skip to content

Add Steam Controller 2026 support and per-game mappings - #731

Merged
v0lumehi merged 6 commits into
LNPANDY:mainfrom
thehobbit85:codex/steam-controller-221
Nov 6, 2026
Merged

v0lumehi merged 6 commits into
LNPANDY:mainfrom
thehobbit85:codex/steam-controller-221

Conversation

@thehobbit85

@thehobbit85 thehobbit85 commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Fix or feature

  • Fix
  • Feature

Implements #764, approved for the v1.2.1 milestone. Carries the still-open seedds/nxnative#221 through the patch queue.

What and why

Games built with NX2Apple's pinned engine cannot use the Steam Controller (2026) directly. Carry the controller implementation from upstream PR #221 at 5e2b1c7276e0745825f17d7875ec0e56b6913f7b as patch 0078 on current main's engine pin 7249ee9f95ed728aff9a86395330d10d4c91fb5c. It includes USB-C, Bluetooth and Puck report handling, buttons and sticks, rumble, six-axis motion, trackpad modes and shared button mappings. The carrier excludes that PR's unrelated lint-workflow and shader-test changes.

Resolve the input-map and current-main conflicts while preserving fork window/screen/cursor behavior, split Joy-Con support, per-half haptics and iOS mapping consumers. Mark newly packed Mac apps with DMControllerMappings=1. The launcher exposes per-game keyboard/button mappings and Steam extras in the same preferences domain as the in-game mapping panel. Global layouts seed capable games once, so later in-game changes and resets survive the next launch. Older apps keep their existing environment-based controls. Explicit launch overrides retain precedence, and disabling saved choices prevents migration and editor writes. Document the macOS Input Monitoring setup needed for direct HID access. Update the Windows packer's source-list contract to recognize and exclude the Mac-only controller source.

Existing game apps must be rebuilt to receive the new engine. Direct raw Steam support is in the built game's host. Library/console navigation still uses Apple's GameController framework. Rumble follows the upstream amplitude translation and does not reproduce exact Switch HD waveforms.

Checked

Original implementation validation on an Apple Silicon Mac, macOS 26.6, Xcode 27.2 and Swift 6.4.1. Pre-push checks for the original test build passed on 9b04757b1e9df5b2c5c04fbf2d32d676d5f9c07b. The full Swift suite passed for arm64 before the final text/documentation follow-up and for x86_64 on the final head. Final German localization and capture checks also passed natively:

  • cd frontend && swift build && swift test: passed, 1,091 tests, 28 skipped, zero failures. Tests cover migration, malformed mapping rejection, old-build compatibility, manual override precedence, disabled saving (including retained UI actions), actual gamepad navigation into/out of the editor, and visible persistence errors.
  • python3 -m unittest discover -s frontend-qt/tests: passed, 292 tests, 26 skipped. An isolated test PATH avoids the locally installed linker interfering with discovery fixtures. The four normal-PATH failures reproduce unchanged on the base revision.
  • python3 -m unittest discover tools/tests: passed, 207 tests, 21 skipped.
  • python3 tools/engine.py check: the original 42-patch queue applied cleanly to its 777a5f6c618a38cd2d7f795f465466fb56920534 pin. Pristine sync and stale-state check passed. Refreshed-queue results are reported below.
  • Engine make -C dm/host/tests GAME=010015100B514000 -j3 run: passed, including input-map, Steam report/ownership/routing, motion and haptics fixtures. Full dmhost build passed.
  • GAME=010015100B514000 python3 dm/ios/tools/compile_check.py: passed, 265 files in each of default and framework modes. Mac packaging tests (66) and documentation tests (11) passed. clang-tidy is unavailable locally.
  • Native visual QA in English, Chinese, German, French, Italian and Swedish. The changed Controls messages are translated in German. French, Italian and Swedish use the current English fallback for new help text. Screenshots below include the new editor's default/custom states and the fixed save-error footer.

Bluetooth hardware check: a bounded standalone probe compiled the exact integrated controllers_mac.m and decoder with real IOHID APIs and logging sinks instead of a game. With macOS Input Monitoring enabled, it claimed one raw 28de:1303 BLE endpoint and observed buttons, both sticks, both trackpads, trackpad clicks, touch publication and changing gyro/accelerometer data. The 45-second active run recorded 82 mapped-button transitions and 359 touch events. All 35 feature/output writes succeeded, including the light 150 ms rumble pulse and both zero-output writes. The user confirmed feeling the rumble. The probe exited normally and released ownership. Initial probes could not discover the endpoint while macOS denied Input Monitoring. No product code was changed to obtain the successful result. This validates the driver path, not game-side consumption or calibrated motion accuracy.

Current-main refresh

Refreshed against main a19205e0f5338a146b8b88b728f27059404553a8 in merge commit 13d6eef6458e9a9ff020c26056150ff68907f946. GitHub reports no merge conflicts. The PR remains ready for review, and the local branch tracks the published fork branch with no divergence. GitHub CI is running for this head, so the results below are local checks.

The Steam carrier is now patch 0078, following main's 46 unchanged patches. Its source remains upstream PR #221 at 5e2b1c7276e0745825f17d7875ec0e56b6913f7b, with the carrier attribution corrected. The integration preserves main's split Joy-Con input, motion and per-half haptics through the shared controller registry. Added regression coverage checks two split Joy-Cons alongside a raw Steam controller, mapping/focus/Home behavior, native/locality haptics transitions, and launcher propagation of DM_JOYCON_SPLIT. The Steam report decoder, mapping and motion headers remain identical to the manually tested version.

All required commit/push hooks passed on the refreshed head:

  • Patch queue: 47 patches apply cleanly to engine pin 7249ee9f95ed728aff9a86395330d10d4c91fb5c. Pristine sync and stale-state checks also passed.
  • Native arm64 swift build && swift test: 1,105 tests, 29 skipped, zero failures.
  • Qt: 325 tests, 36 skipped. Tools: 211 tests, 22 skipped. Triage: 158 tests. All passed. The Qt check uses the isolated test PATH described above.
  • Refreshed engine: full host fixtures and focused controller, mapping, motion, Home and haptics checks passed. Complete Mac host compile/link passed. Both iOS compile modes passed with 265 files each. Packaging and documentation checks passed all 77 tests.
  • A separate code review of the conflict resolution found no integration issues. No physical-controller or gameplay rerun was performed on this refreshed head.

The other review requests, including PR separation and the remaining translations, are outside this testing/conflict refresh and are not claimed as resolved.

Manual gameplay results

Tested by the author with a Steam Controller (2026) on a Mac using the branch's NX2Apple 1.1.1-local test build from 9b04757b1e9df5b2c5c04fbf2d32d676d5f9c07b:

Game Version Bluetooth Puck USB-C
Pokémon Legends: Z-A 2.0.2 Passed Passed Passed
The Legend of Zelda: Tears of the Kingdom 1.4.3 Passed Passed Passed

The author reported that the controller worked in gameplay over all three connection methods in both games. Pokémon's version was verified from the built game's Info.plist in the supplied game folder. These manual results apply to the supplied test build, before the current main-branch refresh. They are separate from the standalone Bluetooth probe above and from automated checks of the refreshed branch.

Known mapping caveat, observed in Pokémon: holding the middle Steam button correctly opens the quit-game prompt, but that prompt uses the default physical Steam A/B layout rather than the customized in-game mapping. With Steam A mapped to Switch B and Steam B mapped to Switch A in the game, the quit prompt still uses Steam A as A and Steam B as B. This is recorded as a remaining limitation. No behavior change is being made for it in this refresh.

Screenshots

Global Controls before and after (the change explains global layouts as defaults for new per-game mappings):

Language Before After
English English Controls before English Controls after
Chinese Chinese Controls before Chinese Controls after
German German Controls before German Controls after
French French Controls before French Controls after
Italian Italian Controls before Italian Controls after
Swedish Swedish Controls before Swedish Controls after

Per-game launch options before and after:

Language Before After
English English launch options before English launch options after
Chinese Chinese launch options before Chinese launch options after

The per-game mapping editor is new. Default, customized and save-error states:

Language Default Customized Save error below scrolled standard mappings
English English default mapping English custom mapping English visible save error
Chinese Chinese default mapping Chinese custom mapping Chinese visible save error
German German default mapping German custom mapping German visible save error

Scope

  • No engine file (dm/, scripts/, docs/, ref/) is added at the repository root. The engine change is a patch in patches/, its Upstream-Status is recorded, and the queue applies cleanly.
  • The upstream implementation remains in seedds/nxnative#221, and the carrier is marked forwarded seedds/nxnative#221.
  • Changed launcher views have before/after screenshots above, with screenshots for the new editor.
  • No keys, game files or data extracted from a game are included in the change or attachments.

@github-actions github-actions Bot added documentation Improvements or additions to documentation launcher The NX2Apple launcher app windows Windows (the Qt front end) patch-queue Changes the patch queue or the upstream pin labels Nov 5, 2026
@thehobbit85
thehobbit85 marked this pull request as ready for review November 5, 2026 05:45

@v0lumehi v0lumehi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @thehobbit85 for the Steam Controller work and for the careful description of what you could and could not test.

This one is too big to take as one PR (about 6.3k added lines), and the body says it awaits a milestone issue. It changes controls in the launcher and the engine for every Mac game, so it needs an issue first.

To take it up again:

  1. Open an issue for Steam Controller 2026 support and per-game mappings. It needs a milestone that we agree on before the code.
  2. Wait for seedds/nxnative#221 to be reviewed or merged, then the engine part comes in from there. Please also fix the patch author line, which names seedds while #221 is yours.
  3. Split the launcher editor from the engine carrier patch.
  4. Test at least a few games on a Mac with the controller, and say which ones.
  5. Add French, Italian and Swedish texts, and keep controls.applied and controls.note in those languages.

Next: I will look again when the issue is agreed. It is not for v1.1.2.

@thehobbit85

Copy link
Copy Markdown
Contributor Author

@v0lumehi, landing the controller support in nxnative first was my intention from the start. I opened seedds/nxnative#221 on October 25 and have kept it updated since then, including addressing the stick-deadzone/gyro findings and incorporating the shared-controls contract discussed in that thread. It is still open without a formal review, and I do not have a clear next step from the maintainers. I am concerned that it has stalled. That is why I tried carrying the implementation here through the patch queue.

I understand the requirements for taking this up in NX2Apple: an agreed issue and milestone, separating the launcher editor from the engine carrier, named Mac gameplay tests, and completing the French, Italian and Swedish texts while retaining the existing keys. The carrier description also incorrectly attributes the PR to seedds, and that wording needs correcting. The Bluetooth probe documented here is not a replacement for the gameplay testing you requested.

Could you point me to the right upstream maintainer or help establish a concrete review path for #221? What technical changes or additional test evidence are still needed before it can be reviewed and merged? If its size is the obstacle upstream too, would a smaller controller transport/motion/rumble PR followed by the mapping UI be preferable? I am willing to work through a specific checklist, but keeping the PR current without knowing what is blocking review leaves me unsure how to move it forward.

A later agreed NX2Apple milestone is fine. My goal is to get the controller implementation accepted upstream first, then build the appropriately scoped launcher support on that foundation.

@v0lumehi

v0lumehi commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

Thank you @thehobbit85. I understand your point about landing the engine part in nxnative first, and I see seedds/nxnative#221 is open and updated. We want to take this into a release.

I opened #764 for it and put it into v1.2.1. An issue in a milestone is the go for this PR. The point is that neither of us has a Steam Controller (2026), so we cannot test it. We can take it if you stay with it. This is what I ask:

  1. Write in the PR which hardware and connection types you tested (Bluetooth, USB-C, the Puck) and which games you played with it.
  2. Answer the questions and bug reports about it, also after the release.
  3. Link Steam Controller 2026 support and per-game mappings (#731) #764 in the PR body, and keep the patch header Upstream-Status current with A Mac game opens on the display of the launcher's window (DM_SCREEN) #221.
  4. Fix the points in my review comment when you push.

If you can do this, I merge it when CI is green and the points are done.

@v0lumehi v0lumehi added this to the v1.2.1 milestone Nov 5, 2026
@v0lumehi v0lumehi added the enhancement New feature or request label Nov 5, 2026
@v0lumehi

v0lumehi commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

@thehobbit85, thank you for the work you have sent to us and to seedds/nxnative. I read a sample of it. The causes are right, the tests are there, and you say what you did not test.

@LNPANDY and I are looking for a few strong people to build this with us. Would you be interested? If yes, tell us how you like to be reached (Discord, Telegram or here) and we talk.

@thehobbit85

Copy link
Copy Markdown
Contributor Author

@v0lumehi Thank you so so much for the offer. I am VERY interested in joining. Both Discord and Telegram works for me. If you could please send me an email to thehobbit85@gmail.com, and I will send you my contact information.

P.S, The #221 PR for seedds/nxnative was tested by both me and another contributor using Legends ZA and Totk, but as there are a few changes/addition between that PR and the one I am opening here, I am currently in the process of building a couple of games to run tests for the PR here. I am going to report the result of my findings very very soon, just waiting on some builds to finish.

@v0lumehi

v0lumehi commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

@thehobbit85 I sent you an email

@thehobbit85

thehobbit85 commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

@v0lumehi, following up with the requested gameplay results. I tested the Steam Controller (2026) on a Mac with the local build from this PR (9b04757b, NX2Apple 1.1.1-local):

Game Version Bluetooth Puck USB-C
Pokémon Legends: Z-A 2.0.2 Passed Passed Passed
The Legend of Zelda: Tears of the Kingdom 1.4.3 Passed Passed Passed

Both games worked successfully with all three connection methods. The Pokémon version is confirmed by the built app's metadata.

One caveat from Pokémon: holding the middle Steam button opens the quit-game prompt as intended, but its A/B actions use the default physical Steam layout rather than the game's custom mapping. I had Steam A mapped to Switch B and Steam B mapped to Switch A in-game, while the quit prompt still used Steam A as A and Steam B as B. I am documenting this separately and preserving that behavior for now.

The PR description now links the approved #764 / v1.2.1 milestone and replaces the earlier no-gameplay/USB-C-and-Puck-untested statements with these results.

The branch refresh is now published as 13d6eef6, merging main a19205e0. GitHub reports no merge conflicts, and branch tracking is up to date. The Steam carrier is renumbered to 0078 and reconciled with main's split Joy-Con support, retaining the tested Steam decoder, mapping and motion code. The quit-prompt caveat above remains unchanged.

All required local hooks passed: native arm64 Mac build/tests (1,105 tests, 29 skipped), Qt (325, 36 skipped), tools (211, 22 skipped), triage (158), and the 47-patch queue. Refreshed engine host/controller/motion/haptics checks, the complete Mac host build, both iOS compile modes (265 files each), and 77 packaging/docs tests also passed. GitHub CI is running for the new head.

The manual gameplay results above belong to the supplied 9b04757b test build, before this refresh. No new physical-controller or gameplay run is claimed for 13d6eef6. The other review requests, including PR separation and remaining translations, are not claimed as resolved by this testing/conflict update.

@v0lumehi v0lumehi mentioned this pull request Nov 5, 2026
5 of 6 tasks
@v0lumehi
v0lumehi merged commit c9cb63b into LNPANDY:main Nov 6, 2026
17 checks passed
Wilderness01 pushed a commit to Wilderness01/NX2Apple that referenced this pull request Nov 6, 2026
The Steam Controller and global controller texts from LNPANDY#731 were English in fr, it and sv. They are translated now. The French test lists the Steam names that stay as they are.
@v0lumehi v0lumehi mentioned this pull request Nov 6, 2026
1 task done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request launcher The NX2Apple launcher app patch-queue Changes the patch queue or the upstream pin windows Windows (the Qt front end)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants