Skip to content

Mac: identical pads that GameController lists as one are each a player of their own (issue #1218) - #1595

Closed
elieteyssedou wants to merge 4 commits into
seedds:mainfrom
elieteyssedou:pads-identical
Closed

elieteyssedou wants to merge 4 commits into
seedds:mainfrom
elieteyssedou:pads-identical

Conversation

@elieteyssedou

Copy link
Copy Markdown
Contributor

What and why

Fixes #1218. Builds on #859: its commits come first in this branch until it lands. #1534 does the same as #859; if that one lands instead, this moves onto it.

Two Switch Pro Controllers over USB play as one player on the Mac: GameController lists one controller, IOKit has both devices, and their USB serial numbers match (over Bluetooth both are listed). GameController gives a controller no HID identity, so its one controller cannot be tied to one of the two devices.

In dm/host:

  • pad_hid_mac.h keeps the devices GameController lists (g_hidpads_gc), unread, instead of dropping them, so that the pads of each model can be counted, by name (vendorName is the HID product string, as Mac: a HID pad GameController says it lists waits two seconds for it, and is read as HID if it never comes #859 matches them).
  • front_mac.m: when IOKit has at least two pads of a model and more than GameController lists, after a 3 s wait for a pad not listed yet (pad_dupes_want, pad_hid.h), every pad of that model is read here, a player each, by USB location. GameController's controllers of that model are left unread. When GameController lists them all again, they go back to it.
  • A Pro Controller is read from its report 0x30 (pad_pro.h), with its motion through motion_pro_mac.h; any other model from its HID elements, as pad_hid.h reads them.
  • A Pro Controller's sticks take its own calibration from SPI flash (subcommand 0x10: the user's at 0x8010 when its magic is there, else the factory's at 0x603D). Without an answer, each axis measures its own rest and reach.
  • DM_PAD_IDENTICAL=0 turns it off.

Limits: such pads have no rumble or player lights, and Home does not quit from them. Another model's buttons follow 8BitDo's numbering unless DM_HID_MAP says otherwise; only Pro Controllers were run this way.

Checked

Pads What the log and the game showed
2 Pro Controllers, USB Pro Controllers: GameController lists 1 of 2: reading them all from their HID reports; two players; one pad took its factory calibration, the other the user's
Pro Controller, Bluetooth listed by GameController and read through it; the calibration read answers (checked with a standalone tool)
2 Xbox Wireless Controllers, Bluetooth both listed by GameController; nothing read here
  • A duplicate player found in that run (GameController's own Pro Controller joining too) is fixed; the fix builds and the tests pass.
  • Device: M4 Pro, macOS 27, Xcode 27.2.
  • scripts/regress.py: not run; the change is in the host's controller input only, with no shader, rendering or tool change.

Docs

  • docs/reference/services.md: Identical controllers that GameController lists as one; python3 dm/tools/docs_symptoms.py run
  • docs/playing.md, docs/env_switches.tsv
  • No game files, keys or data extracted from a game in the change

🤖 Generated with Claude Code

larsniet and others added 4 commits November 1, 2026 10:19
…says YES

a5281d0 skipped every HID pad for which +[GCController supportsHIDDevice:]
answers YES. It answers YES for the 8BitDo Ultimate 2C Wireless (2dc8:301b),
which GameController then never lists, so the pad got no input from either
path (seedds#637 again). The call is now made only for Microsoft, Sony and Nintendo
pads, the vendors GameController does list; other vendors are read as HID.
… and is read as HID if it never comes

+[GCController supportsHIDDevice:] is only a hint: it answers YES for the 8BitDo Ultimate 2C (2dc8:301b), which GameController then never lists (GitHub issue seedds#965). A device it says YES for now waits HIDPAD_GC_WAIT seconds for a GCController of its name; without one it is read as HID, and dropped again if that GCController connects later. A device it says NO for is read at once, as before. This replaces the Microsoft, Sony and Nintendo vendor list, so a pad of another vendor that GameController does list is not read twice.
… are each a player of their own (issue seedds#1218)

Two Pro Controllers over USB are listed as one by GameController. When IOKit has at least two pads of a
model and more than GameController lists, after a 3 s wait, every pad of that model is read here, by USB
location, each a player of its own:
- a Switch Pro Controller from its report 0x30 (pad_pro.h), with its gyro, and its sticks from its own
  calibration in SPI flash (the user's, else the factory's), asked with subcommand 0x10; until it answers,
  or if it never does, each axis measures its rest and reach itself;
- any other model from its HID elements as pad_hid.h reads them: no gyro, 8BitDo's button numbering
  unless DM_HID_MAP says otherwise.
Built on seedds#859: the devices GameController lists are now kept, unread, to count them. GameController gives a
controller no HID identity, so its controllers of such a model are left unread rather than read twice.
DM_PAD_IDENTICAL=0 turns it off. No rumble or player lights on such pads, and Home does not quit from them.

Tests: t_pad_pro (report 0x30, the SPI read, both calibrations), t_pad_hid (when a model is read here).
Run: Mario Kart World 1.8.0 on an M4 Pro (macOS 27), 33 min of split-screen races, two Pro Controllers
over USB, which GameController lists as one: each was a player of its own, each with its own calibration
(one the factory's, one the user's), read over USB and over Bluetooth; two Xbox controllers over Bluetooth,
which GameController lists both, joined as players 3 and 4 while the game ran. A duplicate player found in
that run (GameController's own Pro Controller also joining) is fixed here; the fix builds and the unit
tests pass.

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) docs Documentation labels Nov 6, 2026
seedds added a commit that referenced this pull request Nov 7, 2026
… are each a player of their own, two Pro Controllers over USB among them (PR #1595)

GameController lists two Switch Pro Controllers over USB as one, so a two-player game refused them. pad_hid_mac.h now keeps the devices GameController reads, unread, to count IOKit's pads of each model (vendor and product ID) against GameController's controllers of it: a Pro Controller's by product category, another model's by name. When IOKit has at least two of a model and more than GameController lists, after a 3 s wait (pad_dupes_want), every pad of that model is read there, a player each, by USB location: a Pro Controller from its report 0x30 (pad_pro.h) with its gyro and its own stick calibration from SPI flash, another model from its HID elements. front_mac.m leaves GameController's controllers of that model unread. When GameController lists them all again they go back to it.

Reworked from the PR: one mechanism on pad_hid_mac.h's own HID manager instead of a Pro Controller path on motion_pro_mac.h's manager beside a path for other models, so DM_MOTION=0 / DM_MOTION_HID=0 turn off only the gyro and no longer bring the bug back, and motion_pro_mac.h is unchanged. Microsoft's, Sony's and Nintendo's other pads are never read this way: their names do not match GameController's (a DualShock 4 is "Wireless Controller"), so two of them would have been read twice. DM_PAD_IDENTICAL=0 or DM_HID_PADS=0 turn it off. No rumble, player lights or Home quit on such pads.

Tests: t_pad_pro (report 0x30, the SPI read, both calibrations), t_pad_hid (pad_dupes_want); host tests pass. The PR's version ran 33 minutes of Mario Kart World 1.8.0 split-screen with two Pro Controllers over USB; the reworked version awaits a run with real pads.

Fixes #1218

Co-authored-by: Elie <8143621+elieteyssedou@users.noreply.github.com>
@seedds

seedds commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Landed in 1ed28ed, reworked, with you credited as co-author. Thanks, the diagnosis and the decoder made this easy.

What changed from the PR:

  • One mechanism, in pad_hid_mac.h, on its own HID manager. It counts IOKit's pads of each model (vendor and product ID) against GameController's controllers of it, and reads a model's pads with that model's reader: pad_pro.h for a Pro Controller, HID elements for others. The Pro Controller path no longer goes through motion_pro_mac.h's manager, which is unchanged. That also fixes a bug: DM_MOTION=0 or DM_MOTION_HID=0 stopped dm_motion_pro_start early, so the two Pros were never seen and Mac: two identical Pro Controllers over USB count as one, so a two-player game refuses them #1218 came back without a word in the log. Those switches now turn off only the gyro.
  • Microsoft, Sony and Nintendo pads other than the Pro are never read this way. Since Mac: a HID pad GameController says it lists waits two seconds for it, and is read as HID if it never comes #859 landed, main keeps those vendors off the name match because their names don't match GameController's (a DualShock 4's HID product string is Wireless Controller). With the name-matched path, two DualShock 4s would have been read both through GameController and from IOKit. The Pro is matched by its product category instead.
  • Unchanged from you: pad_pro.h and its tests, the calibration read, pad_dupes_want and the 3 s wait, the order by USB location, leaving GameController's controller unread, and the docs (now describing the new structure).

The reworked version builds and the host tests pass, but it hasn't run on real pads; I have no Pro Controllers here. Could you run two Pro Controllers over USB on current main, and check that each is a player of its own with its calibration? The log lines are now [hid] 'Pro Controller' (057e:2009) at USB location 0x…: GameController lists 1 of 2 pads of this model: read here, from its report 0x30. Please also try once with DM_MOTION_HID=0 to confirm the buttons still work. The push closed #1218; if it fails, I'll reopen it.

#1596 is next: I'll check it on top of this.

@seedds seedds closed this Nov 7, 2026
seedds added a commit that referenced this pull request Nov 8, 2026
…once per matching dictionary it meets, so the HID reader knows a pad by its I/O Registry entry, not by its IOHIDDeviceRef (PR #1722)

A Pro Controller meets both the GamePad usage dictionary and the vendor / product one added for identical pads (PR #1595), and each match came as a new
IOHIDDeviceRef for the same registry entry. hidpad_found compared references only, so one pad counted as two of its model against GameController's one
controller: both were read as twins, players 1 and 2. A one-player game then opened the controller applet every few frames, and The Legend of Zelda:
Ocarina of Time, which suspends its music while an applet has focus, played no area or cutscene music.

Co-authored-by: Ludovic LEMARINEL - Adesin.fr <10656218+LudovicLemarinel@users.noreply.github.com>
dandelion711 pushed a commit to dandelion711/nxnative that referenced this pull request Nov 8, 2026
…once per matching dictionary it meets, so the HID reader knows a pad by its I/O Registry entry, not by its IOHIDDeviceRef (PR seedds#1722)

A Pro Controller meets both the GamePad usage dictionary and the vendor / product one added for identical pads (PR seedds#1595), and each match came as a new
IOHIDDeviceRef for the same registry entry. hidpad_found compared references only, so one pad counted as two of its model against GameController's one
controller: both were read as twins, players 1 and 2. A one-player game then opened the controller applet every few frames, and The Legend of Zelda:
Ocarina of Time, which suspends its music while an applet has focus, played no area or cutscene music.

Co-authored-by: Ludovic LEMARINEL - Adesin.fr <10656218+LudovicLemarinel@users.noreply.github.com>
Know-It-All-Brother pushed a commit to Know-It-All-Brother/nxnative that referenced this pull request Nov 9, 2026
… are each a player of their own, two Pro Controllers over USB among them (PR seedds#1595)

GameController lists two Switch Pro Controllers over USB as one, so a two-player game refused them. pad_hid_mac.h now keeps the devices GameController reads, unread, to count IOKit's pads of each model (vendor and product ID) against GameController's controllers of it: a Pro Controller's by product category, another model's by name. When IOKit has at least two of a model and more than GameController lists, after a 3 s wait (pad_dupes_want), every pad of that model is read there, a player each, by USB location: a Pro Controller from its report 0x30 (pad_pro.h) with its gyro and its own stick calibration from SPI flash, another model from its HID elements. front_mac.m leaves GameController's controllers of that model unread. When GameController lists them all again they go back to it.

Reworked from the PR: one mechanism on pad_hid_mac.h's own HID manager instead of a Pro Controller path on motion_pro_mac.h's manager beside a path for other models, so DM_MOTION=0 / DM_MOTION_HID=0 turn off only the gyro and no longer bring the bug back, and motion_pro_mac.h is unchanged. Microsoft's, Sony's and Nintendo's other pads are never read this way: their names do not match GameController's (a DualShock 4 is "Wireless Controller"), so two of them would have been read twice. DM_PAD_IDENTICAL=0 or DM_HID_PADS=0 turn it off. No rumble, player lights or Home quit on such pads.

Tests: t_pad_pro (report 0x30, the SPI read, both calibrations), t_pad_hid (pad_dupes_want); host tests pass. The PR's version ran 33 minutes of Mario Kart World 1.8.0 split-screen with two Pro Controllers over USB; the reworked version awaits a run with real pads.

Fixes seedds#1218

Co-authored-by: Elie <8143621+elieteyssedou@users.noreply.github.com>
Know-It-All-Brother pushed a commit to Know-It-All-Brother/nxnative that referenced this pull request Nov 9, 2026
…once per matching dictionary it meets, so the HID reader knows a pad by its I/O Registry entry, not by its IOHIDDeviceRef (PR seedds#1722)

A Pro Controller meets both the GamePad usage dictionary and the vendor / product one added for identical pads (PR seedds#1595), and each match came as a new
IOHIDDeviceRef for the same registry entry. hidpad_found compared references only, so one pad counted as two of its model against GameController's one
controller: both were read as twins, players 1 and 2. A one-player game then opened the controller applet every few frames, and The Legend of Zelda:
Ocarina of Time, which suspends its music while an applet has focus, played no area or cutscene music.

Co-authored-by: Ludovic LEMARINEL - Adesin.fr <10656218+LudovicLemarinel@users.noreply.github.com>
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) 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.

Mac: two identical Pro Controllers over USB count as one, so a two-player game refuses them

3 participants