@@ -735,7 +735,7 @@ class ControllerService: ObservableObject {
735735
736736 /// Identifies the small 8BitDo pads from their SDL/HID product names
737737 /// ("8BitDo Zero 2", "8BitDo Micro", "8BitDo Lite 2", "8BitDo Lite SE").
738- static func eightBitDoMinimapModel( forControllerName name: String ) -> EightBitDoMinimapModel ? {
738+ nonisolated static func eightBitDoMinimapModel( forControllerName name: String ) -> EightBitDoMinimapModel ? {
739739 let lowered = name. lowercased ( )
740740 guard lowered. contains ( " 8bitdo " ) else { return nil }
741741 if lowered. contains ( " zero " ) { return . zero2 }
@@ -745,10 +745,21 @@ class ControllerService: ObservableObject {
745745 return nil
746746 }
747747
748- static func eightBitDoMinimapModel( vendorName: String ? , productCategory: String ) -> EightBitDoMinimapModel ? {
748+ nonisolated static func eightBitDoMinimapModel( vendorName: String ? , productCategory: String ) -> EightBitDoMinimapModel ? {
749749 eightBitDoMinimapModel ( forControllerName: " \( vendorName ?? " " ) \( productCategory) " )
750750 }
751751
752+ nonisolated static func isSticklessEightBitDoModel( vendorName: String ? , productCategory: String ) -> Bool {
753+ eightBitDoMinimapModel ( vendorName: vendorName, productCategory: productCategory) ? . isStickless == true
754+ }
755+
756+ nonisolated static func shouldUsePhysicalDirectionPadAsLeftStickFallback(
757+ vendorName: String ? ,
758+ productCategory: String
759+ ) -> Bool {
760+ !isSticklessEightBitDoModel( vendorName: vendorName, productCategory: productCategory)
761+ }
762+
752763 private func startEightBitDoHIDMonitoringIfNeeded( for controller: GCController , reason: String ) {
753764 guard Self . eightBitDoMinimapModel (
754765 vendorName: controller. vendorName,
@@ -2128,17 +2139,14 @@ class ControllerService: ObservableObject {
21282139 ]
21292140
21302141 // Stickless 8BitDo pads (Zero 2 / Micro in D-input mode) have no real
2131- // analog stick — their physical d-pad IS the directional input. The
2132- // Micro exposes it as a "Direction Pad" (with digital sub-buttons),
2133- // which we must NOT bind as .dpad* buttons here: that would fire d-pad
2134- // actions AND (via the thumbstick fallback below) drive the mouse —
2135- // the double-input. Instead we route the d-pad to the left stick so
2136- // the left-stick mode (Mouse by default, or the D-Pad mode) governs
2137- // it, exactly like the Zero 2 (which exposes its d-pad as a thumbstick).
2138- let isSticklessEightBitDo = Self . eightBitDoMinimapModel (
2142+ // analog stick. When macOS exposes the physical d-pad as a
2143+ // `Direction Pad`, bind it as real .dpad* buttons and do not also use it
2144+ // as the left-stick fallback. The mapping canvas labels this control as
2145+ // a D-pad, and Android-mode Micro reports digital D-pad directions here.
2146+ let useDirectionPadAsLeftStickFallback = Self . shouldUsePhysicalDirectionPadAsLeftStickFallback (
21392147 vendorName: controller. vendorName,
21402148 productCategory: controller. productCategory
2141- ) ? . isStickless == true
2149+ )
21422150
21432151 var boundCount = 0
21442152 for (inputName, controllerButton) in buttonMap {
@@ -2148,9 +2156,8 @@ class ControllerService: ObservableObject {
21482156 }
21492157 }
21502158
2151- // D-pad (skip the direct button binding for stickless 8BitDo pads —
2152- // their d-pad flows through the left stick instead, see above).
2153- if !isSticklessEightBitDo, let dpad = profile. dpads [ GCInputDirectionPad] {
2159+ // D-pad.
2160+ if let dpad = profile. dpads [ GCInputDirectionPad] {
21542161 bindButton ( dpad. up, to: . dpadUp, from: controller)
21552162 bindButton ( dpad. down, to: . dpadDown, from: controller)
21562163 bindButton ( dpad. left, to: . dpadLeft, from: controller)
@@ -2166,7 +2173,7 @@ class ControllerService: ObservableObject {
21662173 service. updateLeftStick ( x: x, y: y)
21672174 }
21682175 }
2169- } else if let dpad = profile. dpads [ GCInputDirectionPad] {
2176+ } else if useDirectionPadAsLeftStickFallback , let dpad = profile. dpads [ GCInputDirectionPad] {
21702177 // Fallback: Joy-Con stick may be exposed as a D-pad rather than a thumbstick.
21712178 // Use it for mouse movement so the user gets analog-like cursor control.
21722179 dpad. valueChangedHandler = { [ weak self, weak controller] _, xValue, yValue in
0 commit comments