import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; /// Shared utility for simulating key press events through the focus tree. /// /// Used by both [CompanionRemoteReceiver] and [GamepadService] to translate /// external input (remote commands, gamepad buttons) into focus-tree key events. void simulateKeyPress(LogicalKeyboardKey logicalKey) { SchedulerBinding.instance.addPostFrameCallback((_) { final focusNode = FocusManager.instance.primaryFocus; if (focusNode == null) return; final physicalKey = _getPhysicalKey(logicalKey); final keyDownEvent = KeyDownEvent( physicalKey: physicalKey, logicalKey: logicalKey, timeStamp: Duration(milliseconds: DateTime.now().millisecondsSinceEpoch), ); // Walk up the focus tree dispatching the key event FocusNode? node = focusNode; KeyEventResult result = KeyEventResult.ignored; while (node != null && result != KeyEventResult.handled) { if (node.onKeyEvent != null) { result = node.onKeyEvent!(node, keyDownEvent); } node = node.parent; } // Send key up event final keyUpEvent = KeyUpEvent( physicalKey: physicalKey, 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; } }); } /// Force a frame when the engine is idle so focus visuals update immediately /// on external input (desktop may not wake up without mouse/keyboard activity). void scheduleFrameIfIdle() { if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) { SchedulerBinding.instance.scheduleFrame(); } } PhysicalKeyboardKey _getPhysicalKey(LogicalKeyboardKey logicalKey) { if (logicalKey == LogicalKeyboardKey.arrowUp) return PhysicalKeyboardKey.arrowUp; if (logicalKey == LogicalKeyboardKey.arrowDown) return PhysicalKeyboardKey.arrowDown; if (logicalKey == LogicalKeyboardKey.arrowLeft) return PhysicalKeyboardKey.arrowLeft; if (logicalKey == LogicalKeyboardKey.arrowRight) return PhysicalKeyboardKey.arrowRight; if (logicalKey == LogicalKeyboardKey.enter) return PhysicalKeyboardKey.enter; if (logicalKey == LogicalKeyboardKey.escape) return PhysicalKeyboardKey.escape; if (logicalKey == LogicalKeyboardKey.space) return PhysicalKeyboardKey.space; if (logicalKey == LogicalKeyboardKey.contextMenu) return PhysicalKeyboardKey.contextMenu; if (logicalKey == LogicalKeyboardKey.audioVolumeUp) return PhysicalKeyboardKey.audioVolumeUp; if (logicalKey == LogicalKeyboardKey.audioVolumeDown) return PhysicalKeyboardKey.audioVolumeDown; if (logicalKey == LogicalKeyboardKey.audioVolumeMute) return PhysicalKeyboardKey.audioVolumeMute; if (logicalKey == LogicalKeyboardKey.keyF) return PhysicalKeyboardKey.keyF; if (logicalKey == LogicalKeyboardKey.gameButtonA) return PhysicalKeyboardKey.gameButtonA; if (logicalKey == LogicalKeyboardKey.gameButtonB) return PhysicalKeyboardKey.gameButtonB; if (logicalKey == LogicalKeyboardKey.gameButtonX) return PhysicalKeyboardKey.gameButtonX; return PhysicalKeyboardKey.enter; }