From 12969eeff9e1f2a9136cc0155cea1c706fbf5774 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:31:56 +0100 Subject: [PATCH] feat: gamepad support tested with dualsense on macOS, close #25 --- lib/focus/input_mode_tracker.dart | 28 ++ lib/main.dart | 6 + lib/screens/libraries/libraries_screen.dart | 28 ++ lib/services/gamepad_service.dart | 349 ++++++++++++++++++ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Podfile.lock | 6 + pubspec.lock | 76 +++- pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 12 files changed, 503 insertions(+), 2 deletions(-) create mode 100644 lib/services/gamepad_service.dart diff --git a/lib/focus/input_mode_tracker.dart b/lib/focus/input_mode_tracker.dart index fe02e123..e4357df8 100644 --- a/lib/focus/input_mode_tracker.dart +++ b/lib/focus/input_mode_tracker.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../services/tv_detection_service.dart'; +import '../services/gamepad_service.dart'; /// Tracks whether the user is navigating via keyboard/d-pad or pointer (mouse/touch). /// @@ -51,13 +52,21 @@ class _InputModeTrackerState extends State { @override void initState() { super.initState(); + // Initialize focus highlight strategy based on starting mode + _updateFocusHighlightStrategy(_mode); // Listen to hardware keyboard events globally HardwareKeyboard.instance.addHandler(_handleKeyEvent); + RawKeyboard.instance.addListener(_handleRawKeyEvent); + + // Register callback for gamepad input to switch to keyboard mode + GamepadService.onGamepadInput = () => _setMode(InputMode.keyboard); } @override void dispose() { HardwareKeyboard.instance.removeHandler(_handleKeyEvent); + RawKeyboard.instance.removeListener(_handleRawKeyEvent); + GamepadService.onGamepadInput = null; super.dispose(); } @@ -70,10 +79,29 @@ class _InputModeTrackerState extends State { return false; } + void _handleRawKeyEvent(RawKeyEvent event) { + if (event is RawKeyDownEvent) { + _setMode(InputMode.keyboard); + } + } + void _setMode(InputMode mode) { if (_mode != mode) { setState(() => _mode = mode); } + _updateFocusHighlightStrategy(mode); + } + + // Keep Material focus highlights in sync with our input mode so keyboard/gamepad + // navigation immediately shows focus without waiting for a real keypress. + void _updateFocusHighlightStrategy(InputMode mode) { + final desiredStrategy = mode == InputMode.keyboard + ? FocusHighlightStrategy.alwaysTraditional + : FocusHighlightStrategy.automatic; + + if (FocusManager.instance.highlightStrategy != desiredStrategy) { + FocusManager.instance.highlightStrategy = desiredStrategy; + } } @override diff --git a/lib/main.dart b/lib/main.dart index bf4d66fa..8d057354 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,6 +11,7 @@ import 'services/fullscreen_state_manager.dart'; import 'services/update_service.dart'; import 'services/settings_service.dart'; import 'services/tv_detection_service.dart'; +import 'services/gamepad_service.dart'; import 'providers/user_profile_provider.dart'; import 'providers/plex_client_provider.dart'; import 'providers/multi_server_provider.dart'; @@ -73,6 +74,11 @@ void main() async { // Start global fullscreen state monitoring FullscreenStateManager().startMonitoring(); + // Initialize gamepad service for desktop platforms + if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) { + GamepadService.instance.start(); + } + // DTD service is available for MCP tooling connection if needed runApp(const MainApp()); diff --git a/lib/screens/libraries/libraries_screen.dart b/lib/screens/libraries/libraries_screen.dart index 636d51e1..122768cd 100644 --- a/lib/screens/libraries/libraries_screen.dart +++ b/lib/screens/libraries/libraries_screen.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import 'package:dio/dio.dart'; import '../../focus/dpad_navigator.dart'; import '../../focus/input_mode_tracker.dart'; +import '../../services/gamepad_service.dart'; import '../../../services/plex_client.dart'; import '../../models/plex_library.dart'; import '../../models/plex_metadata.dart'; @@ -106,6 +107,30 @@ class _LibrariesScreenState extends State _tabController = TabController(length: 4, vsync: this); _tabController.addListener(_onTabChanged); _loadLibraries(); + + // Register L1/R1 callbacks for tab navigation + GamepadService.onL1Pressed = _goToPreviousTab; + GamepadService.onR1Pressed = _goToNextTab; + } + + void _goToPreviousTab() { + if (_tabController.index > 0) { + setState(() { + _suppressAutoFocus = true; + _tabController.index = _tabController.index - 1; + }); + _getTabChipFocusNode(_tabController.index).requestFocus(); + } + } + + void _goToNextTab() { + if (_tabController.index < _tabController.length - 1) { + setState(() { + _suppressAutoFocus = true; + _tabController.index = _tabController.index + 1; + }); + _getTabChipFocusNode(_tabController.index).requestFocus(); + } } void _onTabChanged() { @@ -263,6 +288,9 @@ class _LibrariesScreenState extends State _browseTabChipFocusNode.dispose(); _collectionsTabChipFocusNode.dispose(); _playlistsTabChipFocusNode.dispose(); + // Clear L1/R1 callbacks + GamepadService.onL1Pressed = null; + GamepadService.onR1Pressed = null; super.dispose(); } diff --git a/lib/services/gamepad_service.dart b/lib/services/gamepad_service.dart new file mode 100644 index 00000000..6781c7d5 --- /dev/null +++ b/lib/services/gamepad_service.dart @@ -0,0 +1,349 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/services.dart'; +import 'package:gamepads/gamepads.dart'; + +import '../utils/app_logger.dart'; + +/// Service that bridges gamepad input to Flutter's focus navigation system. +/// +/// Listens to gamepad events from the `gamepads` package and translates them +/// into focus navigation actions and key events that integrate with the +/// existing keyboard navigation system. +class GamepadService { + static GamepadService? _instance; + StreamSubscription? _subscription; + + /// Callback to switch InputModeTracker to keyboard mode. + /// Set by InputModeTracker when it initializes. + static VoidCallback? onGamepadInput; + + /// Callback for L1 bumper press (previous tab). + /// Screens with tabs can listen to this. + static VoidCallback? onL1Pressed; + + /// Callback for R1 bumper press (next tab). + /// Screens with tabs can listen to this. + static VoidCallback? onR1Pressed; + + // Deadzone for analog sticks (0.0 to 1.0) + static const double _stickDeadzone = 0.5; + + // Track D-pad state to avoid repeated navigation events + bool _dpadUp = false; + bool _dpadDown = false; + bool _dpadLeft = false; + bool _dpadRight = false; + + // Track stick state to avoid repeated navigation events + bool _leftStickUp = false; + bool _leftStickDown = false; + bool _leftStickLeft = false; + bool _leftStickRight = false; + + // Track button states to prevent repeated events from button holds + final Set _pressedButtons = {}; + + GamepadService._(); + + /// Get the singleton instance. + static GamepadService get instance { + _instance ??= GamepadService._(); + return _instance!; + } + + /// Start listening to gamepad events. + /// Only active on desktop platforms (macOS, Windows, Linux). + void start() async { + // Only enable on desktop platforms + if (!Platform.isMacOS && !Platform.isWindows && !Platform.isLinux) return; + + appLogger.i('GamepadService: Starting on ${Platform.operatingSystem}'); + + // List connected gamepads + try { + final gamepads = await Gamepads.list(); + appLogger.i('GamepadService: Found ${gamepads.length} gamepad(s)'); + for (final gamepad in gamepads) { + appLogger.i(' - ${gamepad.name} (id: ${gamepad.id})'); + } + } catch (e) { + appLogger.e('GamepadService: Error listing gamepads', error: e); + } + + _subscription?.cancel(); + _subscription = Gamepads.events.listen( + _handleGamepadEvent, + onError: (e) => appLogger.e('GamepadService: Stream error', error: e), + ); + appLogger.i('GamepadService: Listening for gamepad events'); + } + + /// Stop listening to gamepad events. + void stop() { + _subscription?.cancel(); + _subscription = null; + } + + void _handleGamepadEvent(GamepadEvent event) { + final key = event.key.toLowerCase(); + final value = event.value; + + // Switch to keyboard mode on any significant gamepad input + if (value.abs() > 0.3) { + onGamepadInput?.call(); + _setTraditionalFocusHighlight(); + _scheduleFrameIfIdle(); + } + + // Handle D-pad (reported as axes on macOS) + if (_isDpadYAxis(key)) { + _handleDpadY(value); + return; + } + if (_isDpadXAxis(key)) { + _handleDpadX(value); + return; + } + + // Handle face buttons + final isPressed = value > 0.5; + final wasPressed = _pressedButtons.contains(key); + + if (isPressed && !wasPressed) { + _pressedButtons.add(key); + appLogger.d('GamepadService: Button pressed: "$key"'); + + if (_isButtonA(key)) { + appLogger.d('GamepadService: A button pressed'); + // Use enter instead of gameButtonA so it works with Flutter's built-in + // widgets (buttons, list tiles, etc.) which listen for enter + _simulateKeyPress(LogicalKeyboardKey.enter); + } else if (_isButtonB(key)) { + appLogger.d('GamepadService: B button pressed'); + // Use escape instead of gameButtonB so it works with Flutter's built-in + // widgets (bottom sheets, dialogs, menus) which only listen for escape + _simulateKeyPress(LogicalKeyboardKey.escape); + } else if (_isButtonX(key)) { + appLogger.d('GamepadService: X button pressed'); + _simulateKeyPress(LogicalKeyboardKey.gameButtonX); + } else if (_isL1(key)) { + appLogger.d('GamepadService: L1 pressed'); + onL1Pressed?.call(); + } else if (_isR1(key)) { + appLogger.d('GamepadService: R1 pressed'); + onR1Pressed?.call(); + } + } else if (!isPressed && wasPressed) { + _pressedButtons.remove(key); + } + + // Handle left analog stick + if (_isLeftStickY(key)) { + _handleLeftStickY(value); + return; + } + if (_isLeftStickX(key)) { + _handleLeftStickX(value); + return; + } + } + + void _moveFocus(TraversalDirection direction) { + appLogger.d('GamepadService: Moving focus $direction'); + + // Convert direction to arrow key and simulate a key press + // This allows widgets like HubSection that intercept key events to handle navigation + final logicalKey = _directionToKey(direction); + _simulateKeyPress(logicalKey); + } + + LogicalKeyboardKey _directionToKey(TraversalDirection direction) { + switch (direction) { + case TraversalDirection.up: + return LogicalKeyboardKey.arrowUp; + case TraversalDirection.down: + return LogicalKeyboardKey.arrowDown; + case TraversalDirection.left: + return LogicalKeyboardKey.arrowLeft; + case TraversalDirection.right: + return LogicalKeyboardKey.arrowRight; + } + } + + void _simulateKeyPress(LogicalKeyboardKey logicalKey) { + // Schedule on next frame to ensure we're on the main thread + SchedulerBinding.instance.addPostFrameCallback((_) { + final focusNode = FocusManager.instance.primaryFocus; + if (focusNode == null) { + appLogger.d('GamepadService: No focused node to send key to'); + return; + } + + // Create a synthetic key down event + final keyDownEvent = KeyDownEvent( + physicalKey: _getPhysicalKey(logicalKey), + logicalKey: logicalKey, + timeStamp: Duration(milliseconds: DateTime.now().millisecondsSinceEpoch), + ); + + // Dispatch through the focus system by walking up the focus tree + // and calling each node's onKeyEvent handler + FocusNode? node = focusNode; + KeyEventResult result = KeyEventResult.ignored; + + while (node != null && result != KeyEventResult.handled) { + // The Focus widget stores its handler in onKeyEvent + if (node.onKeyEvent != null) { + result = node.onKeyEvent!(node, keyDownEvent); + appLogger.d('GamepadService: Node ${node.debugLabel} returned $result'); + } + node = node.parent; + } + + appLogger.d('GamepadService: Final key event result: $result'); + + // Send key up event + final keyUpEvent = KeyUpEvent( + physicalKey: _getPhysicalKey(logicalKey), + logicalKey: logicalKey, + timeStamp: Duration(milliseconds: DateTime.now().millisecondsSinceEpoch), + ); + + node = focusNode; + while (node != null) { + if (node.onKeyEvent != null) { + final upResult = node.onKeyEvent!(node, keyUpEvent); + if (upResult == KeyEventResult.handled) break; + } + node = node.parent; + } + }); + } + + PhysicalKeyboardKey _getPhysicalKey(LogicalKeyboardKey logicalKey) { + if (logicalKey == LogicalKeyboardKey.gameButtonA) { + return PhysicalKeyboardKey.gameButtonA; + } else if (logicalKey == LogicalKeyboardKey.gameButtonB) { + return PhysicalKeyboardKey.gameButtonB; + } else if (logicalKey == LogicalKeyboardKey.gameButtonX) { + return PhysicalKeyboardKey.gameButtonX; + } else if (logicalKey == LogicalKeyboardKey.arrowUp) { + return PhysicalKeyboardKey.arrowUp; + } else if (logicalKey == LogicalKeyboardKey.arrowDown) { + return PhysicalKeyboardKey.arrowDown; + } else if (logicalKey == LogicalKeyboardKey.arrowLeft) { + return PhysicalKeyboardKey.arrowLeft; + } else if (logicalKey == LogicalKeyboardKey.arrowRight) { + return PhysicalKeyboardKey.arrowRight; + } else if (logicalKey == LogicalKeyboardKey.escape) { + return PhysicalKeyboardKey.escape; + } + return PhysicalKeyboardKey.enter; + } + + // macOS DualSense key matching + // D-pad reports as axes: dpad - xaxis, dpad - yaxis + bool _isDpadYAxis(String key) => key == 'dpad - yaxis'; + bool _isDpadXAxis(String key) => key == 'dpad - xaxis'; + + // Face buttons - macOS uses SF Symbol names for PlayStation controllers + bool _isButtonA(String key) => key == 'xmark.circle'; // Cross/X button (bottom) + bool _isButtonB(String key) => key == 'circle.circle'; // Circle/O button (right) + bool _isButtonX(String key) => key == 'square.circle'; // Square button (left) + + // Analog sticks + bool _isLeftStickX(String key) => key == 'l.joystick - xaxis'; + bool _isLeftStickY(String key) => key == 'l.joystick - yaxis'; + + // Bumper buttons + bool _isL1(String key) => key == 'l1.rectangle.roundedbottom'; + bool _isR1(String key) => key == 'r1.rectangle.roundedbottom'; + + // D-pad Y axis: -1 = down (visually up on controller), 1 = up (visually down) + // Inverted because macOS reports opposite of expected + void _handleDpadY(double value) { + if (value < -0.5 && !_dpadDown) { + _dpadDown = true; + _dpadUp = false; + _moveFocus(TraversalDirection.down); + } else if (value > 0.5 && !_dpadUp) { + _dpadUp = true; + _dpadDown = false; + _moveFocus(TraversalDirection.up); + } else if (value == 0) { + _dpadUp = false; + _dpadDown = false; + } + } + + // D-pad X axis: -1 = left, 1 = right, 0 = released + void _handleDpadX(double value) { + if (value < -0.5 && !_dpadLeft) { + _dpadLeft = true; + _dpadRight = false; + _moveFocus(TraversalDirection.left); + } else if (value > 0.5 && !_dpadRight) { + _dpadRight = true; + _dpadLeft = false; + _moveFocus(TraversalDirection.right); + } else if (value == 0) { + _dpadLeft = false; + _dpadRight = false; + } + } + + // Left stick Y axis - inverted like D-pad + void _handleLeftStickY(double value) { + if (value < -_stickDeadzone && !_leftStickDown) { + _leftStickDown = true; + _leftStickUp = false; + _moveFocus(TraversalDirection.down); + } else if (value > _stickDeadzone && !_leftStickUp) { + _leftStickUp = true; + _leftStickDown = false; + _moveFocus(TraversalDirection.up); + } else if (value.abs() <= _stickDeadzone) { + _leftStickUp = false; + _leftStickDown = false; + } + } + + void _handleLeftStickX(double value) { + if (value < -_stickDeadzone && !_leftStickLeft) { + _leftStickLeft = true; + _leftStickRight = false; + _moveFocus(TraversalDirection.left); + } else if (value > _stickDeadzone && !_leftStickRight) { + _leftStickRight = true; + _leftStickLeft = false; + _moveFocus(TraversalDirection.right); + } else if (value.abs() <= _stickDeadzone) { + _leftStickLeft = false; + _leftStickRight = false; + } + } + + // Ensure Material uses traditional (keyboard) focus highlights when navigating + // via gamepad. Synthetic key events we dispatch below don't go through the + // platform key pipeline, so Flutter won't automatically flip highlight mode. + void _setTraditionalFocusHighlight() { + if (FocusManager.instance.highlightStrategy != + FocusHighlightStrategy.alwaysTraditional) { + FocusManager.instance.highlightStrategy = + FocusHighlightStrategy.alwaysTraditional; + } + } + + // Force a frame when the engine is idle so focus visuals update immediately + // on gamepad input (desktop may not wake up without mouse/keyboard activity). + void _scheduleFrameIfIdle() { + final scheduler = SchedulerBinding.instance; + if (scheduler.schedulerPhase == SchedulerPhase.idle) { + scheduler.scheduleFrame(); + } + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 3b730d7a..da1af78e 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -13,6 +14,9 @@ #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) gamepads_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GamepadsLinuxPlugin"); + gamepads_linux_plugin_register_with_registrar(gamepads_linux_registrar); g_autoptr(FlPluginRegistrar) hotkey_manager_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerLinuxPlugin"); hotkey_manager_linux_plugin_register_with_registrar(hotkey_manager_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 0cb2d895..46571722 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + gamepads_linux hotkey_manager_linux os_media_controls screen_retriever_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 69c42e2b..ef0e2808 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import connectivity_plus import device_info_plus +import gamepads_darwin import hotkey_manager_macos import macos_window_utils import os_media_controls @@ -21,6 +22,7 @@ import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + GamepadsDarwinPlugin.register(with: registry.registrar(forPlugin: "GamepadsDarwinPlugin")) HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) OsMediaControlsPlugin.register(with: registry.registrar(forPlugin: "OsMediaControlsPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 6a9205fb..d5529a0b 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -4,6 +4,8 @@ PODS: - device_info_plus (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) + - gamepads_darwin (0.1.1): + - FlutterMacOS - HotKey (0.2.1) - hotkey_manager_macos (0.0.1): - FlutterMacOS @@ -34,6 +36,7 @@ DEPENDENCIES: - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - gamepads_darwin (from `Flutter/ephemeral/.symlinks/plugins/gamepads_darwin/macos`) - hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`) - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) - os_media_controls (from `Flutter/ephemeral/.symlinks/plugins/os_media_controls/macos`) @@ -56,6 +59,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos FlutterMacOS: :path: Flutter/ephemeral + gamepads_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/gamepads_darwin/macos hotkey_manager_macos: :path: Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos macos_window_utils: @@ -81,6 +86,7 @@ SPEC CHECKSUMS: connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + gamepads_darwin: 643b6a69e20ca678fae83781b7f7fc8f15f5d710 HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 hotkey_manager_macos: a4317849af96d2430fa89944d3c58977ca089fbe macos_window_utils: 23f54331a0fd51eea9e0ed347253bf48fd379d1d diff --git a/pubspec.lock b/pubspec.lock index 478af3fc..69b51c86 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -408,6 +408,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + gamepads: + dependency: "direct main" + description: + name: gamepads + sha256: "3a8a35502f0b3d28ea55e2c6c42320583389c0514a2bdd92ba0440d0cadd628e" + url: "https://pub.dev" + source: hosted + version: "0.1.9" + gamepads_android: + dependency: transitive + description: + name: gamepads_android + sha256: "7913cd171ff06d5b588cb3e1dae64390c6e1352dd2999ff19a96d822eb7441fd" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + gamepads_darwin: + dependency: transitive + description: + name: gamepads_darwin + sha256: "91250975ee196703816c55117502ec53ce4a1b881ca50dec1d0fbcb9fbb75eff" + url: "https://pub.dev" + source: hosted + version: "0.1.2+2" + gamepads_ios: + dependency: transitive + description: + name: gamepads_ios + sha256: "085459e2f677c18c4b15aee5dacc66e0b05491d4ed32bd3041c8328394e00d3a" + url: "https://pub.dev" + source: hosted + version: "0.1.3+1" + gamepads_linux: + dependency: transitive + description: + name: gamepads_linux + sha256: f4c17915a84400d7f624aadb6371424ada596eedff2a25663121453e65917e0d + url: "https://pub.dev" + source: hosted + version: "0.1.1+3" + gamepads_platform_interface: + dependency: transitive + description: + name: gamepads_platform_interface + sha256: ddab8677a4137d92e381b04cc97a8081ae4b75673dc0f24c846618d2b5226c4f + url: "https://pub.dev" + source: hosted + version: "0.1.2+1" + gamepads_web: + dependency: transitive + description: + name: gamepads_web + sha256: "4885a792f16de023c4975854ffe17ffccacb46beb1e78ac1ecde76fd10edcb22" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + gamepads_windows: + dependency: transitive + description: + name: gamepads_windows + sha256: "454320638b8ad73890530545a2f788d83a752471010edade0f00b1636c1b382d" + url: "https://pub.dev" + source: hosted + version: "0.1.4+1" glob: dependency: transitive description: @@ -516,10 +580,18 @@ packages: dependency: transitive description: name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.6.7" + js_interop: + dependency: transitive + description: + name: js_interop + sha256: "7ec859c296958ccea34dc770504bd3ff4ae52fdd9e7eeb2bacc7081ad476a1f5" + url: "https://pub.dev" + source: hosted + version: "0.0.1" json2yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0d19b9b2..a3b3fbad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: rate_limiter: ^1.0.0 path_provider: ^2.1.0 path: ^1.9.0 + gamepads: ^0.1.9 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c474c09e..6dfc4b49 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + GamepadsWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("GamepadsWindowsPluginCApi")); HotkeyManagerWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); OsMediaControlsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index ef9d7c28..b4a0397f 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus + gamepads_windows hotkey_manager_windows os_media_controls screen_retriever_windows