Skip to content

ios: Switch 2 controllers on iPhone and iPad - #1900

Open
JakePre wants to merge 1 commit into
seedds:mainfrom
JakePre:switch2-ios
Open

JakePre wants to merge 1 commit into
seedds:mainfrom
JakePre:switch2-ios

Conversation

@JakePre

@JakePre JakePre commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

What and why

The iOS side of the Switch 2 controllers (#1267), as you asked: the iOS front end starts the shared transport, pad_switch2_apple.m, as front_mac.m does.

  • DmGamepad.m gives a Switch 2 pad a player slot the way front_mac.m does:
    • the same slots as the GameController pads;
    • its rumble goes to dm_switch2_rumble. With DM_PAD_MERGE, player 1's rumble follows the controller last used, as on the Mac since your landing;
    • press to join (DM_PAD_PRESS_TO_JOIN);
    • the controller applet's prompt: a pad's A and B count, a pad that hasn't joined counts as waiting, and no input reaches the game while the prompt is up;
    • the touch controls hide while a pad is in use, and the transport pauses while the app is in the background.
  • The iOS Info.plist gets NSBluetoothAlwaysUsageDescription (project.yml). Without it, the transport stays off.
  • settings.tsv offers DM_SW2 on iOS too, and test_launcher_settings.py no longer lists it as Mac-only.
  • docs/playing.md and docs/settings.md say how it works on iOS.

iOS doesn't bond. iOS gives an app no way to read the device's own Bluetooth address, which bonding writes to a controller. Your IOBluetoothHostController read is Mac-only. So a controller in Sync is used without bonding, as pad_switch2_apple.h already says: hold Sync after each start of the game. A button press alone doesn't wake it toward the phone. The player docs say this.

The commit carries Front-ends: new:. An iOS app that offers DM_SW2 needs the Bluetooth usage description in its Info.plist.

Checked

Its host part, #1907 (Switch 2 controllers with press to join), landed as 7ef8339, and this branch is rebased onto main with it: one commit, the iOS wiring. The iOS front end's s2pad_attach already returns whether it keeps the pad, as pad_switch2_apple.h now asks.

  • Tests:
    • test_launcher_settings.py, test_settings.py, test_docs.py and dm/ios/tests pass.
    • DmGamepad.m compiles for iOS (arm64, -mcpu=apple-m1) with no warnings.
  • iPhone 17, 8 November, this branch: Super Mario Galaxy (010099C022B96000, v327680), a left and a right Joy-Con 2, with Switch 2 controllers and Press a button to join on.
  • iPhone 16, 7 November, the earlier wiring: Paper Mario: The Thousand-Year Door (0100ECD018EBE000, v65536) and Super Mario Galaxy, docked with the gyro pointer. Pairing by Sync, motion and rumble worked, and the touch controls hid while the Joy-Cons were connected.
  • Hold Home to quit: not on iOS, as for every other controller there. The app gives Home and Capture no function and only keeps iOS from taking them as system gestures.
  • Not checked:
    • an iPad;
    • the Pro Controller 2 on iOS;
    • DM_PAD_MERGE with a Switch 2 pad on iOS;
    • the controller prompt (Galaxy doesn't open it).
  • regress.py: not run. This PR's own change is the iOS front end, its Info.plist, one settings row and two docs.

Docs

  • docs/playing.md (a section for iPhone and iPad) and docs/settings.md (the setting moves from the Mac window's list to Controls, and says what differs on iOS); no symptom entry, since nothing broke
  • No chain tool changed
  • No game files, keys or data extracted from a game

🤖 Generated with Claude Code

@github-actions github-actions Bot added ios The iPhone and iPad app (dm/ios) tools Tools and scripts (dm/tools, scripts) docs Documentation labels Nov 9, 2026
JakePre added a commit to JakePre/nxnative that referenced this pull request Nov 9, 2026
…r 2 that connects waits for its first button press instead of being let go as if every player were taken (issue seedds#1267)

With DM_PAD_PRESS_TO_JOIN=1 the front end gives a new pad no player until its first button (front_mac.m s2pad_attach, then s2pad_read through pad_join), but the transport took a pad with no player after attach as "no free player" and dropped the connection: a controller held in Sync paired, connected and disconnected at once, and a button press only woke it to be dropped again. Found on an iPhone with the iOS wiring (seedds#1900), which offers the setting; the Mac has the same path, unseen because every run had the setting off.

The front end's attach now says whether it keeps the pad (pad_switch2_apple.h: 1 with a player or waiting for its first press, 0 with no free player), and only 0 lets the controller go. A pad that gets its player later, at that press, gets its Joy-Con styles (dm_joy_attach through s2_sync_sides) and its player lights at the next report, which before came only at connection. A Joy-Con split off a pair (SL + SR held) waits for a press the same way when the setting is on; with no free player it goes back into the pair as before. The log says "a player once a button is pressed" for a pad that waits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) mac The Mac front end and app (dm/mac, front_mac.m) labels Nov 9, 2026
JakePre added a commit to JakePre/nxnative that referenced this pull request Nov 9, 2026
…r 2 that connects waits for its first button press instead of being let go as if every player were taken (issue seedds#1267)

With DM_PAD_PRESS_TO_JOIN=1 the front end gives a new pad no player until its first button (front_mac.m s2pad_attach, then s2pad_read through pad_join), but the transport took a pad with no player after attach as "no free player" and dropped the connection: a controller held in Sync paired, connected and disconnected at once, and a button press only woke it to be dropped again. Found on an iPhone with the iOS wiring (seedds#1900), which offers the setting; the Mac has the same path, unseen because every run had the setting off.

The front end's attach now says whether it keeps the pad (pad_switch2_apple.h: 1 with a player or waiting for its first press, 0 with no free player), and only 0 lets the controller go. A pad that gets its player later, at that press, gets its Joy-Con styles (dm_joy_attach through s2_sync_sides) and its player lights at the next report, which before came only at connection. A Joy-Con split off a pair (SL + SR held) waits for a press the same way when the setting is on; with no free player it goes back into the pair as before. The log says "a player once a button is pressed" for a pad that waits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
seedds added a commit that referenced this pull request Nov 11, 2026
…onnects waits for its first button press instead of being let go: with DM_PAD_PRESS_TO_JOIN=1 the front end gives a new pad no player until its first button (s2pad_attach, then s2pad_read through pad_join), and the transport took no player after attach for no free player and dropped the controller (attachPad, pad_switch2_apple.m), so no Joy-Con 2 or Pro Controller 2 could be used with the setting on; a press only woke it to be dropped again. The front end's attach now returns whether it keeps the pad (pad_switch2_apple.h): 1 with a player or waiting for its first press, 0 with no free player, and only 0 lets the controller go; a Joy-Con split off a pair (SL + SR held) is kept the same way, and goes back into the pair only with no free player. A pad that gets its player later gets its Joy-Con styles (dm_joy_attach through s2_sync_sides) and its player lights then: playerSync, after each read() of the front end, in report: and in leavePair, where the held SL + SR are that press and read() makes the Joy-Con a player at once. PR #1907 made that check in report: only, after leavePair had already noted the new player, so a split-off Joy-Con never got its single Joy-Con style with the setting on. The log says a player once a button is pressed for a pad that waits. Checked here: the Mac host builds and the host tests pass (t_switch2 among them; none covers the Objective-C transport). The author's runs, with the PR's form: on the Mac (M1 Pro) Nintendo Switch 2 Welcome Tour with a left and a right Joy-Con 2, both stayed connected and joined as player 1 at the first press, with rumble and the mouse; on an iPhone 17 with PR #1900's wiring, Super Mario Galaxy, the same, and a split made player 2 at once. Not run: any Switch 2 controller with this form, so the split-off Joy-Con's style is unchecked; the Pro Controller 2; more than two controllers waiting; lint_c.py; regress.py (the reach is the Switch 2 transport, off by default). Open: a waiting pad's lights show player 1's pattern (PR #1907)

Co-authored-by: JakePre <130175182+JakePre@users.noreply.github.com>
Switch 2 controllers on iPhone and iPad: the iOS front end starts the shared transport (pad_switch2_apple.m) as front_mac.m does, and DM_SW2 is offered on iOS (no bonding there: hold Sync after each start; issue seedds#1267)

DmGamepad.m gives a Switch 2 pad a player slot as front_mac.m does: the same slots as the GameController pads, its rumble to dm_switch2_rumble (player 1's follows the controller last used with DM_PAD_MERGE), press to join (DM_PAD_PRESS_TO_JOIN), the controller applet's prompt (its A and B count, a pad that has not joined is one waiting, and no input reaches the game while the prompt is up), and the touch gamepad hides while one is in use. The transport stops while the app is in the background, as the Mac's does when it loses the focus.

iOS gives an app no way to read the device's own Bluetooth address, which bonding writes to a controller (pad_switch2_apple.m reads it only on the Mac), so a controller in Sync is used without bonding, as pad_switch2_apple.h already says: it needs Sync again after each start, and a button press alone does not wake it. The iOS Info.plist gets NSBluetoothAlwaysUsageDescription (project.yml), without which the transport stays off; settings.tsv offers DM_SW2 on both platforms, and test_launcher_settings.py no longer lists it as Mac-only. docs/playing.md and docs/settings.md say how it is used on iOS.

Front-ends: new: DM_SW2 is offered on iOS: an iOS app that shows it needs NSBluetoothAlwaysUsageDescription in its Info.plist, or the transport stays off (pad_switch2_apple.m)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JakePre JakePre changed the title Switch 2 controllers on iPhone and iPad: the iOS front end starts the shared transport, and DM_SW2 is offered on iOS (no bonding: Sync after each start) ios: Switch 2 controllers on iPhone and iPad Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation host The Horizon host: SVCs, services, threads, memory (dm/host) ios The iPhone and iPad app (dm/ios) mac The Mac front end and app (dm/mac, front_mac.m) tools Tools and scripts (dm/tools, scripts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant