diff --git a/android/app/src/main/kotlin/de/jonasbark/swiftcontrol/MainActivity.kt b/android/app/src/main/kotlin/de/jonasbark/swiftcontrol/MainActivity.kt index b18ba94..5dcc5bd 100644 --- a/android/app/src/main/kotlin/de/jonasbark/swiftcontrol/MainActivity.kt +++ b/android/app/src/main/kotlin/de/jonasbark/swiftcontrol/MainActivity.kt @@ -1,5 +1,39 @@ package de.jonasbark.swiftcontrol +import android.hardware.input.InputManager +import android.os.Handler +import android.view.InputDevice +import android.view.KeyEvent +import android.view.MotionEvent import io.flutter.embedding.android.FlutterActivity +import org.flame_engine.gamepads_android.GamepadsCompatibleActivity -class MainActivity : FlutterActivity() +class MainActivity: FlutterActivity(), GamepadsCompatibleActivity { + var keyListener: ((KeyEvent) -> Boolean)? = null + var motionListener: ((MotionEvent) -> Boolean)? = null + + override fun dispatchGenericMotionEvent(motionEvent: MotionEvent): Boolean { + return motionListener?.invoke(motionEvent) ?: false + } + + override fun dispatchKeyEvent(keyEvent: KeyEvent): Boolean { + if (keyListener?.invoke(keyEvent) == true) { + return true + } + return super.dispatchKeyEvent(keyEvent) + } + + override fun registerInputDeviceListener( + listener: InputManager.InputDeviceListener, handler: Handler?) { + val inputManager = getSystemService(INPUT_SERVICE) as InputManager + inputManager.registerInputDeviceListener(listener, null) + } + + override fun registerKeyEventHandler(handler: (KeyEvent) -> Boolean) { + keyListener = handler + } + + override fun registerMotionEventHandler(handler: (MotionEvent) -> Boolean) { + motionListener = handler + } +} diff --git a/lib/bluetooth/connection.dart b/lib/bluetooth/connection.dart index bed6652..1739caf 100644 --- a/lib/bluetooth/connection.dart +++ b/lib/bluetooth/connection.dart @@ -4,6 +4,8 @@ import 'dart:io'; import 'package:dartx/dartx.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:gamepads/gamepads.dart'; +import 'package:swift_control/bluetooth/devices/gamepad/gamepad.dart'; import 'package:swift_control/main.dart'; import 'package:swift_control/utils/actions/android.dart'; import 'package:swift_control/utils/requirements/android.dart'; @@ -44,6 +46,11 @@ class Connection { UniversalBle.onScanResult = (result) { if (_lastScanResult.none((e) => e.deviceId == result.deviceId)) { _lastScanResult.add(result); + + if (kDebugMode) { + print('Scan result: ${result.name} - ${result.deviceId}'); + } + final scanResult = BaseDevice.fromScanResult(result); if (scanResult != null) { @@ -71,6 +78,33 @@ class Connection { device.processCharacteristic(characteristicUuid, value); } }; + + Gamepads.list().then((list) { + print('Connected gamepads: ${list.length}'); + if (list.isNotEmpty) { + final pads = list + .map( + (pad) => Gamepad( + BleDevice( + deviceId: pad.id, + name: pad.name, + rssi: 0, + services: [], + ), + ), + ) + .toList(); + _addDevices(pads); + Gamepads.events.listen((event) { + _actionStreams.add(LogNotification('Gamepad event: $event')); + final device = devices.firstOrNullWhere((e) => e.device.deviceId == event.gamepadId); + if (device is Gamepad) { + device.processGamepadEvent(event); + } + }); + } + }); + // ... } Future performScanning() async { @@ -120,22 +154,26 @@ class Connection { if (_connectionQueue.isNotEmpty && !_handlingConnectionQueue) { _handlingConnectionQueue = true; final device = _connectionQueue.removeAt(0); - _actionStreams.add(LogNotification('Connecting to: ${device.device.name ?? device.runtimeType}')); - _connect(device) - .then((_) { - _handlingConnectionQueue = false; - _actionStreams.add(LogNotification('Connection finished: ${device.device.name ?? device.runtimeType}')); - if (_connectionQueue.isNotEmpty) { - _handleConnectionQueue(); - } - }) - .catchError((e) { - _handlingConnectionQueue = false; - _actionStreams.add(LogNotification('Connection failed: ${device.device.name ?? device.runtimeType} - $e')); - if (_connectionQueue.isNotEmpty) { - _handleConnectionQueue(); - } - }); + if (device is! Gamepad) { + _actionStreams.add(LogNotification('Connecting to: ${device.device.name ?? device.runtimeType}')); + _connect(device) + .then((_) { + _handlingConnectionQueue = false; + _actionStreams.add(LogNotification('Connection finished: ${device.device.name ?? device.runtimeType}')); + if (_connectionQueue.isNotEmpty) { + _handleConnectionQueue(); + } + }) + .catchError((e) { + _handlingConnectionQueue = false; + _actionStreams.add( + LogNotification('Connection failed: ${device.device.name ?? device.runtimeType} - $e'), + ); + if (_connectionQueue.isNotEmpty) { + _handleConnectionQueue(); + } + }); + } } } diff --git a/lib/bluetooth/devices/base_device.dart b/lib/bluetooth/devices/base_device.dart index b4fd3d9..2b0d639 100644 --- a/lib/bluetooth/devices/base_device.dart +++ b/lib/bluetooth/devices/base_device.dart @@ -125,7 +125,7 @@ abstract class BaseDevice { return runtimeType.toString(); } - BleDevice get device => scanResult; + BleDevice get device => scanResult!; final StreamController actionStreamInternal = StreamController.broadcast(); Stream get actionStream => actionStreamInternal.stream; diff --git a/lib/bluetooth/devices/gamepad/gamepad.dart b/lib/bluetooth/devices/gamepad/gamepad.dart new file mode 100644 index 0000000..9e00984 --- /dev/null +++ b/lib/bluetooth/devices/gamepad/gamepad.dart @@ -0,0 +1,35 @@ +import 'dart:typed_data'; + +import 'package:gamepads/gamepads.dart'; +import 'package:swift_control/bluetooth/devices/base_device.dart'; +import 'package:swift_control/utils/keymap/buttons.dart'; +import 'package:universal_ble/src/models/ble_service.dart'; + +class Gamepad extends BaseDevice { + Gamepad(super.scanResult) : super(availableButtons: ControllerButton.values.toList(), isBeta: true); + + @override + Future handleServices(List services) { + // TODO: implement handleServices + throw UnimplementedError(); + } + + @override + Future processCharacteristic(String characteristic, Uint8List bytes) { + // TODO: implement processCharacteristic + throw UnimplementedError(); + } + + void processGamepadEvent(GamepadEvent event) { + print('KEy: ${event.key}'); + switch (event.key) { + case 'AXIS_HAT_X': + handleButtonsClicked([ControllerButton.shiftUpLeft]); + case 'KEYCODE_BUTTON_R1': + handleButtonsClicked([ControllerButton.shiftUpRight]); + case 'KEYCODE_BUTTON_L1': + handleButtonsClicked([ControllerButton.shiftDownLeft]); + } + handleButtonsClicked([]); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 2cb6afc..4f4e016 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + 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) screen_retriever_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 0072807..35fdcfd 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST bluetooth_low_energy_linux file_selector_linux + gamepads_linux screen_retriever_linux url_launcher_linux window_manager diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 57430bf..353c7b9 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,6 +9,7 @@ import bluetooth_low_energy_darwin import device_info_plus import file_selector_macos import flutter_local_notifications +import gamepads_darwin import keypress_simulator_macos import package_info_plus import screen_retriever_macos @@ -23,6 +24,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + GamepadsDarwinPlugin.register(with: registry.registrar(forPlugin: "GamepadsDarwinPlugin")) KeypressSimulatorMacosPlugin.register(with: registry.registrar(forPlugin: "KeypressSimulatorMacosPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index ad60c50..570c522 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -9,6 +9,8 @@ PODS: - flutter_local_notifications (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) + - gamepads_darwin (0.1.1): + - FlutterMacOS - keypress_simulator_macos (0.0.1): - FlutterMacOS - package_info_plus (0.0.1): @@ -34,6 +36,7 @@ DEPENDENCIES: - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - gamepads_darwin (from `Flutter/ephemeral/.symlinks/plugins/gamepads_darwin/macos`) - keypress_simulator_macos (from `Flutter/ephemeral/.symlinks/plugins/keypress_simulator_macos/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) @@ -54,6 +57,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos FlutterMacOS: :path: Flutter/ephemeral + gamepads_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/gamepads_darwin/macos keypress_simulator_macos: :path: Flutter/ephemeral/.symlinks/plugins/keypress_simulator_macos/macos package_info_plus: @@ -77,6 +82,7 @@ SPEC CHECKSUMS: file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d flutter_local_notifications: 4ccab5b7a22835214a6672e3f9c5e8ae207dab36 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + gamepads_darwin: 07af6c60c282902b66574c800e20b2b26e68fda8 keypress_simulator_macos: f8556f9101f9f2f175652e0bceddf0fe82a4c6b2 package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 diff --git a/pubspec.lock b/pubspec.lock index e19fb9e..515d4d0 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -343,6 +343,62 @@ packages: description: flutter source: sdk version: "0.0.0" + gamepads: + dependency: "direct main" + description: + name: gamepads + sha256: "5f8f1f31e241bce1c77e7bc5dcf5c03d9ea51a04ed775c8c299e788c512e0b54" + url: "https://pub.dev" + source: hosted + version: "0.1.8+2" + 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_windows: + dependency: transitive + description: + name: gamepads_windows + sha256: "454320638b8ad73890530545a2f788d83a752471010edade0f00b1636c1b382d" + url: "https://pub.dev" + source: hosted + version: "0.1.4+1" get_it: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fcb4d18..e009871 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: universal_ble: git: url: https://github.com/jonasbark/universal_ble.git + gamepads: ^0.1.8+2 intl: any version: ^3.0.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 7ff59c2..8d7dd6c 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("BluetoothLowEnergyWindowsPluginCApi")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + GamepadsWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("GamepadsWindowsPluginCApi")); KeypressSimulatorWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("KeypressSimulatorWindowsPluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index e7605d1..8e22eef 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST bluetooth_low_energy_windows file_selector_windows + gamepads_windows keypress_simulator_windows permission_handler_windows screen_retriever_windows