Skip to content

Switch 2 controllers with press to join: a controller that connects waits for its first button press instead of being let go - #1907

Closed
JakePre wants to merge 1 commit into
seedds:mainfrom
JakePre:switch2-press-to-join
Closed

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

Conversation

@JakePre

@JakePre JakePre commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

What and why

With Press a button to join on (DM_PAD_PRESS_TO_JOIN=1), a Switch 2 controller couldn't be used. Holding Sync paired it and it connected, then it was dropped at once. A button press only woke it to be dropped again. On an iPhone, the log showed:

controller 'Joy-Con 2' (Bluetooth) connected: press a button to play (DM_PAD_PRESS_TO_JOIN)
[sw2] Joy-Con 2 (L): no free player, not used
[sw2] Joy-Con 2 (L) disconnected

The front end deliberately gives a new pad no player until its first button (s2pad_attach, then s2pad_read through pad_join). But the transport took "no player after attach" to mean "no free player" and let the controller go. The Mac has the same path. It wasn't seen because every run I made had the setting off. That's on me: it came in with #1346.

The change:

  • 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. 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. Before, both 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.

front_mac.m returns the new value. #1900's iOS wiring does the same.

Checked

  • Tests: make -C dm/host/tests GAME=0100000000010000 run passes. lint_c.py tidy: 0 findings above the baseline.
  • Mac (M1 Pro, macOS 27.2): Nintendo Switch 2 Welcome Tour (040057C02159A000, with game: Nintendo Switch 2 Welcome Tour plays on the Mac #1425), a left and a right Joy-Con 2, DM_PAD_PRESS_TO_JOIN=1.
    • Sync on each: both connected and stayed (a player once a button is pressed, the right one joining the left as a pair).
    • The first press: a button pressed: player 1 (npad 0).
    • Rumble played, and the mouse moved the cursor. No disconnects until quitting with Home.
  • iPhone 17 (with ios: Switch 2 controllers on iPhone and iPad #1900): Super Mario Galaxy (010099C022B96000, v327680), the same two Joy-Cons.
    • Sync on each, then a press: player 1, as a pair.
    • Splitting one off (SL + SR) made it player 2 at once (its held buttons count as the press). L + R held made them one pair again.
  • Known detail: while a pad waits, its lights show player 1, since the light code's fallback for "no player" is the first pattern. They change at the join if it becomes another player. I didn't pick an "off" or flashing pattern, since I don't know what the Joy-Con 2 takes for that.
  • Not checked: the Pro Controller 2; more than two controllers waiting at once.

Docs

  • Nothing for the player docs: they already say a controller joins at its first press
  • No chain tool changed
  • No game files, keys or extracted game data

🤖 Generated with Claude Code

…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
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>
@seedds

seedds commented Nov 11, 2026

Copy link
Copy Markdown
Owner

Landed on main as 7ef8339, with you as co-author. Thanks for the diagnosis and the fix.

Your three files are in as written, with one change in pad_switch2_apple.m: the player check you added in report: is now a helper, playerSync, which leavePair calls too. With press to join on, a Joy-Con split off a pair (SL + SR) becomes a player inside read(), since the held buttons are the press. In the PR, s2_sync_sides ran before that and _player was set right after, so the check in report: never fired and the split-off Joy-Con never got dm_joy_attach for its new player.

I have no Joy-Con 2 here, so this is checked by the host build and the host tests only. Could you retest on main with press to join on: Sync, first press, then a split, and whether the game sees the split-off one as a single Joy-Con?

#1900 needs a rebase onto main now; its host part is this commit.

@seedds seedds closed this Nov 11, 2026
@JakePre

JakePre commented Nov 12, 2026

Copy link
Copy Markdown
Contributor Author

Retested on main as asked (7ef8339, built at dfc5a15), on the Mac (M1 Pro) with a left and a right Joy-Con 2, DM_SW2=1 and DM_PAD_PRESS_TO_JOIN=1. I added two log lines locally, not in any PR: one in dm_joy_attach and one in styles_sync where it tells an npad a new style. The split-off Joy-Con gets its single style now:

Super Smash Bros. Ultimate (supported styles 0x3f):

  • Both Joy-Cons connected and waited ("a player once a button is pressed"). At the first press they became player 1: dm_joy_attach player 1 sides 3, and npad 0 went FullKey → JoyDual.
  • SL + SR on the right one: dm_joy_attach player 1 sides 1, and npad 0 went JoyDual → JoyLeft. Then "a button pressed: player 2", dm_joy_attach player 2 sides 2, and npad 1 went 0 → FullKey → JoyRight. The log then said "Joy-Con 2 (R) plays alone now: player 2 (SL + SR held); the other Joy-Con stays player 1".
  • One small thing: npad 1 is FullKey for the moment between read() giving it the player and playerSync's dm_joy_attach, so the game gets two style signals for it in a row. Smash took it without trouble.

Nintendo Switch 2 Welcome Tour (supported styles 0x7, no single Joy-Con styles): the same joins and split, with the same dm_joy_attach calls. Both npads went to FullKey after the split, as npad_resolve maps an unsupported JoyLeft / JoyRight. Its controller applet then asked for 1 player and folded player 2 into player 1.

Not run: the Pro Controller 2, and more than two controllers waiting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

host The Horizon host: SVCs, services, threads, memory (dm/host) mac The Mac front end and app (dm/mac, front_mac.m)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants