@@ -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<InputModeTracker> {
|
||||
@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<InputModeTracker> {
|
||||
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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<LibrariesScreen>
|
||||
_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<LibrariesScreen>
|
||||
_browseTabChipFocusNode.dispose();
|
||||
_collectionsTabChipFocusNode.dispose();
|
||||
_playlistsTabChipFocusNode.dispose();
|
||||
// Clear L1/R1 callbacks
|
||||
GamepadService.onL1Pressed = null;
|
||||
GamepadService.onR1Pressed = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<GamepadEvent>? _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<String> _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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <gamepads_linux/gamepads_linux_plugin.h>
|
||||
#include <hotkey_manager_linux/hotkey_manager_linux_plugin.h>
|
||||
#include <os_media_controls/os_media_controls_plugin.h>
|
||||
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
||||
@@ -13,6 +14,9 @@
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
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);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
gamepads_linux
|
||||
hotkey_manager_linux
|
||||
os_media_controls
|
||||
screen_retriever_linux
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
+74
-2
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <gamepads_windows/gamepads_windows_plugin_c_api.h>
|
||||
#include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h>
|
||||
#include <os_media_controls/os_media_controls_plugin_c_api.h>
|
||||
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||
@@ -16,6 +17,8 @@
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
GamepadsWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GamepadsWindowsPluginCApi"));
|
||||
HotkeyManagerWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi"));
|
||||
OsMediaControlsPluginCApiRegisterWithRegistrar(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus
|
||||
gamepads_windows
|
||||
hotkey_manager_windows
|
||||
os_media_controls
|
||||
screen_retriever_windows
|
||||
|
||||
Reference in New Issue
Block a user