From d6d72a6adc2f755d926c09997f48462f38a59bfa Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Wed, 1 Apr 2026 21:25:32 -0600 Subject: [PATCH 1/7] fix: Enhance gamepad event handling with fixed key names for ambiguous elements --- .../macos/Classes/GamepadsDarwinPlugin.swift | 68 ++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift b/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift index 6e07fd74..64d65ae5 100644 --- a/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift +++ b/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift @@ -29,7 +29,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 +42,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) -> String? { + 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: String? = 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 ?? "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 ?? "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 ?? "Unknown direction pad" + if fixedKey == nil { + if #available(macOS 11.0, *) { + if let name = element.sfSymbolsName { + directionPad = name + } } } return [ From 388ea8b8ce03cfd921439c6e3b97cf2ccfccaafe Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Wed, 1 Apr 2026 21:25:44 -0600 Subject: [PATCH 2/7] fix: Update macOS gamepad button mappings to use native plugin keys for clarity --- packages/gamepads/lib/src/mappings/macos_mapping.dart | 8 ++++++++ 1 file changed, 8 insertions(+) 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, From 23be118d5e6caaf814093289394517b17d8474b9 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Wed, 1 Apr 2026 21:25:50 -0600 Subject: [PATCH 3/7] feat: Add touchpad button to GamepadButton enum for PlayStation controllers --- packages/gamepads/lib/src/api/gamepad_button.dart | 4 ++++ 1 file changed, 4 insertions(+) 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, } From e4693a24b9bf4b344ba90468d032c4996b70386b Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Wed, 1 Apr 2026 21:26:04 -0600 Subject: [PATCH 4/7] feat: Add tests for normalizing system and touchpad buttons in IosMapping --- packages/gamepads/test/mappings_test.dart | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) 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, From cb9b17c31af0bc55b4c3133f2e9d8cd3a00c31ba Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Wed, 1 Apr 2026 21:26:17 -0600 Subject: [PATCH 5/7] chore: Update .gitignore and .metadata for build artifacts and project revision --- packages/gamepads/example/.gitignore | 2 ++ packages/gamepads/example/.metadata | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) 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 From d60193fc44a8ea72b10008abd5930ae501311642 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Fri, 3 Apr 2026 09:58:33 -0600 Subject: [PATCH 6/7] feat: Add touchpad button mapping to README for gamepad input --- README.md | 1 + 1 file changed, 1 insertion(+) 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 | From 72f3982ff1c8c46653d40b460779e0653eea8c62 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Fri, 3 Apr 2026 09:59:19 -0600 Subject: [PATCH 7/7] refactor: Refactor getFixedKey and getValues methods to use FixedKey enum for consistency --- .../macos/Classes/GamepadsDarwinPlugin.swift | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift b/packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift index 64d65ae5..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() @@ -46,36 +53,36 @@ public class GamepadsDarwinPlugin: NSObject, FlutterPlugin { /// 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) -> String? { + private func getFixedKey(gamepad: GCExtendedGamepad, element: GCControllerElement) -> FixedKey? { if element === gamepad.buttonMenu { - return "buttonMenu" + return .buttonMenu } if let opt = gamepad.buttonOptions, element === opt { - return "buttonOptions" + return .buttonOptions } if #available(macOS 11.0, *) { if let home = gamepad.buttonHome, element === home { - return "buttonHome" + return .buttonHome } } if #available(macOS 11.3, *) { if let ds = gamepad as? GCDualSenseGamepad, element === ds.touchpadButton { - return "touchpadButton" + return .touchpadButton } } if #available(macOS 11.0, *) { if let ds = gamepad as? GCDualShockGamepad, element === ds.touchpadButton { - return "touchpadButton" + return .touchpadButton } } return nil } - private func getValues(element: GCControllerElement, fixedKey: String? = nil) -> [(String, Float)] { + private func getValues(element: GCControllerElement, fixedKey: FixedKey? = nil) -> [(String, Float)] { if let element = element as? GCControllerButtonInput { - var button: String = fixedKey ?? "Unknown button" + var button: String = fixedKey?.rawValue ?? "Unknown button" if fixedKey == nil { if #available(macOS 11.0, *) { if let name = element.sfSymbolsName { @@ -85,7 +92,7 @@ public class GamepadsDarwinPlugin: NSObject, FlutterPlugin { } return [(button, element.value)] } else if let element = element as? GCControllerAxisInput { - var axis: String = fixedKey ?? "Unknown axis" + var axis: String = fixedKey?.rawValue ?? "Unknown axis" if fixedKey == nil { if #available(macOS 11.0, *) { if let name = element.sfSymbolsName { @@ -95,7 +102,7 @@ public class GamepadsDarwinPlugin: NSObject, FlutterPlugin { } return [(axis, element.value)] } else if let element = element as? GCControllerDirectionPad { - var directionPad: String = fixedKey ?? "Unknown direction pad" + var directionPad: String = fixedKey?.rawValue ?? "Unknown direction pad" if fixedKey == nil { if #available(macOS 11.0, *) { if let name = element.sfSymbolsName {