Repository navigation
Add per-player controller type configuration on macOS and iOS - #1348
Draft
dandelion711 wants to merge 1 commit into
Draft
dandelion711 wants to merge 1 commit into
dandelion711 wants to merge 1 commit into
Conversation
dandelion711
marked this pull request as draft
November 6, 2026 11:07
dandelion711
force-pushed
the
feat/apple-controller-configuration
branch
from
November 8, 2026 16:46
2452673 to
d2010e4
Compare
dandelion711
marked this pull request as ready for review
November 8, 2026 16:47
Games that require Joy-Cons can now configure ordinary controllers by player and game-facing type before launch or while playing. Reuse the existing allocator, joining prompt, motion and rumble paths. Apply validates one draft and publishes one HID transaction, preserves unchanged owners, and rejects stale samples. Automatic remains the session default. Support independent sources for a Joy-Con pair, normal full-gamepad buttons from either source, and native Mac/iOS configuration. Keep current-main native Joy-Con splitting, Switch 2 styles, mouse sensors and Bluetooth rumble. Silence transport output when a configured gamepad or pair takes ownership. Guard absent registry sources, invalid player indexes and capture output failures. Exclude the Mac-only IOBluetooth framework from generated iOS projects so the current-main host dependencies still link on iPhone. Reconstructed from the final feature tree on upstream 7479adf, without synchronization merges or misattributed feature commits. Upstream commits retain their original authorship in the base history. HID passes 21,211 checks. Native haptics ownership-transition tests and 20 controller, launch, documentation and usage tests pass. LLVM 23.1.4 passes over all 273 sources, shrinking one resolved finding without adding any. Pinned Python lint, the Mac host build and the unsigned iOS production smoke build pass. The iOS framework regression fails before the fix and passes afterward. Broader final-revision verification and unavailable game/hardware checks are recorded in the PR description. Front-ends: interface: optional session source descriptors and registry callbacks let external frontends retain their transport and slot authority. Front-ends: new: dm_pad_set_pair_gamepad publishes ordinary full-gamepad buttons for a pair member while motion and rumble retain the assigned side. Front-ends: new: dm_controller_setup_requirements exposes scoped setup limits, and dm_ios_controller_open opens the existing runtime editor.
dandelion711
force-pushed
the
feat/apple-controller-configuration
branch
from
November 9, 2026 04:56
874bcba to
16b9756
Compare
dandelion711
marked this pull request as draft
November 10, 2026 05:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Previously motion games that required Joy-Cons were unaccessible to players with regular gamepads. When a game requested a controller change (Joy-con required, but a Pro Controller is detected), nothing would happen. This PR aims to close these gaps.
Add session-only controller configuration to native macOS and iOS game apps. Games such as Super Mario Party Jamboree can require individual Joy-Cons or a Joy-Con pair, while a connected DualSense, DualShock or other gamepad is normally exposed as Pro Controller or Handheld. Players can choose the player and game-facing type, form a two-controller pair, and apply changes while playing. The engine continues to own physical input, motion and rumble.
This builds on upstream’s existing controller allocation, joining popup and live input handling. Player and controller type default to Automatic: leaving these choices alone preserves the existing automatic routing policy. Configuration edits remain drafts until confirmed; runtime changes take effect only after Apply. A game-requested menu may still appear to resolve an incompatible setup, which is the intentional new workflow described below.
The PR contains nxnative source, tests and documentation only. Controller choices are per connection/session. It adds no per-game assignment store, behavior-mode switch or button-mapping editor.
Features and changes
1. Controller configuration on the game launch screen
2. Explicit game-facing controller styles
3. Upright full gamepads when a virtual single Joy-Con rotates
4. Runtime opening and game-requested controller changes
ShowControllerSupportoverloads use the same compatibility evaluation. Each game request owns one presentation route and one result, so the joining popup and editor do not open independently for the same request. The game thread waits outside the HID lock, and Apply checks the current requirements again. The trigger is the game’s reported player/type/pair requirements, not a Jamboree-specific rule or an assumption that a physical gamepad has motion hardware. Compatible initial draft suggestions remain uncommitted until Apply.5. Stable device identity and explicit player assignments
6. Requirements, readiness and actionable validation
7. Native controller-support argument flags
dm_controller_setup_requirementswithout changing the game's ordinary supported-style/ID queries. Both UI validation and the HID result enforce the effective limits.8. Identify feedback without a new settings surface
9. Normal pair buttons from either full gamepad
dm_pad_set_pair_gamepadand share the locked publication logic with native-side input. Primary sticks, motion, Shake and rumble keep their assigned roles: pressing R on the left gamepad does not move the right gyro. Ordinary button bits already identify the virtual side.10. Motion for single Joy-Cons and Joy-Con pairs
11. Extensible source-registry and launch-channel contracts
cs_update_sourcesaccepts transport-owned descriptors with optionalphysicalSide,automaticPlayerandreservedPlayers;cs_run_registrysupplies host discovery, Identify and final acceptance through the existing private launch protocol. Ordinary clients retaincs_update.Screenshots
These are earlier feature illustrations, not newly captured images of the rebased head. The three Mac images are actual standalone-branch SwiftUI/AppKit captures driven by synthetic device/requirement fixtures. The iOS image is the contributor’s original landscape screenshot from Downloads. Each illustrates a different task. No image is repeated, and none is evidence of sensor accuracy or exhaustive gameplay coverage.
Launch access: Controllers are opened from Controls, without listing all devices inline.
Requirements and corrective guidance: A two-player L/R-only fixture explains why one controller is not ready and why Apply is disabled.
Two physical sources, one logical player: Both full-size controllers share Player 1 with complementary Pair input roles. Identify feedback is also visible. The taller capture shows both complete rows without cropping.
iOS menu and Automatic labels: The supplied landscape image shows native Cancel/Apply actions, a DualSense, and resolved Automatic player/type choices. Its requirements say Any with no player count requested, so it is not presented as a capture of Jamboree’s incompatible-controller prompt.
Checked
Published source:
16b97568d0f7994d70533f0accdfefffb0e38172(tree655e4fe881f651073074a003bc0ac704b20198c1), rebuilt as one commit on upstream7479adf86cd7997c806d45c4c99a875ed4a6038e. The previous branch is preserved atdandelion711/nxnative:backup/pr1348-before-remedies-20261109. Genuine upstream attribution remains in the base, including the original Pro Controller deduplication commita00f03band its coauthor, rather than carrying its redundant cherry-pick as a feature commit.Runtime/build/test sources are identical to tested
0da0413. The final amend only clarifies the native-versus-GameController discovery wording indocs/frontends.md; documentation checks were rerun afterward. Fresh local checks on Apple M1 / macOS 27.2 / Xcode 27.2:make -C dm/host/tests GAME=0100965017338000 run: full host suite passed with the final runtime sources. HID and its ThreadSanitizer target each passed 21,211 checks, zero failed, with no ThreadSanitizer report. Native controller fixtures include assignment, registry admission, joining, motion and haptics. New haptics ownership regressions cover switching from an external Switch 2 output sink to a configured pair and then an ordinary controller.dm/tools/lint_c.py compdbandtidy --update --against origin/main, using CI's SHA-verified LLVM 23.1.4: all 273 sources checked, zero file/check pairs above or below the baseline. The baseline shrinks from 223 to 222 findings, with no increase. A separate same-toolchain run on current main passes at 223 findings. No unrelated graphics rewrite or broad baseline expansion was used.dm/tools/lint_python.py --against origin/main, using pinned ruff 0.17.2 / pyright 1.1.414: zero new or stale findings (336 existing baseline findings).make -C dm/host GAME=0100965017338000 dmhost: production Mac host built.DM_GAME=0100965017338000 dm/ios/tools/build_smoke.py: unsigned production iOS app compiled and linked with SDK 27.2 and the iOS 26 target, using synthetic modules, without installation. The rebase exposed Mac-only IOBluetooth being forwarded into the iOS project; filtering it fixes the observed linker failure while retaining CoreBluetooth.make -C dm/gfx/tests GAME=0100965017338000 runbuilt all 124 test programs but is not green.t_render_counter_overlapfails 1 of 42 checks (CopyBuffer0); continuing the remaining 39 targets findst_render_enable_queuefailing 9 of 46. Both targets reproduce with the same counts on unmodified baseline7479adfusing the same Mac/SDK. All other targets complete; six reflection-sample skips remain. The PR changes no graphics source. These are inherited observed failures, not a claimed all-GPU pass.scripts/regress.py impact origin/main..HEAD: the controller/HID changes reach the four manifested titles below, with 149 profiles unknown because they lack manifests. The missingpad_hid_mac.h:hidpad_gc_controllersrule is now present. The only final unmapped file is the non-runtime lint-baseline metadata.Selected game regression: Jamboree for the motivating Joy-Con workflow, Hades for ordinary input/rumble, Breath of the Wild for assignment/six-axis, Pokémon Scarlet for initialization/state, and Tears of the Kingdom for disconnect/six-axis. The five-game check exits 2 because only Jamboree has a usable local Mac build. The initial full Jamboree check on runtime-identical
0da0413exited 2 before candidate comparison (the local cache has since been repaired):01009650173380007479adf→0da0413Jamboree also has no committed smoke route. No fresh boot/gameplay pass,
validated.tsvrecord or five-game compatibility claim is made. The shader-chain code was not changed. Cache-repair follow-up: regenerated the local shader package, verified all 25,693 mapped records and all four registered tables, and preserved the old package as a backup. The retry passed package validation and began compiling shaders, then was stopped at the contributor’s request. The full comparison remains incomplete; no gameplay pass is claimed. No fresh physical-controller calibration, UIKit simulator test or exhaustive screen check was performed.Hosted CI for
16b9756: tests and lint-c are queued; lint-python has passed. The queued workflows are pending results, not passes. Local results do not imply maintainer review or acceptance.Contributor-reported gameplay testing:
Front ends
Front-ends: interface:Optional connection descriptors and registry callbacks allow another frontend to reuse its existing transport/slot authority. UUIDs and physical device references stay host-owned; names are not identifiers.Front-ends: new:Full-size two-source pair members publish throughdm_pad_set_pair_gamepad; native halves retaindm_pad_set_side.Front-ends: new:dm_controller_setup_requirementsexposes scoped effective setup limits, anddm_ios_controller_openopens the runtime editor from the in-process gear menu.front_ctrl_support/DM_CTRL_PROMPT_TEXTjoining behavior retains priority. Configure on that popup opens the child editor. The configuration hook opens directly only when upstream skips joining and the current setup cannot satisfy the request.Docs
docs/reference/services.mdanddocs/frontends.md, linked from the Jamboree note. The iOS framework-link finding is indocs/reference/packing.md.Related work already identified for this PR includes #1127, #1268, #480, #585, #1143, #1322 and #221. The snapshot does not depend on those branches. The controller-joining prompt has been integrated into the current local candidate. This PR builds around that popup instead of introducing a competing applet result or an additional controller-settings store.