diff --git a/README.md b/README.md index d711d489..0e91f36b 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ Buttons and axes follow the Xbox/standard gamepad layout: | `Button` | `back`, `start`, `home` | 0.0 or 1.0 | | `Button` | `leftStick`, `rightStick` | 0.0 or 1.0 | | `Button` | `dpadUp/Down/Left/Right` | 0.0 or 1.0 | +| `Button` | `touchpad` | 0.0 or 1.0 | | `Axis` | `leftStickX`, `leftStickY` | -1.0 to 1.0 | | `Axis` | `rightStickX`, `rightStickY` | -1.0 to 1.0 | | `Axis` | `leftTrigger`, `rightTrigger`| 0.0 to 1.0 | diff --git a/packages/gamepads/example/.gitignore b/packages/gamepads/example/.gitignore index 09237690..7618a6b3 100644 --- a/packages/gamepads/example/.gitignore +++ b/packages/gamepads/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/packages/gamepads/example/.metadata b/packages/gamepads/example/.metadata index 23f62689..e4ea3027 100644 --- a/packages/gamepads/example/.metadata +++ b/packages/gamepads/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "ff37bef603469fb030f2b72995ab929ccfc227f0" + revision: "44a626f4f0027bc38a46dc68aed5964b05a83c18" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 - base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 - - platform: linux - create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 - base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + create_revision: 44a626f4f0027bc38a46dc68aed5964b05a83c18 + base_revision: 44a626f4f0027bc38a46dc68aed5964b05a83c18 + - platform: macos + create_revision: 44a626f4f0027bc38a46dc68aed5964b05a83c18 + base_revision: 44a626f4f0027bc38a46dc68aed5964b05a83c18 # User provided section diff --git a/packages/gamepads/lib/src/api/gamepad_button.dart b/packages/gamepads/lib/src/api/gamepad_button.dart index e6d3bb04..a4ebb93f 100644 --- a/packages/gamepads/lib/src/api/gamepad_button.dart +++ b/packages/gamepads/lib/src/api/gamepad_button.dart @@ -54,4 +54,8 @@ enum GamepadButton { /// D-pad right. dpadRight, + + /// The touchpad button (pressing the touchpad surface, available on + /// PlayStation DualSense and DualShock controllers). + touchpad, } diff --git a/packages/gamepads/lib/src/mappings/macos_mapping.dart b/packages/gamepads/lib/src/mappings/macos_mapping.dart index 71af05bf..fe9863ee 100644 --- a/packages/gamepads/lib/src/mappings/macos_mapping.dart +++ b/packages/gamepads/lib/src/mappings/macos_mapping.dart @@ -29,6 +29,14 @@ class MacosMapping extends PlatformMapping { // SF Symbols names can vary by controller, so we use contains-based // matching on first encounter, then cache the result. static const _buttonPatterns = { + // Fixed keys from native plugin — these use GCController typed + // property names instead of SF Symbols to avoid ambiguity across + // controller types (e.g. DualSense reports capsule.portrait for + // both system buttons). + 'buttonMenu': GamepadButton.start, + 'buttonOptions': GamepadButton.back, + 'buttonHome': GamepadButton.home, + 'touchpadButton': GamepadButton.touchpad, // Xbox-style face buttons 'a.circle': GamepadButton.a, 'b.circle': GamepadButton.b, diff --git a/packages/gamepads/test/mappings_test.dart b/packages/gamepads/test/mappings_test.dart index ae53c958..739eb826 100644 --- a/packages/gamepads/test/mappings_test.dart +++ b/packages/gamepads/test/mappings_test.dart @@ -389,6 +389,28 @@ void main() { ); }); + test('normalizes system buttons from fixed keys', () { + expect( + mapping.normalizeButton('buttonMenu', 1.0)?.button, + GamepadButton.start, + ); + expect( + mapping.normalizeButton('buttonOptions', 1.0)?.button, + GamepadButton.back, + ); + expect( + mapping.normalizeButton('buttonHome', 1.0)?.button, + GamepadButton.home, + ); + }); + + test('normalizes touchpad button', () { + expect( + mapping.normalizeButton('touchpadButton', 1.0)?.button, + GamepadButton.touchpad, + ); + }); + test('normalizes shoulder buttons', () { expect( mapping.normalizeButton('l1.rectangle.roundedbottom', 1.0)?.button, diff --git a/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift b/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift index 6e07fd74..a5d19ab6 100644 --- a/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift +++ b/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift @@ -2,6 +2,13 @@ import Cocoa import GameController import FlutterMacOS +enum FixedKey: String { + case buttonMenu + case buttonOptions + case buttonHome + case touchpadButton +} + public class GamepadsDarwinPlugin: NSObject, FlutterPlugin { let channel: FlutterMethodChannel let gamepads = GamepadsListener() @@ -29,7 +36,8 @@ public class GamepadsDarwinPlugin: NSObject, FlutterPlugin { } private func onGamepadEvent(gamepadId: Int, gamepad: GCExtendedGamepad, element: GCControllerElement) { - for (key, value) in getValues(element: element) { + let fixedKey = getFixedKey(gamepad: gamepad, element: element) + for (key, value) in getValues(element: element, fixedKey: fixedKey) { let arguments: [String: Any] = [ "gamepadId": String(gamepadId), "time": Int(getTimestamp(gamepad: gamepad)), @@ -41,30 +49,65 @@ public class GamepadsDarwinPlugin: NSObject, FlutterPlugin { } } - private func getValues(element: GCControllerElement) -> [(String, Float)] { + /// Returns a fixed key name for elements whose SF Symbol names are + /// ambiguous across controller types (e.g. both DualSense system + /// buttons report "capsule.portrait"). For other elements, returns + /// nil so the caller falls back to SF Symbol names. + private func getFixedKey(gamepad: GCExtendedGamepad, element: GCControllerElement) -> FixedKey? { + if element === gamepad.buttonMenu { + return .buttonMenu + } + if let opt = gamepad.buttonOptions, element === opt { + return .buttonOptions + } + if #available(macOS 11.0, *) { + if let home = gamepad.buttonHome, element === home { + return .buttonHome + } + } + if #available(macOS 11.3, *) { + if let ds = gamepad as? GCDualSenseGamepad, + element === ds.touchpadButton { + return .touchpadButton + } + } + if #available(macOS 11.0, *) { + if let ds = gamepad as? GCDualShockGamepad, + element === ds.touchpadButton { + return .touchpadButton + } + } + return nil + } + + private func getValues(element: GCControllerElement, fixedKey: FixedKey? = nil) -> [(String, Float)] { if let element = element as? GCControllerButtonInput { - var button: String = "Unknown button" - if #available(macOS 11.0, *) { - if (element.sfSymbolsName != nil) { - button = element.sfSymbolsName! + var button: String = fixedKey?.rawValue ?? "Unknown button" + if fixedKey == nil { + if #available(macOS 11.0, *) { + if let name = element.sfSymbolsName { + button = name + } } } - return [(button, element.value)] } else if let element = element as? GCControllerAxisInput { - var axis: String = "Unknown axis" - if #available(macOS 11.0, *) { - if (element.sfSymbolsName != nil) { - axis = element.sfSymbolsName! + var axis: String = fixedKey?.rawValue ?? "Unknown axis" + if fixedKey == nil { + if #available(macOS 11.0, *) { + if let name = element.sfSymbolsName { + axis = name + } } } return [(axis, element.value)] } else if let element = element as? GCControllerDirectionPad { - var directionPad: String = "Unknown direction pad" - - if #available(macOS 11.0, *) { - if (element.sfSymbolsName != nil) { - directionPad = element.sfSymbolsName! + var directionPad: String = fixedKey?.rawValue ?? "Unknown direction pad" + if fixedKey == nil { + if #available(macOS 11.0, *) { + if let name = element.sfSymbolsName { + directionPad = name + } } } return [