├── packages ├── gamepads │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ ├── analysis_options.yaml │ │ ├── README.md │ │ ├── pubspec.yaml │ │ ├── .gitignore │ │ ├── .metadata │ │ └── lib │ │ │ └── main.dart │ ├── lib │ │ ├── gamepads.dart │ │ └── src │ │ │ └── gamepads.dart │ ├── .metadata │ ├── .gitignore │ ├── pubspec.yaml │ ├── CHANGELOG.md │ └── test │ │ └── gamepads_test.dart ├── gamepads_darwin │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── .gitignore │ ├── pubspec.yaml │ ├── macos │ │ ├── gamepads_darwin.podspec │ │ └── Classes │ │ │ ├── GamepadsListener.swift │ │ │ └── GamepadsDarwinPlugin.swift │ ├── .metadata │ └── CHANGELOG.md ├── gamepads_ios │ ├── README.md │ ├── analysis_options.yaml │ ├── ios │ │ ├── .gitignore │ │ ├── gamepads_ios.podspec │ │ └── Classes │ │ │ ├── GamepadsListener.swift │ │ │ └── GamepadsIosPlugin.swift │ ├── .gitignore │ ├── pubspec.yaml │ ├── LICENSE │ └── CHANGELOG.md ├── gamepads_linux │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── linux │ │ ├── utils.h │ │ ├── utils.cc │ │ ├── .gitignore │ │ ├── gamepad.h │ │ ├── connection_listener.h │ │ ├── include │ │ │ └── gamepads_linux │ │ │ │ └── gamepads_linux_plugin.h │ │ ├── CMakeLists.txt │ │ ├── gamepad.cc │ │ ├── connection_listener.cc │ │ └── gamepads_linux_plugin.cc │ ├── .metadata │ ├── CHANGELOG.md │ ├── .gitignore │ └── pubspec.yaml ├── gamepads_web │ ├── LICENSE │ ├── README.md │ ├── CHANGELOG.md │ ├── analysis_options.yaml │ ├── .gitignore │ ├── lib │ │ ├── src │ │ │ └── gamepad_detector.dart │ │ └── gamepads_web.dart │ └── pubspec.yaml ├── gamepads_windows │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── windows │ │ ├── utils.h │ │ ├── .gitignore │ │ ├── gamepads_windows_plugin_c_api.cpp │ │ ├── include │ │ │ └── gamepads_windows │ │ │ │ └── gamepads_windows_plugin_c_api.h │ │ ├── utils.cpp │ │ ├── gamepad.h │ │ ├── gamepads_windows_plugin.h │ │ ├── CMakeLists.txt │ │ ├── gamepads_windows_plugin.cpp │ │ └── gamepad.cpp │ ├── .gitignore │ ├── pubspec.yaml │ ├── .metadata │ └── CHANGELOG.md ├── gamepads_android │ ├── README.md │ ├── gradle.properties │ ├── android │ │ ├── settings.gradle │ │ ├── kls_database.db │ │ ├── .gitignore │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── kotlin │ │ │ │ └── org │ │ │ │ └── flame_engine │ │ │ │ └── gamepads_android │ │ │ │ ├── GamepadsCompatibleActivity.kt │ │ │ │ ├── DeviceListener.kt │ │ │ │ ├── EventListener.kt │ │ │ │ └── GamepadsAndroidPlugin.kt │ │ ├── app │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── flutter │ │ │ │ └── plugins │ │ │ │ └── GeneratedPluginRegistrant.java │ │ └── build.gradle │ ├── analysis_options.yaml │ ├── pubspec.yaml │ ├── LICENSE │ └── CHANGELOG.md └── gamepads_platform_interface │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── .metadata │ ├── pubspec.yaml │ ├── .gitignore │ ├── CHANGELOG.md │ └── lib │ ├── method_channel_interface.dart │ ├── api │ ├── gamepad_state.dart │ ├── gamepad_controller.dart │ └── gamepad_event.dart │ ├── gamepads_platform_interface.dart │ └── method_channel_gamepads_platform_interface.dart ├── .github ├── FUNDING.yml └── workflows │ ├── release-publish.yml │ ├── release-prepare.yml │ ├── release-tag.yml │ ├── title-validation.yml │ └── cicd.yml ├── .markdownlintignore ├── .gitignore ├── .clang-format ├── LICENSE ├── pubspec.yaml ├── CODE_OF_CONDUCT.md ├── README.md ├── .markdownlint.yaml ├── CONTRIBUTING.md └── CHANGELOG.md /packages/gamepads/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/gamepads/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: bluefireoss 2 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | **/CHANGELOG.md 2 | LICENSE -------------------------------------------------------------------------------- /packages/gamepads_darwin/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/gamepads_ios/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/gamepads_linux/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/gamepads_linux/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/gamepads_web/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE 2 | -------------------------------------------------------------------------------- /packages/gamepads_windows/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/gamepads_android/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/gamepads_darwin/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/gamepads_web/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md 2 | -------------------------------------------------------------------------------- /packages/gamepads_windows/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/gamepads_android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.prefabVersion=2.0.0 -------------------------------------------------------------------------------- /packages/gamepads_web/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | 3 | Initial release. 4 | -------------------------------------------------------------------------------- /packages/gamepads/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads_android/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'gamepads_android' 2 | -------------------------------------------------------------------------------- /packages/gamepads_ios/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads_web/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads_android/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads_darwin/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads_linux/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads_windows/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flame_lint/analysis_options.yaml -------------------------------------------------------------------------------- /packages/gamepads/example/README.md: -------------------------------------------------------------------------------- 1 | # gamepads_example 2 | 3 | A simple example project showcasing the gamepads plugin. 4 | -------------------------------------------------------------------------------- /packages/gamepads_android/android/kls_database.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flame-engine/gamepads/HEAD/packages/gamepads_android/android/kls_database.db -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | pubspec.lock 4 | .dart_tool/ 5 | pubspec_overrides.yaml 6 | cmake-build-debug/ 7 | .vs/ 8 | **/.flutter-plugins-dependencies 9 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | std::string to_string(const std::wstring& wideString); -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | bool starts_with(const std::string& string, const std::string& prefix); -------------------------------------------------------------------------------- /packages/gamepads_android/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /packages/gamepads_android/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Defines the Chromium style for automatic reformatting. 2 | # http://clang.llvm.org/docs/ClangFormatStyleOptions.html 3 | BasedOnStyle: Chromium 4 | Standard: c++17 5 | SortIncludes: false 6 | -------------------------------------------------------------------------------- /packages/gamepads/lib/gamepads.dart: -------------------------------------------------------------------------------- 1 | export 'package:gamepads_platform_interface/api/gamepad_controller.dart'; 2 | export 'package:gamepads_platform_interface/api/gamepad_event.dart'; 3 | 4 | export 'src/gamepads.dart'; 5 | -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/utils.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool starts_with(const std::string& str, const std::string& prefix) { 4 | if (prefix.length() > str.length()) { 5 | return false; 6 | } 7 | return str.compare(0, prefix.length(), prefix) == 0; 8 | } -------------------------------------------------------------------------------- /packages/gamepads/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/gamepads_linux/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2ad6cd72c040113b47ee9055e722606a490ef0da 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ 2 | 3 | # CLion build files. 4 | cmake-build-debug 5 | 6 | # Visual Studio user-specific files. 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # Visual Studio build-related files. 13 | x64/ 14 | x86/ 15 | 16 | # Visual Studio cache files 17 | # files ending in .cache can be ignored 18 | *.[Cc]ache 19 | # but keep track of directories ending in .cache 20 | !*.[Cc]ache/ -------------------------------------------------------------------------------- /packages/gamepads/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_example 2 | description: A simple example project showcasing the gamepads plugin. 3 | resolution: workspace 4 | publish_to: 'none' 5 | 6 | version: 0.1.0 7 | 8 | environment: 9 | sdk: ">=3.9.0 <4.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | gamepads: ^0.1.9 15 | 16 | dev_dependencies: 17 | flame_lint: ^1.4.1 18 | flutter_test: 19 | sdk: flutter 20 | 21 | flutter: 22 | uses-material-design: true 23 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/gamepads_windows_plugin_c_api.cpp: -------------------------------------------------------------------------------- 1 | #include "include/gamepads_windows/gamepads_windows_plugin_c_api.h" 2 | 3 | #include 4 | 5 | #include "gamepads_windows_plugin.h" 6 | 7 | void GamepadsWindowsPluginCApiRegisterWithRegistrar( 8 | FlutterDesktopPluginRegistrarRef registrar) { 9 | gamepads_windows::GamepadsWindowsPlugin::RegisterWithRegistrar( 10 | flutter::PluginRegistrarManager::GetInstance() 11 | ->GetRegistrar(registrar)); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gamepads_linux/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.1+3 2 | 3 | - Update a dependency to the latest release. 4 | 5 | ## 0.1.1+2 6 | 7 | - Update a dependency to the latest release. 8 | 9 | ## 0.1.1+1 10 | 11 | - **REFACTOR**: Lint Kotlin, C and C++ code ([#6](https://github.com/flame-engine/gamepads/issues/6)). ([6d3e9334](https://github.com/flame-engine/gamepads/commit/6d3e9334072d24525ed7ccf9f8c7fa481c8373fc)) 12 | 13 | ## 0.1.1 14 | 15 | - Bump "gamepads_linux" to `0.1.1`. 16 | 17 | ## 0.1.0 18 | 19 | ## 0.0.1 20 | 21 | * TODO: Describe initial release. 22 | -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/gamepad.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "utils.h" 10 | 11 | namespace gamepad { 12 | struct GamepadInfo { 13 | std::string device_id; 14 | std::string name; 15 | int file_descriptor; 16 | bool alive; 17 | }; 18 | 19 | std::optional get_gamepad_info(const std::string& device); 20 | 21 | void listen(GamepadInfo* gamepad, 22 | const std::function& event_consumer); 23 | } // namespace gamepad -------------------------------------------------------------------------------- /packages/gamepads_ios/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/connection_listener.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "utils.h" 9 | 10 | namespace connection_listener { 11 | enum class ConnectionEventType { 12 | CONNECTED, 13 | DISCONNECTED, 14 | }; 15 | 16 | struct ConnectionEvent { 17 | ConnectionEventType type; 18 | std::string device_id; 19 | }; 20 | 21 | void listen(const bool* keep_reading, 22 | const std::function& event_consumer); 23 | } // namespace connection_listener -------------------------------------------------------------------------------- /.github/workflows/release-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish packages 2 | on: 3 | # Enable to also publish, when pushing a tag 4 | #push: 5 | # tags: 6 | # - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish-packages: 11 | name: Publish packages 12 | permissions: 13 | contents: write 14 | id-token: write # Required for authentication using OIDC 15 | runs-on: [ ubuntu-latest ] 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | - uses: subosito/flutter-action@v2 21 | - uses: bluefireteam/melos-action@v3 22 | with: 23 | publish: true 24 | 25 | -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_platform_interface 2 | resolution: workspace 3 | description: The platform interface for gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 4 | version: 0.1.2+1 5 | homepage: https://github.com/flame-engine/gamepads 6 | repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads_platform_interface 7 | 8 | environment: 9 | sdk: ">=3.9.0 <4.0.0" 10 | flutter: ">=3.35.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | plugin_platform_interface: ^2.1.8 16 | 17 | dev_dependencies: 18 | flame_lint: ^1.4.1 19 | flutter_test: 20 | sdk: flutter 21 | -------------------------------------------------------------------------------- /packages/gamepads_android/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import androidx.annotation.Keep; 4 | import androidx.annotation.NonNull; 5 | import io.flutter.Log; 6 | 7 | import io.flutter.embedding.engine.FlutterEngine; 8 | 9 | /** 10 | * Generated file. Do not edit. 11 | * This file is generated by the Flutter tool based on the 12 | * plugins that support the Android platform. 13 | */ 14 | @Keep 15 | public final class GeneratedPluginRegistrant { 16 | private static final String TAG = "GeneratedPluginRegistrant"; 17 | public static void registerWith(@NonNull FlutterEngine flutterEngine) { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/gamepads_web/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /packages/gamepads/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /packages/gamepads_darwin/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /packages/gamepads_ios/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /packages/gamepads_linux/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /packages/gamepads_windows/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /packages/gamepads/lib/src/gamepads.dart: -------------------------------------------------------------------------------- 1 | library gamepads; 2 | 3 | import 'package:gamepads_platform_interface/api/gamepad_controller.dart'; 4 | import 'package:gamepads_platform_interface/api/gamepad_event.dart'; 5 | import 'package:gamepads_platform_interface/gamepads_platform_interface.dart'; 6 | 7 | class Gamepads { 8 | Gamepads._(); 9 | 10 | static final _platform = GamepadsPlatformInterface.instance; 11 | 12 | static Future> list() => _platform.listGamepads(); 13 | 14 | static Stream get events => _platform.gamepadEventsStream; 15 | 16 | static Stream eventsByGamepad(String gamepadId) { 17 | return events.where((event) => event.gamepadId == gamepadId); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/gamepads_web/lib/src/gamepad_detector.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop'; 2 | 3 | import 'package:gamepads_platform_interface/api/gamepad_controller.dart'; 4 | import 'package:gamepads_platform_interface/gamepads_platform_interface.dart'; 5 | import 'package:web/web.dart'; 6 | 7 | List getGamepads(GamepadsPlatformInterface plugin) { 8 | return getGamepadList() 9 | .map( 10 | (gamepad) => GamepadController( 11 | id: gamepad.index.toString(), 12 | name: gamepad.id, 13 | plugin: plugin, 14 | ), 15 | ) 16 | .toList(); 17 | } 18 | 19 | List getGamepadList() { 20 | return window.navigator.getGamepads().toDart.whereType().toList(); 21 | } 22 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/include/gamepads_windows/gamepads_windows_plugin_c_api.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_GAMEPADS_WINDOWS_PLUGIN_C_API_H_ 2 | #define FLUTTER_PLUGIN_GAMEPADS_WINDOWS_PLUGIN_C_API_H_ 3 | 4 | #include 5 | 6 | #ifdef FLUTTER_PLUGIN_IMPL 7 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) 8 | #else 9 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) 10 | #endif 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | FLUTTER_PLUGIN_EXPORT void GamepadsWindowsPluginCApiRegisterWithRegistrar( 17 | FlutterDesktopPluginRegistrarRef registrar); 18 | 19 | #if defined(__cplusplus) 20 | } // extern "C" 21 | #endif 22 | 23 | #endif // FLUTTER_PLUGIN_GAMEPADS_WINDOWS_PLUGIN_C_API_H_ 24 | -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.2+1 2 | 3 | - **FIX**: Take other values than 1 into consideration for pressed buttons ([#46](https://github.com/flame-engine/gamepads/issues/46)). ([8c27112d](https://github.com/flame-engine/gamepads/commit/8c27112ddf1f2d0ea8e07bdcdd13c84546a72836)) 4 | 5 | ## 0.1.2 6 | 7 | - **FEAT**: Added GamepadState that can be updated ([#43](https://github.com/flame-engine/gamepads/issues/43)). ([0c9890e8](https://github.com/flame-engine/gamepads/commit/0c9890e80c423621c52226521612e307d8419308)) 8 | 9 | ## 0.1.1 10 | 11 | - Bump "gamepads_platform_interface" to `0.1.1`. 12 | 13 | ## 0.1.0 14 | 15 | - Bump "gamepads_platform_interface" to `0.1.0`. 16 | 17 | ## 0.0.1 18 | 19 | * TODO: Describe initial release. 20 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | std::string to_string(const std::wstring& wide_string) { 6 | if (wide_string.empty()) 7 | return std::string(); 8 | 9 | int requiredSize = WideCharToMultiByte(CP_UTF8, 0, wide_string.data(), -1, 10 | nullptr, 0, nullptr, nullptr); 11 | if (requiredSize == 0) 12 | return std::string(); 13 | 14 | std::vector buffer(requiredSize); 15 | WideCharToMultiByte(CP_UTF8, 0, wide_string.data(), -1, buffer.data(), 16 | requiredSize, nullptr, nullptr); 17 | 18 | return std::string(buffer.begin(), 19 | buffer.end() - 1); // Remove the null terminator 20 | } -------------------------------------------------------------------------------- /packages/gamepads_ios/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_ios 2 | resolution: workspace 3 | description: iOS implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 4 | version: 0.1.3+1 5 | homepage: https://github.com/flame-engine/gamepads 6 | repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads_ios 7 | 8 | flutter: 9 | plugin: 10 | implements: gamepads 11 | platforms: 12 | ios: 13 | pluginClass: GamepadsIosPlugin 14 | 15 | environment: 16 | sdk: ">=3.9.0 <4.0.0" 17 | flutter: ">=3.35.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | gamepads_platform_interface: ^0.1.2+1 23 | 24 | 25 | dev_dependencies: 26 | flame_lint: ^1.4.1 27 | flutter_test: 28 | sdk: flutter 29 | -------------------------------------------------------------------------------- /packages/gamepads_linux/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_linux 2 | resolution: workspace 3 | description: Linux implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 4 | version: 0.1.1+3 5 | homepage: https://github.com/flame-engine/gamepads 6 | repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads_linux 7 | 8 | flutter: 9 | plugin: 10 | implements: gamepads 11 | platforms: 12 | linux: 13 | pluginClass: GamepadsLinuxPlugin 14 | 15 | environment: 16 | sdk: ">=3.9.0 <4.0.0" 17 | flutter: ">=3.35.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | gamepads_platform_interface: ^0.1.2+1 23 | 24 | dev_dependencies: 25 | flame_lint: ^1.4.1 26 | flutter_test: 27 | sdk: flutter 28 | -------------------------------------------------------------------------------- /packages/gamepads_darwin/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_darwin 2 | resolution: workspace 3 | description: MacOS implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 4 | version: 0.1.2+2 5 | homepage: https://github.com/flame-engine/gamepads 6 | repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads_darwin 7 | 8 | flutter: 9 | plugin: 10 | implements: gamepads 11 | platforms: 12 | macos: 13 | pluginClass: GamepadsDarwinPlugin 14 | 15 | environment: 16 | sdk: ">=3.9.0 <4.0.0" 17 | flutter: ">=3.35.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | gamepads_platform_interface: ^0.1.2+1 23 | 24 | dev_dependencies: 25 | flame_lint: ^1.4.1 26 | flutter_test: 27 | sdk: flutter 28 | -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/include/gamepads_linux/gamepads_linux_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_GAMEPADS_LINUX_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_GAMEPADS_LINUX_PLUGIN_H_ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | #ifdef FLUTTER_PLUGIN_IMPL 9 | #define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) 10 | #else 11 | #define FLUTTER_PLUGIN_EXPORT 12 | #endif 13 | 14 | typedef struct _GamepadsLinuxPlugin GamepadsLinuxPlugin; 15 | typedef struct { 16 | GObjectClass parent_class; 17 | } GamepadsLinuxPluginClass; 18 | 19 | FLUTTER_PLUGIN_EXPORT GType gamepads_linux_plugin_get_type(); 20 | 21 | FLUTTER_PLUGIN_EXPORT void gamepads_linux_plugin_register_with_registrar( 22 | FlPluginRegistrar* registrar); 23 | 24 | G_END_DECLS 25 | 26 | #endif -------------------------------------------------------------------------------- /packages/gamepads_windows/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_windows 2 | resolution: workspace 3 | description: Windows implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 4 | version: 0.1.4+1 5 | homepage: https://github.com/flame-engine/gamepads 6 | repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads_windows 7 | 8 | flutter: 9 | plugin: 10 | implements: gamepads 11 | platforms: 12 | windows: 13 | pluginClass: GamepadsWindowsPluginCApi 14 | 15 | environment: 16 | sdk: ">=3.9.0 <4.0.0" 17 | flutter: ">=3.35.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | gamepads_platform_interface: ^0.1.2+1 23 | 24 | dev_dependencies: 25 | flame_lint: ^1.4.1 26 | flutter_test: 27 | sdk: flutter 28 | -------------------------------------------------------------------------------- /packages/gamepads_android/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_android 2 | resolution: workspace 3 | description: Android implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 4 | version: 0.1.6 5 | homepage: https://github.com/flame-engine/gamepads 6 | repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads_android 7 | 8 | flutter: 9 | plugin: 10 | platforms: 11 | android: 12 | package: org.flame_engine.gamepads_android 13 | pluginClass: GamepadsAndroidPlugin 14 | 15 | environment: 16 | sdk: ">=3.9.0 <4.0.0" 17 | flutter: ">=3.35.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | gamepads_platform_interface: ^0.1.2+1 23 | 24 | dev_dependencies: 25 | flame_lint: ^1.4.1 26 | flutter_test: 27 | sdk: flutter 28 | -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/lib/method_channel_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | extension StandardMethodChannel on MethodChannel { 4 | Future call(String method, Map args) async { 5 | return invokeMethod(method, args); 6 | } 7 | 8 | Future compute(String method, Map args) async { 9 | return invokeMethod(method, args); 10 | } 11 | } 12 | 13 | extension StandardMethodCall on MethodCall { 14 | Map get args => arguments as Map; 15 | 16 | String getString(String key) { 17 | return args[key] as String; 18 | } 19 | 20 | int getInt(String key) { 21 | return args[key] as int; 22 | } 23 | 24 | bool getBool(String key) { 25 | return args[key] as bool; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/release-prepare.yml: -------------------------------------------------------------------------------- 1 | name: Prepare release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | prerelease: 6 | description: 'Version as prerelease' 7 | required: false 8 | default: false 9 | type: boolean 10 | 11 | jobs: 12 | prepare-release: 13 | name: Prepare release 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - uses: subosito/flutter-action@v2 23 | - uses: bluefireteam/melos-action@v3 24 | with: 25 | run-versioning: ${{ inputs.prerelease == false }} 26 | run-versioning-prerelease: ${{ inputs.prerelease == true }} 27 | publish-dry-run: true 28 | create-pr: true 29 | -------------------------------------------------------------------------------- /.github/workflows/release-tag.yml: -------------------------------------------------------------------------------- 1 | name: Tag release 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | publish-packages: 8 | name: Create tags for release 9 | permissions: 10 | actions: write 11 | contents: write 12 | runs-on: [ ubuntu-latest ] 13 | if: contains(github.event.head_commit.message, 'chore(release)') 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - uses: subosito/flutter-action@v2 19 | - uses: bluefireteam/melos-action@v3 20 | with: 21 | tag: true 22 | - run: | 23 | melos exec -c1 --no-published --no-private --order-dependents -- \ 24 | gh workflow run release-publish.yml \ 25 | --ref \$MELOS_PACKAGE_NAME-v\$MELOS_PACKAGE_VERSION 26 | env: 27 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/title-validation.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/amannn/action-semantic-pull-request 2 | name: 'PR Title is Conventional' 3 | 4 | on: 5 | pull_request_target: 6 | types: 7 | - opened 8 | - edited 9 | - synchronize 10 | 11 | jobs: 12 | main: 13 | name: Validate PR title 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: amannn/action-semantic-pull-request@v4 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | with: 20 | types: | 21 | build 22 | chore 23 | ci 24 | docs 25 | feat 26 | fix 27 | perf 28 | refactor 29 | revert 30 | style 31 | test 32 | subjectPattern: ^[A-Z].+$ 33 | subjectPatternError: | 34 | The subject of the PR must begin with an uppercase letter. 35 | -------------------------------------------------------------------------------- /packages/gamepads_web/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_web 2 | resolution: workspace 3 | description: Web implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 4 | version: 0.1.0 5 | homepage: https://github.com/flame-engine/gamepads 6 | repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads_web 7 | 8 | flutter: 9 | plugin: 10 | implements: gamepads 11 | platforms: 12 | web: 13 | pluginClass: GamepadsWeb 14 | fileName: gamepads_web.dart 15 | 16 | environment: 17 | sdk: ">=3.9.0 <4.0.0" 18 | flutter: ">=3.35.0" 19 | 20 | dependencies: 21 | flutter: 22 | sdk: flutter 23 | flutter_web_plugins: 24 | sdk: flutter 25 | gamepads_platform_interface: ^0.1.2+1 26 | js_interop: ^0.0.1 27 | plugin_platform_interface: ^2.1.8 28 | web: ^1.1.0 29 | 30 | dev_dependencies: 31 | flame_lint: ^1.4.1 32 | flutter_test: 33 | sdk: flutter 34 | -------------------------------------------------------------------------------- /packages/gamepads_darwin/macos/gamepads_darwin.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint gamepads_darwin.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'gamepads_darwin' 7 | s.version = '0.1.1' 8 | s.summary = 'MacOS implementation of gamepads.' 9 | s.description = <<-DESC 10 | MacOS implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 11 | DESC 12 | s.homepage = 'https://flame-engine.org' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Luan' => 'luan@blue-fire.xyz' } 15 | 16 | s.source = { :path => '.' } 17 | s.source_files = 'Classes/**/*' 18 | s.dependency 'FlutterMacOS' 19 | 20 | s.platform = :osx, '10.11' 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /packages/gamepads/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | 46 | 47 | /test 48 | /macos 49 | /android 50 | /ios 51 | /web 52 | /windows 53 | /linux -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/lib/api/gamepad_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:gamepads_platform_interface/api/gamepad_event.dart'; 2 | 3 | /// The current state of a gamepad. 4 | /// 5 | /// This class keeps mutable state and is intended to be kept up-to-date by 6 | /// calling [update] with the latest [GamepadEvent]. The [analogInputs] and 7 | /// [buttonInputs] maps correspond to [KeyType.analog] and [KeyType.button], 8 | /// respectively. 9 | class GamepadState { 10 | /// Contains inputs from events where [GamepadEvent.type] is [KeyType.analog]. 11 | final Map analogInputs = {}; 12 | 13 | /// Contains inputs from events where [GamepadEvent.type] is [KeyType.button]. 14 | final Map buttonInputs = {}; 15 | 16 | /// Updates the state based on the given event. 17 | void update(GamepadEvent event) { 18 | switch (event.type) { 19 | case KeyType.analog: 20 | analogInputs[event.key] = event.value; 21 | case KeyType.button: 22 | buttonInputs[event.key] = event.value != 0; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/gamepads_ios/ios/gamepads_ios.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint gamepads_ios.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'gamepads_ios' 7 | s.version = '0.1.1' 8 | s.summary = 'iOS implementation of gamepads.' 9 | s.description = <<-DESC 10 | iOS implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms. 11 | DESC 12 | s.homepage = 'https://flame-engine.org' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Luan' => 'luan@blue-fire.xyz' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '11.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /packages/gamepads_darwin/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 8 | channel: stable 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 17 | base_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 18 | - platform: macos 19 | create_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 20 | base_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /packages/gamepads_windows/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 8 | channel: stable 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 17 | base_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 18 | - platform: windows 19 | create_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 20 | base_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /packages/gamepads_android/android/src/main/kotlin/org/flame_engine/gamepads_android/GamepadsCompatibleActivity.kt: -------------------------------------------------------------------------------- 1 | package org.flame_engine.gamepads_android 2 | 3 | import android.hardware.input.InputManager 4 | import android.os.Handler 5 | import android.view.InputDevice 6 | import android.view.KeyEvent 7 | import android.view.MotionEvent 8 | 9 | interface GamepadsCompatibleActivity { 10 | fun isGamepadsInputDevice(device: InputDevice): Boolean { 11 | return device.sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD 12 | || device.sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK 13 | // Some bluetooth keyboards are identified as GamePad. Check if it is ALPHABETIC keyboard. 14 | && device.keyboardType != InputDevice.KEYBOARD_TYPE_ALPHABETIC 15 | } 16 | 17 | fun registerInputDeviceListener(listener: InputManager.InputDeviceListener, handler: Handler?) 18 | fun registerKeyEventHandler(handler: (KeyEvent) -> Boolean) 19 | fun registerMotionEventHandler(handler: (MotionEvent) -> Boolean) 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Blue Fire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/gamepads_ios/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Blue Fire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/gamepads_android/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Blue Fire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/gamepads/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads 2 | resolution: workspace 3 | description: A Flutter plugin to handle gamepad input across multiple platforms. 4 | version: 0.1.9 5 | homepage: https://github.com/flame-engine/gamepads 6 | repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads 7 | 8 | flutter: 9 | plugin: 10 | platforms: 11 | android: 12 | default_package: gamepads_android 13 | ios: 14 | default_package: gamepads_ios 15 | linux: 16 | default_package: gamepads_linux 17 | macos: 18 | default_package: gamepads_darwin 19 | windows: 20 | default_package: gamepads_windows 21 | web: 22 | default_package: gamepads_web 23 | 24 | environment: 25 | sdk: ">=3.9.0 <4.0.0" 26 | flutter: ">=3.35.0" 27 | 28 | dependencies: 29 | flutter: 30 | sdk: flutter 31 | gamepads_android: ^0.1.6 32 | gamepads_darwin: ^0.1.2+2 33 | gamepads_ios: ^0.1.3+1 34 | gamepads_linux: ^0.1.1+3 35 | gamepads_platform_interface: ^0.1.2+1 36 | gamepads_web: ^0.1.0 37 | gamepads_windows: ^0.1.4+1 38 | 39 | dev_dependencies: 40 | flame_lint: ^1.4.1 41 | flutter_test: 42 | sdk: flutter 43 | -------------------------------------------------------------------------------- /packages/gamepads_darwin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.2+2 2 | 3 | - Update a dependency to the latest release. 4 | 5 | ## 0.1.2+1 6 | 7 | - Update a dependency to the latest release. 8 | 9 | ## 0.1.2 10 | 11 | - **FIX**: Remove extendedGamepad from gamepads array on disconnect ([#39](https://github.com/flame-engine/gamepads/issues/39)). ([b24257d3](https://github.com/flame-engine/gamepads/commit/b24257d3e467385351bf5ba14780eacfa318cd0d)) 12 | - **FIX**: Update GamepadsDarwinPlugin.swift to conditionally reference sfSymbolsName ([#23](https://github.com/flame-engine/gamepads/issues/23)). ([cfe9d339](https://github.com/flame-engine/gamepads/commit/cfe9d339f5db69b67f93179a092cd70466ecd4e1)) 13 | - **FIX**: Fix for old mac os support ([#1](https://github.com/flame-engine/gamepads/issues/1)). ([090c3be8](https://github.com/flame-engine/gamepads/commit/090c3be8313aab791160e53450f163d1104f579c)) 14 | - **FEAT**: Support for iOS ([#30](https://github.com/flame-engine/gamepads/issues/30)). ([e8cb9777](https://github.com/flame-engine/gamepads/commit/e8cb9777d42cf35f4b67629a1e6b5f03517edd35)) 15 | 16 | ## 0.1.1 17 | 18 | - Bump "gamepads_darwin" to `0.1.1`. 19 | 20 | ## 0.1.0 21 | 22 | - Bump "gamepads_darwin" to `0.1.0`. 23 | 24 | ## 0.0.1 25 | 26 | * TODO: Describe initial release. 27 | -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/lib/gamepads_platform_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:gamepads_platform_interface/api/gamepad_controller.dart'; 2 | import 'package:gamepads_platform_interface/api/gamepad_event.dart'; 3 | import 'package:gamepads_platform_interface/method_channel_gamepads_platform_interface.dart'; 4 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 5 | 6 | abstract class GamepadsPlatformInterface extends PlatformInterface { 7 | static final Object _token = Object(); 8 | 9 | GamepadsPlatformInterface() : super(token: _token); 10 | 11 | /// The default instance of [GamepadsPlatformInterface] to use. 12 | /// 13 | /// Defaults to [MethodChannelGamepadsPlatformInterface]. 14 | /// Platform-specific plugins should set this with their own platform-specific 15 | /// class that extends [GamepadsPlatformInterface] when they register 16 | /// themselves. 17 | static GamepadsPlatformInterface instance = 18 | MethodChannelGamepadsPlatformInterface(); 19 | 20 | Future> listGamepads(); 21 | 22 | Stream get gamepadEventsStream; 23 | 24 | Stream eventsByGamepad(String gamepadId) => 25 | gamepadEventsStream.where((event) => event.gamepadId == gamepadId); 26 | } 27 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/gamepad.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct Gamepad { 9 | UINT joy_id; 10 | std::string name; 11 | int num_buttons; 12 | bool alive; 13 | }; 14 | 15 | struct Event { 16 | int time; 17 | std::string type; 18 | std::string key; 19 | int value; 20 | }; 21 | 22 | class Gamepads { 23 | private: 24 | std::list diff_states(Gamepad* gamepad, 25 | const JOYINFOEX& old, 26 | const JOYINFOEX& current); 27 | bool are_states_different(const JOYINFOEX& a, const JOYINFOEX& b); 28 | void read_gamepad(Gamepad* gamepad); 29 | void connect_gamepad(UINT joy_id, std::string name, int num_buttons); 30 | 31 | public: 32 | std::map gamepads; 33 | std::optional> 34 | event_emitter; 35 | void update_gamepads(); 36 | }; 37 | 38 | extern Gamepads gamepads; 39 | 40 | std::optional CALLBACK GamepadListenerProc(HWND hwnd, 41 | UINT uMsg, 42 | WPARAM wParam, 43 | LPARAM lParam); -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/gamepads_windows_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_GAMEPADS_WINDOWS_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_GAMEPADS_WINDOWS_PLUGIN_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "gamepad.h" 10 | 11 | namespace gamepads_windows { 12 | 13 | class GamepadsWindowsPlugin : public flutter::Plugin { 14 | public: 15 | static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); 16 | 17 | GamepadsWindowsPlugin(flutter::PluginRegistrarWindows* registrar); 18 | 19 | virtual ~GamepadsWindowsPlugin(); 20 | 21 | // Disallow copy and assign. 22 | GamepadsWindowsPlugin(const GamepadsWindowsPlugin&) = delete; 23 | GamepadsWindowsPlugin& operator=(const GamepadsWindowsPlugin&) = delete; 24 | 25 | private: 26 | flutter::PluginRegistrarWindows* registrar; 27 | static inline std::unique_ptr> 28 | channel{}; 29 | 30 | int window_proc_id = -1; 31 | HDEVNOTIFY hDevNotify; 32 | 33 | void HandleMethodCall( 34 | const flutter::MethodCall& method_call, 35 | std::unique_ptr> result); 36 | 37 | void emit_gamepad_event(Gamepad* gamepad, const Event& event); 38 | }; 39 | 40 | } // namespace gamepads_windows 41 | 42 | #endif // FLUTTER_PLUGIN_GAMEPADS_WINDOWS_PLUGIN_H_ 43 | -------------------------------------------------------------------------------- /packages/gamepads_ios/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.3+1 2 | 3 | - **FIX**: Adding rightThumbstickButton and leftThumbstickButton keys for iOS ([#71](https://github.com/flame-engine/gamepads/issues/71)). ([244e16cb](https://github.com/flame-engine/gamepads/commit/244e16cbd0b2b1e0c193f8397dcae91c93416bba)) 4 | - **FIX**: Correct dpad axis mapping and add support for start/select/home buttons (iOS) ([#65](https://github.com/flame-engine/gamepads/issues/65)). ([1aef0c28](https://github.com/flame-engine/gamepads/commit/1aef0c2881db84b78f68f23b754912a2625b7902)) 5 | 6 | ## 0.1.3 7 | 8 | - **FIX**: Correct dpad axis mapping and add support for start/select/home buttons (iOS) ([#65](https://github.com/flame-engine/gamepads/issues/65)). ([1aef0c28](https://github.com/flame-engine/gamepads/commit/1aef0c2881db84b78f68f23b754912a2625b7902)) 9 | 10 | ## 0.1.2+2 11 | 12 | - Update a dependency to the latest release. 13 | 14 | ## 0.1.2+1 15 | 16 | - Update a dependency to the latest release. 17 | 18 | ## 0.1.2 19 | 20 | - **FIX**: Remove extendedGamepad from gamepads array on disconnect ([#39](https://github.com/flame-engine/gamepads/issues/39)). ([b24257d3](https://github.com/flame-engine/gamepads/commit/b24257d3e467385351bf5ba14780eacfa318cd0d)) 21 | - **FEAT**: Support for Android ([#35](https://github.com/flame-engine/gamepads/issues/35)). ([6996109e](https://github.com/flame-engine/gamepads/commit/6996109e4452406990191af1b1f10d18461c3bfc)) 22 | - **FEAT**: Support for iOS ([#30](https://github.com/flame-engine/gamepads/issues/30)). ([e8cb9777](https://github.com/flame-engine/gamepads/commit/e8cb9777d42cf35f4b67629a1e6b5f03517edd35)) 23 | 24 | ## 0.1.1 25 | 26 | * Initial release based on `gamepads_darwin`. 27 | -------------------------------------------------------------------------------- /packages/gamepads/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.9 2 | 3 | - **FEAT**: Support for Web ([#48](https://github.com/flame-engine/gamepads/issues/48)). ([eae56125](https://github.com/flame-engine/gamepads/commit/eae56125423acd3beea7387dbae0085a24ea5995)) 4 | 5 | ## 0.1.8+2 6 | 7 | - Update a dependency to the latest release. 8 | 9 | ## 0.1.8+1 10 | 11 | - Update a dependency to the latest release. 12 | 13 | ## 0.1.8 14 | 15 | - Bumped dependencies. 16 | 17 | ## 0.1.7 18 | 19 | - Bumped dependencies. 20 | 21 | ## 0.1.6 22 | 23 | - Bump dependencies. 24 | 25 | ## 0.1.5 26 | 27 | - Bump version to 0.1.5 (due to previous manual versioning) 28 | 29 | ## 0.1.4 30 | 31 | - Bump "gamepads" to `0.1.4`. 32 | 33 | ## 0.1.4 34 | 35 | - fix: Take other values than 1 into consideration for pressed buttons 36 | 37 | ## 0.1.3 38 | 39 | - **FEAT**: Added GamepadState that can be updated ([#43](https://github.com/flame-engine/gamepads/issues/43)). ([0c9890e8](https://github.com/flame-engine/gamepads/commit/0c9890e80c423621c52226521612e307d8419308)) 40 | 41 | ## 0.1.2 42 | 43 | - **REFACTOR**: Lint Kotlin, C and C++ code ([#6](https://github.com/flame-engine/gamepads/issues/6)). ([6d3e9334](https://github.com/flame-engine/gamepads/commit/6d3e9334072d24525ed7ccf9f8c7fa481c8373fc)) 44 | - **FEAT**: Support for Android ([#35](https://github.com/flame-engine/gamepads/issues/35)). ([6996109e](https://github.com/flame-engine/gamepads/commit/6996109e4452406990191af1b1f10d18461c3bfc)) 45 | - **FEAT**: Support for iOS ([#30](https://github.com/flame-engine/gamepads/issues/30)). ([e8cb9777](https://github.com/flame-engine/gamepads/commit/e8cb9777d42cf35f4b67629a1e6b5f03517edd35)) 46 | 47 | ## 0.1.1 48 | 49 | - Bump "gamepads" to `0.1.1`. 50 | 51 | ## 0.1.0 52 | 53 | - Bump "gamepads" to `0.1.0`. 54 | 55 | -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/lib/api/gamepad_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:gamepads_platform_interface/api/gamepad_event.dart'; 4 | import 'package:gamepads_platform_interface/api/gamepad_state.dart'; 5 | import 'package:gamepads_platform_interface/gamepads_platform_interface.dart'; 6 | 7 | /// Represents a single, currently connected joystick controller (or gamepad). 8 | /// 9 | /// By calling the constructor, this object will automatically subscribe to 10 | /// events and update its internal [state]. To stop listening, be sure to call 11 | /// [dispose]. Failing to do so may result in the object leaking memory. 12 | class GamepadController { 13 | /// A unique identifier for the gamepad controller. 14 | /// 15 | /// On Linux, it maps to the file descriptor path. 16 | /// On macOs and Windows, it's just the index of the connected controller. 17 | final String id; 18 | 19 | /// A user-facing, platform-dependant name for the gamepad controller. 20 | final String name; 21 | 22 | final state = GamepadState(); 23 | 24 | StreamSubscription? _subscription; 25 | 26 | GamepadController({ 27 | required this.id, 28 | required this.name, 29 | required GamepadsPlatformInterface plugin, 30 | }) { 31 | _subscription = plugin.eventsByGamepad(id).listen(state.update); 32 | } 33 | 34 | factory GamepadController.parse( 35 | Map map, 36 | GamepadsPlatformInterface plugin, 37 | ) { 38 | final id = map['id'] as String; 39 | final name = map['name'] as String; 40 | return GamepadController(id: id, name: name, plugin: plugin); 41 | } 42 | 43 | /// Stops listening for new inputs. 44 | Future dispose() async { 45 | await _subscription?.cancel(); 46 | _subscription = null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/gamepads_android/android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'org.flame_engine.gamepads_android' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.7.10' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:7.3.0' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | if (project.android.hasProperty("namespace")) { 29 | namespace 'org.flame_engine.gamepads_android' 30 | } 31 | 32 | compileSdk 34 33 | 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | 39 | kotlinOptions { 40 | jvmTarget = '1.8' 41 | } 42 | 43 | sourceSets { 44 | main.java.srcDirs += 'src/main/kotlin' 45 | test.java.srcDirs += 'src/test/kotlin' 46 | } 47 | 48 | defaultConfig { 49 | minSdkVersion 19 50 | } 51 | 52 | dependencies { 53 | testImplementation 'org.jetbrains.kotlin:kotlin-test' 54 | testImplementation 'org.mockito:mockito-core:5.0.0' 55 | } 56 | 57 | testOptions { 58 | unitTests.all { 59 | useJUnitPlatform() 60 | 61 | testLogging { 62 | events "passed", "skipped", "failed", "standardOut", "standardError" 63 | outputs.upToDateWhen {false} 64 | showStandardStreams = true 65 | } 66 | } 67 | } 68 | 69 | buildFeatures { 70 | prefab true 71 | } 72 | } -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # See: https://github.com/flutter/flutter/blob/bc0ec85717de14677a0253814faead368ac2abe8/packages/flutter_tools/templates/app_shared/linux.tmpl/CMakeLists.txt.tmpl 2 | cmake_minimum_required(VERSION 3.20) 3 | 4 | set(PROJECT_NAME "gamepads_linux") 5 | project(${PROJECT_NAME} LANGUAGES CXX) 6 | 7 | set(CMAKE_CXX_STANDARD 20) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | 10 | include(FetchContent) 11 | 12 | # This value is used when generating builds using this plugin, so it must not be changed 13 | set(PLUGIN_NAME "${PROJECT_NAME}_plugin") 14 | 15 | add_library(${PLUGIN_NAME} SHARED 16 | "gamepads_linux_plugin.cc" 17 | "gamepad.h" 18 | "gamepad.cc" 19 | "connection_listener.h" 20 | "connection_listener.cc" 21 | "utils.h" 22 | "utils.cc" 23 | ) 24 | apply_standard_settings(${PLUGIN_NAME}) 25 | set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) 26 | 27 | # System-level dependencies. 28 | find_package(PkgConfig REQUIRED) 29 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 30 | pkg_search_module(GLIB REQUIRED glib-2.0) 31 | include_directories( 32 | ${GLIB_INCLUDE_DIRS} 33 | ${CMAKE_CURRENT_SOURCE_DIR} 34 | ) 35 | link_directories( 36 | ${GLIB_LIBRARY_DIRS} 37 | ) 38 | target_include_directories(${PLUGIN_NAME} PRIVATE ${GLIB_INCLUDE_DIRS}) 39 | target_link_libraries(${PLUGIN_NAME} PRIVATE ${GLIB_LIBRARIES}) 40 | 41 | target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) 42 | target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") 43 | target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) 44 | target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) 45 | 46 | # List of absolute paths to libraries that should be bundled with the plugin 47 | set(gamepads_linux_bundled_libraries "" PARENT_SCOPE) -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gamepads_workspace 2 | repository: https://github.com/flame-engine/gamepads 3 | workspace: 4 | - packages/gamepads 5 | - packages/gamepads/example 6 | - packages/gamepads_android 7 | - packages/gamepads_darwin 8 | - packages/gamepads_ios 9 | - packages/gamepads_linux 10 | - packages/gamepads_platform_interface 11 | - packages/gamepads_web 12 | - packages/gamepads_windows 13 | 14 | environment: 15 | sdk: '>=3.9.0 <4.0.0' 16 | 17 | dev_dependencies: 18 | melos: ^7.1.0 19 | 20 | melos: 21 | command: 22 | version: 23 | branch: main 24 | 25 | bootstrap: 26 | environment: 27 | sdk: ">=3.9.0 <4.0.0" 28 | flutter: ">=3.35.0" 29 | dev_dependencies: 30 | flame_lint: ^1.4.1 31 | 32 | scripts: 33 | lint: 34 | run: melos run analyze && melos run format 35 | description: Run all static analysis checks. 36 | 37 | analyze: 38 | run: melos exec -c 10 flutter analyze --fatal-infos 39 | description: Run `flutter analyze` for all packages. 40 | 41 | format: 42 | run: melos exec dart format . --fix 43 | description: Run `dart format` for all packages. 44 | 45 | format-check: 46 | run: melos exec dart format . --set-exit-if-changed 47 | description: Run `dart format` checks for all packages. 48 | 49 | dartdoc: 50 | run: melos exec flutter pub run dartdoc 51 | description: Run dartdoc checks for all non-example packages. 52 | packageFilters: 53 | ignore: "*_example" 54 | 55 | test:select: 56 | run: melos exec flutter test 57 | packageFilters: 58 | dirExists: test 59 | description: Run `flutter test` for selected packages. 60 | 61 | test: 62 | run: melos run test:select --no-select 63 | description: Run all Flutter tests in this project. 64 | 65 | -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/lib/method_channel_gamepads_platform_interface.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:gamepads_platform_interface/api/gamepad_controller.dart'; 6 | import 'package:gamepads_platform_interface/api/gamepad_event.dart'; 7 | import 'package:gamepads_platform_interface/gamepads_platform_interface.dart'; 8 | import 'package:gamepads_platform_interface/method_channel_interface.dart'; 9 | 10 | class MethodChannelGamepadsPlatformInterface extends GamepadsPlatformInterface { 11 | final MethodChannel _channel = const MethodChannel('xyz.luan/gamepads'); 12 | 13 | MethodChannelGamepadsPlatformInterface() { 14 | _channel.setMethodCallHandler(platformCallHandler); 15 | } 16 | 17 | @override 18 | Future> listGamepads() async { 19 | final result = await _channel.compute>( 20 | 'listGamepads', 21 | {}, 22 | ); 23 | return result!.map((Object? e) { 24 | return GamepadController.parse(e! as Map, this); 25 | }).toList(); 26 | } 27 | 28 | Future platformCallHandler(MethodCall call) async { 29 | switch (call.method) { 30 | case 'onGamepadEvent': 31 | emitGamepadEvent(GamepadEvent.parse(call.args)); 32 | } 33 | } 34 | 35 | void emitGamepadEvent(GamepadEvent event) { 36 | _gamepadEventsStreamController.add(event); 37 | } 38 | 39 | final StreamController _gamepadEventsStreamController = 40 | StreamController.broadcast(); 41 | 42 | @override 43 | Stream get gamepadEventsStream => 44 | _gamepadEventsStreamController.stream; 45 | 46 | @mustCallSuper 47 | Future dispose() async { 48 | _gamepadEventsStreamController.close(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/gamepads/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "300451adae589accbece3490f4396f10bdf15e6e" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 17 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 18 | - platform: android 19 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 20 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 21 | - platform: ios 22 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 23 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 24 | - platform: linux 25 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 26 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 27 | - platform: macos 28 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 29 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 30 | - platform: web 31 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 32 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 33 | - platform: windows 34 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e 35 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /packages/gamepads_android/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.6 2 | 3 | - **FIX**: Fix incorrect handling of unhandled MotionEvent axes on android ([#74](https://github.com/flame-engine/gamepads/issues/74)). ([dbd2ced1](https://github.com/flame-engine/gamepads/commit/dbd2ced145a8d009e68696b090a55664d25b2a4a)) 4 | - **FEAT**: Add support for AXIS_WHEEL on Android gamepads ([#75](https://github.com/flame-engine/gamepads/issues/75)). ([f3c03351](https://github.com/flame-engine/gamepads/commit/f3c033513e573e9777c1491aba3be971d2dd4cf5)) 5 | 6 | ## 0.1.5 7 | 8 | - **FIX**: Bluetooth keyboards should not be recognized as gamepads ([#66](https://github.com/flame-engine/gamepads/issues/66)). ([9c657182](https://github.com/flame-engine/gamepads/commit/9c65718227e3638bfc036e32d05cb3e5b88e4448)) 9 | - **FEAT**: Add AXIS_BRAKE and AXIS_GAS as supported axes on Android. ([#50](https://github.com/flame-engine/gamepads/issues/50)). ([adfb8d1f](https://github.com/flame-engine/gamepads/commit/adfb8d1fa2206571d6c59315697d3cf9c951b423)) 10 | 11 | ## 0.1.4 12 | 13 | - **FIX**: Bluetooth keyboards should not be recognized as gamepads ([#66](https://github.com/flame-engine/gamepads/issues/66)). ([9c657182](https://github.com/flame-engine/gamepads/commit/9c65718227e3638bfc036e32d05cb3e5b88e4448)) 14 | 15 | ## 0.1.3 16 | 17 | - **FEAT**: Add AXIS_BRAKE and AXIS_GAS as supported axes on Android. ([#50](https://github.com/flame-engine/gamepads/issues/50)). ([adfb8d1f](https://github.com/flame-engine/gamepads/commit/adfb8d1fa2206571d6c59315697d3cf9c951b423)) 18 | 19 | ## 0.1.2+2 20 | 21 | - Update a dependency to the latest release. 22 | 23 | ## 0.1.2+1 24 | 25 | - Update a dependency to the latest release. 26 | 27 | ## 0.1.2 28 | 29 | - **FEAT**: Support for Android ([#35](https://github.com/flame-engine/gamepads/issues/35)). ([6996109e](https://github.com/flame-engine/gamepads/commit/6996109e4452406990191af1b1f10d18461c3bfc)) 30 | 31 | ## 0.1.1 32 | 33 | - Initial Android version. 34 | -------------------------------------------------------------------------------- /packages/gamepads_windows/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.4+1 2 | 3 | - **FIX**: Use `std::optional` for return from `GamepadListenerProc` ([#58](https://github.com/flame-engine/gamepads/issues/58)). ([fbf52fd2](https://github.com/flame-engine/gamepads/commit/fbf52fd281cec345b110c8a5c63053cafd4571cd)) 4 | - **FIX**: Window resizing bug ([#56](https://github.com/flame-engine/gamepads/issues/56)). ([ae7c8f3d](https://github.com/flame-engine/gamepads/commit/ae7c8f3d7f670c7cbb3d9c55119736cbf4a8f54a)) 5 | - **FIX**: Update gamepad.cpp includes to fix windows compilation error ([#51](https://github.com/flame-engine/gamepads/issues/51)). ([d6b8ab43](https://github.com/flame-engine/gamepads/commit/d6b8ab4346b9e5f617dde5fcb54457721a54cb73)) 6 | 7 | ## 0.1.4 8 | 9 | - **FIX**: Use `std::optional` for return from `GamepadListenerProc` ([#58](https://github.com/flame-engine/gamepads/issues/58)). ([fbf52fd2](https://github.com/flame-engine/gamepads/commit/fbf52fd281cec345b110c8a5c63053cafd4571cd)) 10 | 11 | ## 0.1.3 12 | 13 | - **FIX**: Window resizing bug ([#56](https://github.com/flame-engine/gamepads/issues/56)). ([ae7c8f3d](https://github.com/flame-engine/gamepads/commit/ae7c8f3d7f670c7cbb3d9c55119736cbf4a8f54a)) 14 | 15 | ## 0.1.2 16 | 17 | - **FIX**: Update gamepad.cpp includes to fix windows compilation error ([#51](https://github.com/flame-engine/gamepads/issues/51)). ([d6b8ab43](https://github.com/flame-engine/gamepads/commit/d6b8ab4346b9e5f617dde5fcb54457721a54cb73)) 18 | 19 | ## 0.1.1+3 20 | 21 | - Update a dependency to the latest release. 22 | 23 | ## 0.1.1+2 24 | 25 | - Update a dependency to the latest release. 26 | 27 | ## 0.1.1+1 28 | 29 | - **REFACTOR**: Lint Kotlin, C and C++ code ([#6](https://github.com/flame-engine/gamepads/issues/6)). ([6d3e9334](https://github.com/flame-engine/gamepads/commit/6d3e9334072d24525ed7ccf9f8c7fa481c8373fc)) 30 | 31 | ## 0.1.1 32 | 33 | - Bump "gamepads_windows" to `0.1.1`. 34 | 35 | ## 0.1.0 36 | 37 | - Bump "gamepads_windows" to `0.1.0`. 38 | 39 | -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/gamepad.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "gamepad.h" 12 | #include "utils.h" 13 | 14 | using namespace gamepad; 15 | 16 | /** 17 | * Reads a joystick event from the joystick gamepad_id. 18 | * 19 | * Returns 0 on success. Otherwise -1 is returned. 20 | */ 21 | static int read_event(int fd, struct js_event* event) { 22 | ssize_t bytes; 23 | 24 | bytes = read(fd, event, sizeof(*event)); 25 | 26 | if (bytes == sizeof(*event)) { 27 | return 0; 28 | } 29 | 30 | /* Error, could not read full event. */ 31 | return -1; 32 | } 33 | 34 | namespace gamepad { 35 | std::optional get_gamepad_info(const std::string& device_id) { 36 | std::cout << "Listening to gamepad " << device_id << std::endl; 37 | 38 | int file_descriptor = open(device_id.c_str(), O_RDONLY); 39 | if (file_descriptor == -1) { 40 | std::cerr << "Could not open joystick: " << file_descriptor << std::endl; 41 | return std::nullopt; 42 | } 43 | 44 | char name[128]; 45 | if (ioctl(file_descriptor, JSIOCGNAME(sizeof(name)), name) < 0) { 46 | std::cerr << "Failed to get joystick name: " << strerror(errno) 47 | << std::endl; 48 | strcpy(name, "Unknown"); 49 | } 50 | 51 | return {{device_id, name, file_descriptor, true}}; 52 | } 53 | 54 | void listen(GamepadInfo* gamepad, 55 | const std::function& event_consumer) { 56 | std::cout << "Listening to gamepad " << gamepad->device_id << std::endl; 57 | 58 | while (gamepad->alive) { 59 | struct js_event event; 60 | read_event(gamepad->file_descriptor, &event); 61 | event_consumer(event); 62 | } 63 | 64 | std::cout << "Stopped listening for events: " << gamepad->device_id 65 | << std::endl; 66 | close(gamepad->file_descriptor); 67 | } 68 | } // namespace gamepad -------------------------------------------------------------------------------- /packages/gamepads_platform_interface/lib/api/gamepad_event.dart: -------------------------------------------------------------------------------- 1 | /// What type of input is being pressed. 2 | enum KeyType { 3 | /// Analog inputs have a range of possible values depending on how far/hard 4 | /// they are pressed. 5 | /// 6 | /// They represent analog sticks, back triggers, some kinds of d-pads, etc. 7 | analog, 8 | 9 | /// Buttons have only two states, pressed (1.0) or not (0.0). 10 | /// 11 | /// They represent the face buttons, system buttons, and back bumpers, etc. 12 | button, 13 | } 14 | 15 | /// Represents a single "input" listened from a gamepad, i.e. a particular 16 | /// change on the state of the buttons and keys. 17 | /// 18 | /// For [KeyType.button], it means a button was either pressed (1.0) or 19 | /// released (0.0). 20 | /// For [KeyType.analog], it means the exact value associated with that key 21 | /// was changed. 22 | class GamepadEvent { 23 | /// The id of the gamepad controller that fired the event. 24 | final String gamepadId; 25 | 26 | /// The timestamp in which the event was fired, in milliseconds since epoch. 27 | final int timestamp; 28 | 29 | /// The [KeyType] of the key that was triggered. 30 | final KeyType type; 31 | 32 | /// A platform-dependant identifier for the key that was triggered. 33 | final String key; 34 | 35 | /// The current value of the key. 36 | final double value; 37 | 38 | GamepadEvent({ 39 | required this.gamepadId, 40 | required this.timestamp, 41 | required this.type, 42 | required this.key, 43 | required this.value, 44 | }); 45 | 46 | @override 47 | String toString() { 48 | return '[$gamepadId] $key: $value'; 49 | } 50 | 51 | factory GamepadEvent.parse(Map map) { 52 | final gamepadId = map['gamepadId'] as String; 53 | final timestamp = map['time'] as int; 54 | final type = KeyType.values.byName(map['type'] as String); 55 | final key = map['key'] as String; 56 | final value = map['value'] as double; 57 | 58 | return GamepadEvent( 59 | gamepadId: gamepadId, 60 | timestamp: timestamp, 61 | type: type, 62 | key: key, 63 | value: value, 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/cicd.yml: -------------------------------------------------------------------------------- 1 | name: cicd 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | 10 | env: 11 | FLUTTER_MIN_VERSION: '3.35.0' 12 | 13 | jobs: 14 | # BEGIN LINTING STAGE 15 | format: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: subosito/flutter-action@v2 20 | - uses: bluefireteam/melos-action@v3 21 | - run: melos run format-check 22 | 23 | analyze: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: subosito/flutter-action@v2 28 | with: 29 | flutter-version: ${{env.FLUTTER_MIN_VERSION}} 30 | - uses: bluefireteam/melos-action@v3 31 | - run: melos exec dart analyze . 32 | 33 | analyze-latest: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: subosito/flutter-action@v2 38 | - uses: bluefireteam/melos-action@v3 39 | - name: "Analyze with latest stable" 40 | uses: invertase/github-action-dart-analyzer@v3 41 | with: 42 | fatal-infos: true 43 | 44 | markdown-lint: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: actions/setup-node@v4 49 | with: 50 | node-version: 18 51 | - run: npm install -g markdownlint-cli 52 | - run: markdownlint . -p .markdownlintignore -c .markdownlint.yaml 53 | 54 | super-linter: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v3 58 | - uses: super-linter/super-linter@v5 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | VALIDATE_ALL_CODEBASE: true 62 | DEFAULT_BRANCH: main 63 | VALIDATE_CLANG_FORMAT: true 64 | # END LINTING STAGE 65 | 66 | # BEGIN TESTING STAGE 67 | test: 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v4 71 | - uses: subosito/flutter-action@v2 72 | with: 73 | cache: true 74 | - uses: bluefireteam/melos-action@v3 75 | - name: Run tests 76 | run: melos test 77 | # END TESTING STAGE 78 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The Flutter tooling requires that developers have a version of Visual Studio 2 | # installed that includes CMake 3.14 or later. You should not increase this 3 | # version, as doing so will cause the plugin to fail to compile for some 4 | # customers of the plugin. 5 | cmake_minimum_required(VERSION 3.14) 6 | 7 | # Project-level configuration. 8 | set(PROJECT_NAME "gamepads_windows") 9 | project(${PROJECT_NAME} LANGUAGES CXX) 10 | 11 | # This value is used when generating builds using this plugin, so it must 12 | # not be changed 13 | set(PLUGIN_NAME "gamepads_windows_plugin") 14 | 15 | # Any new source files that you add to the plugin should be added here. 16 | list(APPEND PLUGIN_SOURCES 17 | "gamepads_windows_plugin.cpp" 18 | "gamepads_windows_plugin.h" 19 | "gamepad.cpp" 20 | "gamepad.h" 21 | "utils.cpp" 22 | "utils.h" 23 | ) 24 | 25 | # Define the plugin library target. Its name must not be changed (see comment 26 | # on PLUGIN_NAME above). 27 | add_library(${PLUGIN_NAME} SHARED 28 | "include/gamepads_windows/gamepads_windows_plugin_c_api.h" 29 | "gamepads_windows_plugin_c_api.cpp" 30 | ${PLUGIN_SOURCES} 31 | ) 32 | 33 | # Apply a standard set of build settings that are configured in the 34 | # application-level CMakeLists.txt. This can be removed for plugins that want 35 | # full control over build settings. 36 | apply_standard_settings(${PLUGIN_NAME}) 37 | 38 | # Symbols are hidden by default to reduce the chance of accidental conflicts 39 | # between plugins. This should not be removed; any symbols that should be 40 | # exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. 41 | set_target_properties(${PLUGIN_NAME} PROPERTIES 42 | CXX_VISIBILITY_PRESET hidden) 43 | target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) 44 | 45 | # Source include directories and library dependencies. Add any plugin-specific 46 | # dependencies here. 47 | target_include_directories(${PLUGIN_NAME} INTERFACE 48 | "${CMAKE_CURRENT_SOURCE_DIR}/include") 49 | target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) 50 | 51 | # List of absolute paths to libraries that should be bundled with the plugin. 52 | # This list could contain prebuilt libraries, or libraries created by an 53 | # external build triggered from this build file. 54 | set(gamepads_windows_bundled_libraries 55 | "" 56 | PARENT_SCOPE 57 | ) 58 | -------------------------------------------------------------------------------- /packages/gamepads_ios/ios/Classes/GamepadsListener.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import GameController 3 | 4 | class GamepadsListener { 5 | var gamepads: [GCExtendedGamepad] = [] 6 | var listener: ((Int, GCExtendedGamepad, GCControllerElement) -> Void)? 7 | 8 | init() { 9 | NotificationCenter.default.addObserver( 10 | self, 11 | selector: #selector(joystickDidConnect), 12 | name: .GCControllerDidConnect, 13 | object: nil 14 | ) 15 | NotificationCenter.default.addObserver( 16 | self, 17 | selector: #selector(joystickDidDisconnect), 18 | name: .GCControllerDidDisconnect, 19 | object: nil 20 | ) 21 | } 22 | 23 | deinit { 24 | NotificationCenter.default.removeObserver(self) 25 | } 26 | 27 | @objc private func joystickDidConnect(notification: NSNotification) { 28 | if let controller = notification.object as? GCController { 29 | if let gamepad = controller.extendedGamepad { 30 | gamepads.append(gamepad) 31 | let gamepadId = getAndSetPlayerId(of: gamepad) 32 | 33 | gamepad.valueChangedHandler = { gamepad, element in 34 | if let listener = self.listener { 35 | listener(gamepadId, gamepad, element); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | @objc private func joystickDidDisconnect(notification: NSNotification) { 43 | if let controller = notification.object as? GCController { 44 | gamepads.removeAll(where: { $0 == controller.extendedGamepad }) 45 | } 46 | } 47 | 48 | private func getAndSetPlayerId(of gamepad: GCExtendedGamepad) -> Int { 49 | let gamepadId = gamepads.firstIndex(of: gamepad) ?? -1 50 | gamepad.controller?.playerIndex = toPlayerIndex(index: gamepadId) 51 | return gamepadId 52 | } 53 | 54 | private func toPlayerIndex(index: Int) -> GCControllerPlayerIndex { 55 | switch index { 56 | case 0: 57 | return GCControllerPlayerIndex.index1 58 | case 1: 59 | return GCControllerPlayerIndex.index2 60 | case 2: 61 | return GCControllerPlayerIndex.index3 62 | case 3: 63 | return GCControllerPlayerIndex.index4 64 | default: 65 | return GCControllerPlayerIndex.indexUnset 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/gamepads_darwin/macos/Classes/GamepadsListener.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import GameController 3 | 4 | class GamepadsListener { 5 | var gamepads: [GCExtendedGamepad] = [] 6 | var listener: ((Int, GCExtendedGamepad, GCControllerElement) -> Void)? 7 | 8 | init() { 9 | NotificationCenter.default.addObserver( 10 | self, 11 | selector: #selector(joystickDidConnect), 12 | name: .GCControllerDidConnect, 13 | object: nil 14 | ) 15 | NotificationCenter.default.addObserver( 16 | self, 17 | selector: #selector(joystickDidDisconnect), 18 | name: .GCControllerDidDisconnect, 19 | object: nil 20 | ) 21 | } 22 | 23 | deinit { 24 | NotificationCenter.default.removeObserver(self) 25 | } 26 | 27 | @objc private func joystickDidConnect(notification: NSNotification) { 28 | if let controller = notification.object as? GCController { 29 | if let gamepad = controller.extendedGamepad { 30 | gamepads.append(gamepad) 31 | let gamepadId = getAndSetPlayerId(of: gamepad) 32 | 33 | gamepad.valueChangedHandler = { gamepad, element in 34 | if let listener = self.listener { 35 | listener(gamepadId, gamepad, element); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | @objc private func joystickDidDisconnect(notification: NSNotification) { 43 | if let controller = notification.object as? GCController { 44 | gamepads.removeAll(where: { $0 == controller.extendedGamepad }) 45 | } 46 | } 47 | 48 | private func getAndSetPlayerId(of gamepad: GCExtendedGamepad) -> Int { 49 | let gamepadId = gamepads.firstIndex(of: gamepad) ?? -1 50 | gamepad.controller?.playerIndex = toPlayerIndex(index: gamepadId) 51 | return gamepadId 52 | } 53 | 54 | private func toPlayerIndex(index: Int) -> GCControllerPlayerIndex { 55 | switch index { 56 | case 0: 57 | return GCControllerPlayerIndex.index1 58 | case 1: 59 | return GCControllerPlayerIndex.index2 60 | case 2: 61 | return GCControllerPlayerIndex.index3 62 | case 3: 63 | return GCControllerPlayerIndex.index4 64 | default: 65 | return GCControllerPlayerIndex.indexUnset 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/gamepads_android/android/src/main/kotlin/org/flame_engine/gamepads_android/DeviceListener.kt: -------------------------------------------------------------------------------- 1 | package org.flame_engine.gamepads_android 2 | 3 | import android.hardware.input.InputManager 4 | import android.util.Log 5 | import android.view.InputDevice 6 | 7 | class DeviceListener(val isGamepadsInputDevice: (device: InputDevice) -> Boolean): InputManager.InputDeviceListener { 8 | private val devicesLookup: MutableMap = mutableMapOf() 9 | private val TAG = "ConnectionListener" 10 | 11 | init { 12 | getGameControllerIds() 13 | } 14 | 15 | fun getDevices(): Map { 16 | return devicesLookup.toMap() 17 | } 18 | 19 | private fun getGameControllerIds() { 20 | val gameControllerDeviceIds = mutableListOf() 21 | val deviceIds = InputDevice.getDeviceIds() 22 | deviceIds.forEach { deviceId -> 23 | InputDevice.getDevice(deviceId).apply { 24 | if (this != null) { 25 | if (isGamepadsInputDevice(this)) { 26 | Log.i(TAG, "${this.name} passed input device test") 27 | devicesLookup[deviceId] = this 28 | } else { 29 | Log.e(TAG, "${this.name} failed input device test") 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | fun containsKey(deviceId: Int): Boolean { 37 | return devicesLookup.containsKey(deviceId) 38 | } 39 | 40 | override fun onInputDeviceAdded(deviceId: Int) { 41 | val device: InputDevice? = InputDevice.getDevice(deviceId) 42 | if (device != null) { 43 | if (isGamepadsInputDevice(device)) { 44 | Log.i(TAG, "${device.name} passed input device test") 45 | devicesLookup[deviceId] = device 46 | } else { 47 | Log.e(TAG, "${device.name} failed input device test") 48 | } 49 | } 50 | } 51 | 52 | override fun onInputDeviceRemoved(deviceId: Int) { 53 | val device: InputDevice? = InputDevice.getDevice(deviceId) 54 | devicesLookup.remove(deviceId) 55 | } 56 | 57 | override fun onInputDeviceChanged(deviceId: Int) { 58 | val device: InputDevice? = InputDevice.getDevice(deviceId) 59 | if (device != null && isGamepadsInputDevice(device)) { 60 | devicesLookup[deviceId] = device 61 | } else { 62 | devicesLookup.remove(deviceId) 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /packages/gamepads/test/gamepads_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import 'package:gamepads/gamepads.dart'; 5 | import 'package:gamepads_platform_interface/gamepads_platform_interface.dart'; 6 | import 'package:gamepads_platform_interface/method_channel_gamepads_platform_interface.dart'; 7 | 8 | void main() { 9 | TestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | final calls = []; 12 | const channel = MethodChannel('xyz.luan/gamepads'); 13 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 14 | .setMockMethodCallHandler( 15 | channel, 16 | (MethodCall methodCall) async { 17 | calls.add(methodCall); 18 | return []; 19 | }, 20 | ); 21 | 22 | void clear() { 23 | calls.clear(); 24 | } 25 | 26 | MethodCall popCall() { 27 | return calls.removeAt(0); 28 | } 29 | 30 | MethodCall popLastCall() { 31 | expect(calls, hasLength(1)); 32 | return popCall(); 33 | } 34 | 35 | final platformInterface = 36 | GamepadsPlatformInterface.instance 37 | as MethodChannelGamepadsPlatformInterface; 38 | 39 | setUp(clear); 40 | 41 | test('invokes listGamepads through platform interface', () async { 42 | expect(await Gamepads.list(), []); 43 | expect(popLastCall().method, 'listGamepads'); 44 | }); 45 | 46 | test('can listen to events through platform interface', () async { 47 | final listener = Gamepads.events.first; 48 | final millis = DateTime.now().millisecondsSinceEpoch; 49 | await platformInterface.platformCallHandler( 50 | MethodCall( 51 | 'onGamepadEvent', 52 | { 53 | 'gamepadId': '1', 54 | 'time': millis, 55 | 'type': 'button', 56 | 'key': 'a', 57 | 'value': 1.0, 58 | }, 59 | ), 60 | ); 61 | final event = await listener; 62 | expect(event.gamepadId, '1'); 63 | expect(event.timestamp, millis); 64 | expect(event.type, KeyType.button); 65 | expect(event.key, 'a'); 66 | expect(event.value, 1.0); 67 | }); 68 | 69 | test( 70 | 'can listen to gamepad-specific events through platform interface', 71 | () async { 72 | final listener = Gamepads.eventsByGamepad('1').first; 73 | final millis = DateTime.now().millisecondsSinceEpoch; 74 | await platformInterface.platformCallHandler( 75 | MethodCall( 76 | 'onGamepadEvent', 77 | { 78 | 'gamepadId': '1', 79 | 'time': millis, 80 | 'type': 'button', 81 | 'key': 'a', 82 | 'value': 1.0, 83 | }, 84 | ), 85 | ); 86 | final event = await listener; 87 | expect(event.gamepadId, '1'); 88 | expect(event.timestamp, millis); 89 | expect(event.type, KeyType.button); 90 | expect(event.key, 'a'); 91 | expect(event.value, 1.0); 92 | }, 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /packages/gamepads_android/android/src/main/kotlin/org/flame_engine/gamepads_android/EventListener.kt: -------------------------------------------------------------------------------- 1 | package org.flame_engine.gamepads_android 2 | 3 | import android.view.KeyEvent 4 | import android.view.MotionEvent 5 | 6 | import io.flutter.plugin.common.MethodChannel 7 | import kotlin.math.abs 8 | 9 | data class SupportedAxis( 10 | val axisId: Int, 11 | val invert: Boolean = false, 12 | ) 13 | 14 | class EventListener { 15 | companion object { 16 | private const val EPSILON = 0.001 17 | } 18 | private val lastAxisValue = mutableMapOf() 19 | // Reference: https://developer.android.com/reference/android/view/MotionEvent 20 | private val supportedAxes = listOf( 21 | SupportedAxis(MotionEvent.AXIS_X), 22 | SupportedAxis(MotionEvent.AXIS_Y, invert = true), 23 | SupportedAxis(MotionEvent.AXIS_Z), 24 | SupportedAxis(MotionEvent.AXIS_RZ, invert = true), 25 | SupportedAxis(MotionEvent.AXIS_HAT_X), 26 | SupportedAxis(MotionEvent.AXIS_HAT_Y, invert = true), 27 | SupportedAxis(MotionEvent.AXIS_LTRIGGER), 28 | SupportedAxis(MotionEvent.AXIS_RTRIGGER), 29 | SupportedAxis(MotionEvent.AXIS_BRAKE), 30 | SupportedAxis(MotionEvent.AXIS_GAS), 31 | SupportedAxis(MotionEvent.AXIS_WHEEL), 32 | ) 33 | 34 | fun onKeyEvent(keyEvent: KeyEvent, channel: MethodChannel): Boolean { 35 | val arguments = mapOf( 36 | "gamepadId" to keyEvent.deviceId.toString(), 37 | "time" to keyEvent.eventTime, 38 | "type" to "button", 39 | "key" to KeyEvent.keyCodeToString(keyEvent.keyCode), 40 | "value" to keyEvent.action.toDouble() 41 | ) 42 | channel.invokeMethod("onGamepadEvent", arguments) 43 | return true 44 | } 45 | 46 | fun onMotionEvent(motionEvent: MotionEvent, channel: MethodChannel): Boolean { 47 | var handled = false 48 | supportedAxes.forEach { axis -> 49 | val axisHandled = reportAxis(motionEvent, channel, axis) 50 | if (axisHandled) handled = true 51 | } 52 | 53 | return handled 54 | } 55 | 56 | private fun reportAxis( 57 | motionEvent: MotionEvent, 58 | channel: MethodChannel, 59 | axis: SupportedAxis, 60 | ): Boolean { 61 | val multiplier = if (axis.invert) -1 else 1 62 | val value = motionEvent.getAxisValue(axis.axisId) * multiplier 63 | 64 | // No-op if threshold is not met 65 | val lastValue = lastAxisValue[axis.axisId] 66 | if (lastValue is Float) { 67 | if (abs(value - lastValue) < EPSILON) { 68 | return true 69 | } 70 | } 71 | // Update last value 72 | lastAxisValue[axis.axisId] = value 73 | 74 | val arguments = mapOf( 75 | "gamepadId" to motionEvent.deviceId.toString(), 76 | "time" to motionEvent.eventTime, 77 | "type" to "analog", 78 | "key" to MotionEvent.axisToString(axis.axisId), 79 | "value" to value, 80 | ) 81 | channel.invokeMethod("onGamepadEvent", arguments) 82 | return true 83 | } 84 | } -------------------------------------------------------------------------------- /packages/gamepads/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:gamepads/gamepads.dart'; 5 | 6 | void main() { 7 | runApp(const MyApp()); 8 | } 9 | 10 | class MyApp extends StatelessWidget { 11 | const MyApp({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return MaterialApp( 16 | title: 'Gamepads Example', 17 | theme: ThemeData( 18 | primarySwatch: Colors.blue, 19 | ), 20 | home: const MyHomePage(), 21 | ); 22 | } 23 | } 24 | 25 | class MyHomePage extends StatefulWidget { 26 | const MyHomePage({super.key}); 27 | 28 | @override 29 | State createState() => _MyHomePageState(); 30 | } 31 | 32 | class _MyHomePageState extends State { 33 | StreamSubscription? _subscription; 34 | 35 | List _gamepads = []; 36 | List _lastEvents = []; 37 | bool loading = false; 38 | 39 | Future _getValue() async { 40 | setState(() => loading = true); 41 | final response = await Gamepads.list(); 42 | setState(() { 43 | _gamepads = response; 44 | loading = false; 45 | }); 46 | } 47 | 48 | void _clear() { 49 | setState(() => _lastEvents = []); 50 | } 51 | 52 | @override 53 | void initState() { 54 | super.initState(); 55 | _subscription = Gamepads.events.listen((event) { 56 | setState(() { 57 | final newEvents = [ 58 | event, 59 | ..._lastEvents, 60 | ]; 61 | if (newEvents.length > 3) { 62 | newEvents.removeRange(3, newEvents.length); 63 | } 64 | _lastEvents = newEvents; 65 | }); 66 | }); 67 | } 68 | 69 | @override 70 | void dispose() { 71 | _subscription?.cancel(); 72 | super.dispose(); 73 | } 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return Scaffold( 78 | appBar: AppBar( 79 | title: const Text('Gamepads Example'), 80 | ), 81 | body: Center( 82 | child: Column( 83 | mainAxisAlignment: MainAxisAlignment.center, 84 | children: [ 85 | const Text('Last Events:'), 86 | ..._lastEvents.map((e) => Text(e.toString())), 87 | TextButton( 88 | onPressed: _clear, 89 | child: const Text('clear events'), 90 | ), 91 | const SizedBox(height: 16), 92 | TextButton( 93 | onPressed: _getValue, 94 | child: const Text('listGamepads()'), 95 | ), 96 | const Text('Gamepads:'), 97 | if (loading) 98 | const CircularProgressIndicator() 99 | else ...[ 100 | for (final gamepad in _gamepads) ...[ 101 | Text('${gamepad.id} - ${gamepad.name}'), 102 | Text(' Analog inputs: ${gamepad.state.analogInputs}'), 103 | Text(' Button inputs: ${gamepad.state.buttonInputs}'), 104 | ], 105 | ], 106 | ], 107 | ), 108 | ), 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/gamepads_android/android/src/main/kotlin/org/flame_engine/gamepads_android/GamepadsAndroidPlugin.kt: -------------------------------------------------------------------------------- 1 | package org.flame_engine.gamepads_android 2 | 3 | import androidx.annotation.NonNull 4 | import android.app.Activity 5 | import android.content.Context 6 | import android.hardware.input.InputManager 7 | import android.util.Log 8 | import android.view.InputDevice 9 | import android.view.KeyEvent 10 | import android.view.MotionEvent 11 | 12 | import io.flutter.embedding.engine.plugins.FlutterPlugin 13 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 14 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 15 | import io.flutter.plugin.common.MethodCall 16 | import io.flutter.plugin.common.MethodChannel 17 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 18 | import io.flutter.plugin.common.MethodChannel.Result 19 | import kotlin.concurrent.thread 20 | 21 | class GamepadsAndroidPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { 22 | companion object { 23 | private const val TAG = "GamepadsAndroidPlugin" 24 | } 25 | private lateinit var channel : MethodChannel 26 | private lateinit var devices : DeviceListener 27 | private lateinit var events : EventListener 28 | 29 | private fun listGamepads(): List> { 30 | return devices.getDevices().map { device -> 31 | mapOf( 32 | "id" to device.key.toString(), 33 | "name" to device.value.name 34 | ) 35 | } 36 | } 37 | 38 | // FlutterPlugin 39 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 40 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "xyz.luan/gamepads") 41 | channel.setMethodCallHandler(this) 42 | } 43 | 44 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { 45 | channel.setMethodCallHandler(null) 46 | } 47 | 48 | override fun onMethodCall(call: MethodCall, result: Result) { 49 | if (call.method == "listGamepads") { 50 | result.success(listGamepads()) 51 | } else { 52 | result.notImplemented() 53 | } 54 | } 55 | 56 | // Activity Aware 57 | override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) { 58 | onAttachedToActivityShared(activityPluginBinding.activity) 59 | } 60 | 61 | fun onAttachedToActivityShared(activity: Activity) { 62 | val compatibleActivity = activity as GamepadsCompatibleActivity 63 | devices = DeviceListener { compatibleActivity.isGamepadsInputDevice(it) } 64 | events = EventListener() 65 | compatibleActivity.registerInputDeviceListener(devices, handler = null) 66 | compatibleActivity.registerKeyEventHandler { event -> 67 | if (devices.containsKey(event.deviceId)) { 68 | events.onKeyEvent(event, channel) 69 | } else { 70 | false 71 | } 72 | } 73 | compatibleActivity.registerMotionEventHandler { event -> 74 | if (devices.containsKey(event.deviceId)) { 75 | events.onMotionEvent(event, channel) 76 | } else { 77 | false 78 | } 79 | } 80 | } 81 | 82 | override fun onDetachedFromActivity() { 83 | // No-op 84 | } 85 | 86 | override fun onDetachedFromActivityForConfigChanges() { 87 | // No-op 88 | } 89 | 90 | override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) { 91 | onAttachedToActivityShared(activityPluginBinding.activity) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to making participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, sex characteristics, gender identity and expression, 10 | level of experience, education, socio-economic status, nationality, personal 11 | appearance, race, religion, or sexual identity and orientation. 12 | 13 | 14 | ## Our Standards 15 | 16 | Examples of behavior that contributes to creating a positive environment 17 | include: 18 | 19 | - Using welcoming and inclusive language 20 | - Being respectful of differing viewpoints and experiences 21 | - Gracefully accepting constructive criticism 22 | - Focusing on what is best for the community 23 | - Showing empathy towards other community members 24 | 25 | Examples of unacceptable behavior by participants include: 26 | 27 | - The use of sexualized language or imagery and unwelcome sexual attention or 28 | advances 29 | - Trolling, insulting/derogatory comments, and personal or political attacks 30 | - Public or private harassment 31 | - Publishing others' private information, such as a physical or electronic 32 | address, without explicit permission 33 | - Other conduct which could reasonably be considered inappropriate in a 34 | professional setting 35 | 36 | 37 | ## Our Responsibilities 38 | 39 | Project maintainers are responsible for clarifying the standards of acceptable 40 | behavior and are expected to take appropriate and fair corrective action in 41 | response to any instances of unacceptable behavior. 42 | 43 | Project maintainers have the right and responsibility to remove, edit, or 44 | reject comments, commits, code, wiki edits, issues, and other contributions 45 | that are not aligned to this Code of Conduct, or to ban temporarily or 46 | permanently any contributor for other behaviors that they deem inappropriate, 47 | threatening, offensive, or harmful. 48 | 49 | 50 | ## Scope 51 | 52 | This Code of Conduct applies both within project spaces and in public spaces 53 | when an individual is representing the project or its community. Examples of 54 | representing a project or community include using an official project e-mail 55 | address, posting via an official social media account, or acting as an appointed 56 | representative at an online or offline event. Representation of a project may be 57 | further defined and clarified by project maintainers. 58 | 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported by contacting anyone with the Flame staff role at the [Blue Fire 64 | discord](https://discord.gg/pxrBmy4). All complaints will be reviewed and 65 | investigated and will result in a response that is deemed necessary and 66 | appropriate to the circumstances. The project team is obligated to maintain 67 | confidentiality with regard to the reporter of an incident. 68 | Further details of specific enforcement policies may be posted separately. 69 | 70 | Project maintainers who do not follow or enforce the Code of Conduct in good 71 | faith may face temporary or permanent repercussions as determined by other 72 | members of the project's leadership. 73 | 74 | 75 | ## Attribution 76 | 77 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 78 | available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html] 79 | 80 | [homepage]: https://www.contributor-covenant.org 81 | 82 | For answers to common questions about this code of conduct, see 83 | [https://www.contributor-covenant.org/faq] 84 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/gamepads_windows_plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "gamepads_windows_plugin.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace gamepads_windows { 16 | void GamepadsWindowsPlugin::RegisterWithRegistrar( 17 | flutter::PluginRegistrarWindows* registrar) { 18 | channel = std::make_unique>( 19 | registrar->messenger(), "xyz.luan/gamepads", 20 | &flutter::StandardMethodCodec::GetInstance()); 21 | 22 | auto plugin = std::make_unique(registrar); 23 | 24 | channel->SetMethodCallHandler( 25 | [plugin_pointer = plugin.get()](const auto& call, auto result) { 26 | plugin_pointer->HandleMethodCall(call, std::move(result)); 27 | }); 28 | 29 | registrar->AddPlugin(std::move(plugin)); 30 | } 31 | 32 | GamepadsWindowsPlugin::GamepadsWindowsPlugin( 33 | flutter::PluginRegistrarWindows* registrar) 34 | : registrar(registrar) { 35 | gamepads.event_emitter = [&](Gamepad* gamepad, const Event& event) { 36 | this->emit_gamepad_event(gamepad, event); 37 | }; 38 | gamepads.update_gamepads(); 39 | window_proc_id = registrar->RegisterTopLevelWindowProcDelegate( 40 | [this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { 41 | DEV_BROADCAST_DEVICEINTERFACE filter = {}; 42 | filter.dbcc_size = sizeof(filter); 43 | filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; 44 | filter.dbcc_classguid = GUID_DEVINTERFACE_HID; 45 | this->hDevNotify = RegisterDeviceNotification( 46 | hwnd, &filter, DEVICE_NOTIFY_WINDOW_HANDLE); 47 | 48 | return GamepadListenerProc(hwnd, message, wparam, lparam); 49 | }); 50 | } 51 | 52 | GamepadsWindowsPlugin::~GamepadsWindowsPlugin() { 53 | UnregisterDeviceNotification(hDevNotify); 54 | registrar->UnregisterTopLevelWindowProcDelegate(window_proc_id); 55 | } 56 | 57 | void GamepadsWindowsPlugin::HandleMethodCall( 58 | const flutter::MethodCall& method_call, 59 | std::unique_ptr> result) { 60 | if (method_call.method_name().compare("listGamepads") == 0) { 61 | flutter::EncodableList list; 62 | for (auto [device_id, gamepad] : gamepads.gamepads) { 63 | flutter::EncodableMap map; 64 | map[flutter::EncodableValue("id")] = 65 | flutter::EncodableValue(std::to_string(device_id)); 66 | map[flutter::EncodableValue("name")] = 67 | flutter::EncodableValue(gamepad.name); 68 | list.push_back(flutter::EncodableValue(map)); 69 | } 70 | result->Success(flutter::EncodableValue(list)); 71 | } else { 72 | result->NotImplemented(); 73 | } 74 | } 75 | 76 | void GamepadsWindowsPlugin::emit_gamepad_event(Gamepad* gamepad, 77 | const Event& event) { 78 | auto _channel = this->channel.get(); 79 | if (_channel) { 80 | flutter::EncodableMap map; 81 | map[flutter::EncodableValue("gamepadId")] = 82 | flutter::EncodableValue(std::to_string(gamepad->joy_id)); 83 | map[flutter::EncodableValue("time")] = flutter::EncodableValue(event.time); 84 | map[flutter::EncodableValue("type")] = flutter::EncodableValue(event.type); 85 | map[flutter::EncodableValue("key")] = flutter::EncodableValue(event.key); 86 | map[flutter::EncodableValue("value")] = 87 | flutter::EncodableValue(static_cast(event.value)); 88 | _channel->InvokeMethod("onGamepadEvent", 89 | std::make_unique( 90 | flutter::EncodableValue(map))); 91 | } 92 | } 93 | } // namespace gamepads_windows 94 | -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/connection_listener.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "connection_listener.h" 12 | #include "utils.h" 13 | 14 | using namespace connection_listener; 15 | 16 | const std::string _input_dir = "/dev/input/"; 17 | 18 | std::map connectionEventTypeNames = { 19 | {ConnectionEventType::CONNECTED, "CONNECTED"}, 20 | {ConnectionEventType::DISCONNECTED, "DISCONNECTED"}, 21 | }; 22 | 23 | std::optional _parseEventType(inotify_event* event) { 24 | uint mask = event->mask; 25 | if ((mask & IN_CREATE) || (mask & IN_ATTRIB)) { 26 | return ConnectionEventType::CONNECTED; 27 | } else if (mask & IN_DELETE) { 28 | return ConnectionEventType::DISCONNECTED; 29 | } else { 30 | return std::nullopt; 31 | } 32 | } 33 | 34 | void _list_existing( 35 | const std::function& event_consumer) { 36 | DIR* dir = opendir(_input_dir.c_str()); 37 | 38 | if (!dir) { 39 | std::cerr << "Failed to open directory: " << _input_dir << std::endl; 40 | throw std::runtime_error("Error reading existing connections"); 41 | } 42 | 43 | struct dirent* entry; 44 | std::vector devices; 45 | while ((entry = readdir(dir)) != nullptr) { 46 | if (entry->d_type != DT_CHR) { 47 | continue; 48 | } 49 | if (!starts_with(entry->d_name, "js")) { 50 | continue; 51 | } 52 | std::string device = _input_dir + entry->d_name; 53 | devices.push_back(device); 54 | } 55 | 56 | closedir(dir); 57 | 58 | for (std::string& device : devices) { 59 | ConnectionEvent connectionEvent = {ConnectionEventType::CONNECTED, device}; 60 | event_consumer(connectionEvent); 61 | } 62 | } 63 | 64 | void _wait_for_connections( 65 | int inotify, 66 | const std::function& event_consumer) { 67 | char buffer[4096] __attribute__((aligned(__alignof__(struct inotify_event)))); 68 | ssize_t len = read(inotify, buffer, sizeof(buffer)); 69 | if (len < 0) { 70 | std::cerr << "Error reading inotify events" << std::endl; 71 | throw std::runtime_error("Error reading inotify events"); 72 | } 73 | 74 | char* ptr = buffer; 75 | while (ptr < buffer + len) { 76 | auto* event = reinterpret_cast(ptr); 77 | std::string name = event->name; 78 | if (!starts_with(name, "js")) { 79 | break; 80 | } 81 | 82 | std::string device = _input_dir + name; 83 | std::optional type = _parseEventType(event); 84 | 85 | std::cout << "Connection found: " << connectionEventTypeNames[*type] 86 | << " - " << name << std::endl; 87 | ConnectionEvent connection_event = {*type, device}; 88 | event_consumer(connection_event); 89 | 90 | ptr += sizeof(struct inotify_event) + event->len; 91 | } 92 | } 93 | 94 | namespace connection_listener { 95 | void listen(const bool* keep_reading, 96 | const std::function& event_consumer) { 97 | std::cout << "Reading initial gamepads..." << std::endl; 98 | _list_existing(event_consumer); 99 | 100 | int inotify = inotify_init(); 101 | if (inotify == -1) { 102 | std::cerr << "Error initializing inotify" << std::endl; 103 | throw std::runtime_error("Error initializing inotify"); 104 | } 105 | int watcher = inotify_add_watch(inotify, _input_dir.c_str(), 106 | IN_CREATE | IN_DELETE | IN_ATTRIB); 107 | if (watcher == -1) { 108 | close(inotify); 109 | std::cerr << "Error adding watch for " << _input_dir << std::endl; 110 | throw std::runtime_error("Error adding inotify watch"); 111 | } 112 | 113 | std::cout << "Listening for gamepads..." << std::endl; 114 | while (*keep_reading) { 115 | _wait_for_connections(inotify, event_consumer); 116 | } 117 | std::cout << "Stopped listening for gamepads." << std::endl; 118 | 119 | // Remove the inotify watch and close the file descriptor 120 | inotify_rm_watch(inotify, watcher); 121 | close(inotify); 122 | } 123 | } // namespace connection_listener -------------------------------------------------------------------------------- /packages/gamepads_web/lib/gamepads_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:js_interop'; 3 | 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 6 | import 'package:gamepads_platform_interface/api/gamepad_controller.dart'; 7 | import 'package:gamepads_platform_interface/api/gamepad_event.dart'; 8 | import 'package:gamepads_platform_interface/gamepads_platform_interface.dart'; 9 | import 'package:gamepads_web/src/gamepad_detector.dart' 10 | show getGamepadList, getGamepads; 11 | import 'package:web/web.dart' as web; 12 | 13 | class _GamepadState { 14 | _GamepadState(int amountOfKeys) 15 | : keyStates = List.filled(amountOfKeys, null), 16 | axesStates = List.filled(4, 0); 17 | 18 | final List keyStates; 19 | final List axesStates; 20 | } 21 | 22 | /// A web implementation of the GamepadsWebPlatform of the GamepadsWeb plugin. 23 | class GamepadsWeb extends GamepadsPlatformInterface { 24 | int _gamepadCount = 0; 25 | Timer? _gamepadPollingTimer; 26 | 27 | final Map _lastGamepadStates = {}; 28 | 29 | void updateGamepadsStatus() { 30 | final gamepads = getGamepadList(); 31 | for (final gamepad in gamepads) { 32 | final buttons = gamepad.buttons.toDart; 33 | final axes = gamepad.axes.toDart; 34 | final gamepadId = gamepad.index.toString(); 35 | final _GamepadState lastState; 36 | if (_lastGamepadStates.containsKey(gamepadId) && 37 | _lastGamepadStates[gamepadId]?.keyStates.length == buttons.length) { 38 | lastState = _lastGamepadStates[gamepadId]!; 39 | } else { 40 | _lastGamepadStates[gamepadId] = _GamepadState(buttons.length); 41 | lastState = _lastGamepadStates[gamepadId]!; 42 | } 43 | for (var i = 0; i < buttons.length; i++) { 44 | if (lastState.keyStates[i] != buttons[i].value) { 45 | lastState.keyStates[i] = buttons[i].value; 46 | emitGamepadEvent( 47 | GamepadEvent( 48 | gamepadId: gamepadId, 49 | timestamp: DateTime.now().millisecondsSinceEpoch, 50 | type: KeyType.button, 51 | key: 'button $i', 52 | value: buttons[i].value, 53 | ), 54 | ); 55 | } 56 | } 57 | for (var i = 0; i < lastState.axesStates.length; i++) { 58 | if ((lastState.axesStates[i] - axes[i].toDartDouble).abs() > 0.03) { 59 | lastState.axesStates[i] = axes[i].toDartDouble; 60 | emitGamepadEvent( 61 | GamepadEvent( 62 | gamepadId: gamepadId, 63 | timestamp: DateTime.now().millisecondsSinceEpoch, 64 | type: KeyType.analog, 65 | key: 'analog $i', 66 | value: axes[i].toDartDouble, 67 | ), 68 | ); 69 | } 70 | } 71 | } 72 | } 73 | 74 | GamepadsWeb() { 75 | web.window.addEventListener( 76 | 'gamepadconnected', 77 | (web.Event event) { 78 | _gamepadCount++; 79 | if (_gamepadCount == 1) { 80 | // The game pad state for web is not event driven. We need to 81 | // query the game pad state by ourself. 82 | // By default we set the query interval is 8 ms. 83 | _gamepadPollingTimer = Timer.periodic( 84 | const Duration(milliseconds: 8), 85 | (timer) { 86 | updateGamepadsStatus(); 87 | }, 88 | ); 89 | } 90 | }.toJS, 91 | ); 92 | 93 | web.window.addEventListener( 94 | 'gamepaddisconnected', 95 | (web.Event event) { 96 | _gamepadCount--; 97 | if (_gamepadCount == 0) { 98 | _gamepadPollingTimer?.cancel(); 99 | } 100 | }.toJS, 101 | ); 102 | } 103 | 104 | static void registerWith(Registrar registrar) { 105 | GamepadsPlatformInterface.instance = GamepadsWeb(); 106 | } 107 | 108 | List? controllers; 109 | 110 | @override 111 | Future> listGamepads() async { 112 | controllers = getGamepads(this); 113 | return controllers!; 114 | } 115 | 116 | void emitGamepadEvent(GamepadEvent event) { 117 | _gamepadEventsStreamController.add(event); 118 | } 119 | 120 | final StreamController _gamepadEventsStreamController = 121 | StreamController.broadcast(); 122 | 123 | @override 124 | Stream get gamepadEventsStream => 125 | _gamepadEventsStreamController.stream; 126 | 127 | @mustCallSuper 128 | Future dispose() async { 129 | _gamepadEventsStreamController.close(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/gamepads_ios/ios/Classes/GamepadsIosPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import GameController 4 | 5 | public class GamepadsIosPlugin: NSObject, FlutterPlugin { 6 | private var channel: FlutterMethodChannel! 7 | private var controllerIds = [GCController: Int]() 8 | private var nextControllerId = 1 9 | 10 | public static func register(with registrar: FlutterPluginRegistrar) { 11 | let instance = GamepadsIosPlugin() 12 | instance.channel = FlutterMethodChannel(name: "xyz.luan/gamepads", binaryMessenger: registrar.messenger()) 13 | registrar.addMethodCallDelegate(instance, channel: instance.channel) 14 | 15 | NotificationCenter.default.addObserver( 16 | instance, 17 | selector: #selector(instance.controllerConnected), 18 | name: .GCControllerDidConnect, 19 | object: nil 20 | ) 21 | 22 | NotificationCenter.default.addObserver( 23 | instance, 24 | selector: #selector(instance.controllerDisconnected), 25 | name: .GCControllerDidDisconnect, 26 | object: nil 27 | ) 28 | 29 | for controller in GCController.controllers() { 30 | instance.setupController(controller) 31 | } 32 | } 33 | 34 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 35 | if call.method == "listGamepads" { 36 | let gamepads = controllerIds.compactMap { (controller, id) -> [String: Any]? in 37 | guard let vendorName = controller.vendorName else { return nil } 38 | return [ 39 | "id": String(id), 40 | "name": vendorName 41 | ] 42 | } 43 | result(gamepads) 44 | } else { 45 | result(FlutterMethodNotImplemented) 46 | } 47 | } 48 | 49 | @objc private func controllerConnected(notification: Notification) { 50 | if let controller = notification.object as? GCController { 51 | setupController(controller) 52 | } 53 | } 54 | 55 | @objc private func controllerDisconnected(notification: Notification) { 56 | if let controller = notification.object as? GCController { 57 | controllerIds.removeValue(forKey: controller) 58 | // Optional: send disconnection event 59 | } 60 | } 61 | 62 | private func setupController(_ controller: GCController) { 63 | if controllerIds[controller] == nil { 64 | controllerIds[controller] = nextControllerId 65 | nextControllerId += 1 66 | } 67 | 68 | guard let gamepad = controller.extendedGamepad else { return } 69 | let gamepadId = controllerIds[controller]! 70 | 71 | gamepad.dpad.valueChangedHandler = { [weak self] _, xValue, yValue in 72 | self?.sendEvent(gamepadId: gamepadId, key: "dpad - xAxis", value: xValue, isAnalog: true) 73 | self?.sendEvent(gamepadId: gamepadId, key: "dpad - yAxis", value: yValue, isAnalog: true) 74 | } 75 | 76 | gamepad.leftThumbstick.valueChangedHandler = { [weak self] _, xValue, yValue in 77 | self?.sendEvent(gamepadId: gamepadId, key: "leftStick - xAxis", value: xValue, isAnalog: true) 78 | self?.sendEvent(gamepadId: gamepadId, key: "leftStick - yAxis", value: yValue, isAnalog: true) 79 | } 80 | 81 | gamepad.rightThumbstick.valueChangedHandler = { [weak self] _, xValue, yValue in 82 | self?.sendEvent(gamepadId: gamepadId, key: "rightStick - xAxis", value: xValue, isAnalog: true) 83 | self?.sendEvent(gamepadId: gamepadId, key: "rightStick - yAxis", value: yValue, isAnalog: true) 84 | } 85 | 86 | var buttons: [(GCControllerButtonInput?, String)] = [ 87 | (gamepad.buttonA, "buttonA"), 88 | (gamepad.buttonB, "buttonB"), 89 | (gamepad.buttonX, "buttonX"), 90 | (gamepad.buttonY, "buttonY"), 91 | (gamepad.leftShoulder, "leftShoulder"), 92 | (gamepad.rightShoulder, "rightShoulder"), 93 | (gamepad.leftTrigger, "leftTrigger"), 94 | (gamepad.rightTrigger, "rightTrigger"), 95 | (gamepad.leftThumbstickButton, "leftThumbstickButton"), 96 | (gamepad.rightThumbstickButton, "rightThumbstickButton") 97 | ] 98 | 99 | if #available(iOS 14.0, *) { 100 | buttons.append((gamepad.buttonMenu, "buttonMenu")) 101 | buttons.append((gamepad.buttonOptions, "buttonOptions")) 102 | buttons.append((gamepad.buttonHome, "buttonHome")) 103 | } 104 | 105 | for (button, name) in buttons { 106 | button?.valueChangedHandler = { [weak self] _, _, pressed in 107 | self?.sendEvent(gamepadId: gamepadId, key: name, value: pressed ? 1.0 : 0.0, isAnalog: false) 108 | } 109 | } 110 | } 111 | 112 | private func sendEvent(gamepadId: Int, key: String, value: Float, isAnalog: Bool) { 113 | channel.invokeMethod("onGamepadEvent", arguments: [ 114 | "type": isAnalog ? "analog" : "button", 115 | "gamepadId": String(gamepadId), 116 | "key": key, 117 | "value": value, 118 | "time": Int(Date().timeIntervalSince1970 * 1000) 119 | ]) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /packages/gamepads_darwin/macos/Classes/GamepadsDarwinPlugin.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import GameController 3 | import FlutterMacOS 4 | 5 | public class GamepadsDarwinPlugin: NSObject, FlutterPlugin { 6 | let channel: FlutterMethodChannel 7 | let gamepads = GamepadsListener() 8 | 9 | init(channel: FlutterMethodChannel) { 10 | self.channel = channel 11 | super.init() 12 | 13 | self.gamepads.listener = onGamepadEvent 14 | } 15 | 16 | public static func register(with registrar: FlutterPluginRegistrar) { 17 | let channel = FlutterMethodChannel(name: "xyz.luan/gamepads", binaryMessenger: registrar.messenger) 18 | let instance = GamepadsDarwinPlugin(channel: channel) 19 | registrar.addMethodCallDelegate(instance, channel: channel) 20 | } 21 | 22 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 23 | switch call.method { 24 | case "listGamepads": 25 | result(listGamepads()) 26 | default: 27 | result(FlutterMethodNotImplemented) 28 | } 29 | } 30 | 31 | private func onGamepadEvent(gamepadId: Int, gamepad: GCExtendedGamepad, element: GCControllerElement) { 32 | for (key, value) in getValues(element: element) { 33 | let arguments: [String: Any] = [ 34 | "gamepadId": String(gamepadId), 35 | "time": Int(getTimestamp(gamepad: gamepad)), 36 | "type": element.isAnalog ? "analog" : "button", 37 | "key": key, 38 | "value": value, 39 | ] 40 | channel.invokeMethod("onGamepadEvent", arguments: arguments) 41 | } 42 | } 43 | 44 | private func getValues(element: GCControllerElement) -> [(String, Float)] { 45 | if let element = element as? GCControllerButtonInput { 46 | var button: String = "Unknown button" 47 | if #available(macOS 11.0, *) { 48 | if (element.sfSymbolsName != nil) { 49 | button = element.sfSymbolsName! 50 | } 51 | } 52 | 53 | return [(button, element.value)] 54 | } else if let element = element as? GCControllerAxisInput { 55 | var axis: String = "Unknown axis" 56 | if #available(macOS 11.0, *) { 57 | if (element.sfSymbolsName != nil) { 58 | axis = element.sfSymbolsName! 59 | } 60 | } 61 | return [(axis, element.value)] 62 | } else if let element = element as? GCControllerDirectionPad { 63 | var directionPad: String = "Unknown direction pad" 64 | 65 | if #available(macOS 11.0, *) { 66 | if (element.sfSymbolsName != nil) { 67 | directionPad = element.sfSymbolsName! 68 | } 69 | } 70 | return [ 71 | (maybeConcat(directionPad, "xAxis"), element.xAxis.value), 72 | (maybeConcat(directionPad, "yAxis"), element.yAxis.value) 73 | ] 74 | } else { 75 | return [] 76 | } 77 | } 78 | 79 | private func getNameForElement(element: GCControllerElement) -> String? { 80 | if #available(macOS 11.0, *) { 81 | return element.sfSymbolsName 82 | } else { 83 | return nil 84 | } 85 | } 86 | 87 | private func getTimestamp(gamepad: GCExtendedGamepad) -> TimeInterval { 88 | if #available(macOS 11.0, *) { 89 | return gamepad.lastEventTimestamp 90 | } else { 91 | return Date().timeIntervalSince1970 92 | } 93 | } 94 | 95 | private func getName(gamepad: GCExtendedGamepad) -> String { 96 | if #available(macOS 11.0, *) { 97 | let device = gamepad.device 98 | return maybeConcat(device?.vendorName, device?.productCategory) ?? "Unknown device" 99 | } else { 100 | return "Unknown device" 101 | } 102 | } 103 | 104 | private func listGamepads() -> [[String: Any?]] { 105 | return gamepads.gamepads.enumerated().map { (index, gamepad) in 106 | [ "id": String(index), "name": getName(gamepad: gamepad) ] 107 | } 108 | } 109 | 110 | private func maybeConcat(_ string1: String?, _ string2: String) -> String { 111 | return maybeConcat(string1, string2)! 112 | } 113 | 114 | private func maybeConcat(_ strings: String?...) -> String? { 115 | let nonNull = strings.compactMap { $0 } 116 | if (nonNull.isEmpty) { 117 | return nil 118 | } 119 | return nonNull.joined(separator: " - ") 120 | } 121 | } 122 | 123 | extension Optional { 124 | func map(_ closure: (Wrapped) -> T) -> T? { 125 | if let value = self { 126 | return closure(value) 127 | } else { 128 | return nil 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gamepads 2 | 3 |

4 | A Flutter plugin to handle gamepad input across multiple platforms. 5 |

6 | 7 |

8 | 9 | Badge for latest release 13 | 14 | 15 | Badge for build status 19 | 20 | 21 | Badge for Discord server 22 | 23 | 24 | Badge showing that Melos is used 28 | 29 |

30 | 31 | --- 32 | 33 | > **Note**: This plugin is still in beta. All APIs are subject to change. Any feedback is appreciated. 34 | 35 | Gamepads is a Flutter plugin to handle gamepad (or joystick) input across multiple platforms. 36 | 37 | It supports multiple simultaneously connected gamepads, and will automatically detect and listen to 38 | new connections. 39 | 40 | 41 | ## Getting Started 42 | 43 | The `list` method will list all currently connected gamepads: 44 | 45 | ```dart 46 | final gamepads = await Gamepads.list(); 47 | // ... 48 | ``` 49 | 50 | This uses the data class `GamepadController`, which has an `id` and a user-facing `name`. 51 | 52 | And the `events` stream will broadcast input events from all gamepads: 53 | 54 | ```dart 55 | Gamepads.events.listen((event) { 56 | // ... 57 | }); 58 | ``` 59 | 60 | You can also listen to events only for a specific gamepad with `eventsByGamepad`. 61 | 62 | Events are described by the data class `GamepadEvent`: 63 | 64 | ```dart 65 | class GamepadEvent { 66 | /// The id of the gamepad controller that fired the event. 67 | final String gamepadId; 68 | 69 | /// The timestamp in which the event was fired, in milliseconds since epoch. 70 | final int timestamp; 71 | 72 | /// The [KeyType] of the key that was triggered. 73 | final KeyType type; 74 | 75 | /// A platform-dependant identifier for the key that was triggered. 76 | final String key; 77 | 78 | /// The current value of the key. 79 | final double value; 80 | 81 | // ... 82 | } 83 | ``` 84 | 85 | 86 | ## Next Steps 87 | 88 | As mentioned, this is still a WIP library. Not only APIs are expected to change if needed, but we 89 | plan to add more features, like: 90 | 91 | - stream to listen for connecting/disconnecting gamepads 92 | - get current state of a gamepad 93 | 94 | If you are interested in helping, please reach out! 95 | You can use GitHub or our [Discord server](https://discord.gg/pxrBmy4). 96 | 97 | 98 | ## Android Integration 99 | 100 | The Android implementation requires the application's Activity to forward input events (and 101 | input devices) to the plugin. Below is an example of a MainActivity for a clean Flutter project 102 | that has implemented the required boilerplate code. For many projects it will be possible to simply 103 | duplicate this setup. 104 | 105 | ```kotlin 106 | package [YOUR_PACKAGE_NAME] 107 | 108 | import android.hardware.input.InputManager 109 | import android.os.Handler 110 | import android.view.InputDevice 111 | import android.view.KeyEvent 112 | import android.view.MotionEvent 113 | import io.flutter.embedding.android.FlutterActivity 114 | import org.flame_engine.gamepads_android.GamepadsCompatibleActivity 115 | 116 | class MainActivity: FlutterActivity(), GamepadsCompatibleActivity { 117 | var keyListener: ((KeyEvent) -> Boolean)? = null 118 | var motionListener: ((MotionEvent) -> Boolean)? = null 119 | 120 | override fun dispatchGenericMotionEvent(motionEvent: MotionEvent): Boolean { 121 | return motionListener?.invoke(motionEvent) ?: false 122 | } 123 | 124 | override fun dispatchKeyEvent(keyEvent: KeyEvent): Boolean { 125 | if (keyListener?.invoke(keyEvent) == true) { 126 | return true 127 | } 128 | return super.dispatchKeyEvent(keyEvent) 129 | } 130 | 131 | override fun registerInputDeviceListener( 132 | listener: InputManager.InputDeviceListener, handler: Handler?) { 133 | val inputManager = getSystemService(INPUT_SERVICE) as InputManager 134 | inputManager.registerInputDeviceListener(listener, null) 135 | } 136 | 137 | override fun registerKeyEventHandler(handler: (KeyEvent) -> Boolean) { 138 | keyListener = handler 139 | } 140 | 141 | override fun registerMotionEventHandler(handler: (MotionEvent) -> Boolean) { 142 | motionListener = handler 143 | } 144 | } 145 | 146 | ``` 147 | 148 | 149 | ## Support 150 | 151 | The simplest way to show us your support is by giving the project a star! :star: 152 | 153 | If you want, you can also support us monetarily by donating through OpenCollective: 154 | 155 | 156 | Open Collective donate button 161 | 162 | 163 | Through GitHub Sponsors: 164 | 165 | 166 | GitHub Sponsor button 171 | 172 | 173 | Or by becoming a patron on Patreon: 174 | 175 | 176 | Patreon donate button 181 | 182 | -------------------------------------------------------------------------------- /packages/gamepads_windows/windows/gamepad.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #define WIN32_LEAN_AND_MEAN 3 | #include 4 | #include 5 | #include 6 | #include 7 | #pragma comment(lib, "winmm.lib") 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "gamepad.h" 16 | #include "utils.h" 17 | 18 | Gamepads gamepads; 19 | 20 | std::list Gamepads::diff_states(Gamepad* gamepad, 21 | const JOYINFOEX& old, 22 | const JOYINFOEX& current) { 23 | std::time_t now = std::time(nullptr); 24 | int time = static_cast(now); 25 | 26 | std::list events; 27 | if (old.dwXpos != current.dwXpos) { 28 | events.push_back( 29 | {time, "analog", "dwXpos", static_cast(current.dwXpos)}); 30 | } 31 | if (old.dwYpos != current.dwYpos) { 32 | events.push_back( 33 | {time, "analog", "dwYpos", static_cast(current.dwYpos)}); 34 | } 35 | if (old.dwZpos != current.dwZpos) { 36 | events.push_back( 37 | {time, "analog", "dwZpos", static_cast(current.dwZpos)}); 38 | } 39 | if (old.dwRpos != current.dwRpos) { 40 | events.push_back( 41 | {time, "analog", "dwRpos", static_cast(current.dwRpos)}); 42 | } 43 | if (old.dwUpos != current.dwUpos) { 44 | events.push_back( 45 | {time, "analog", "dwUpos", static_cast(current.dwUpos)}); 46 | } 47 | if (old.dwVpos != current.dwVpos) { 48 | events.push_back( 49 | {time, "analog", "dwVpos", static_cast(current.dwVpos)}); 50 | } 51 | if (old.dwPOV != current.dwPOV) { 52 | events.push_back({time, "analog", "pov", static_cast(current.dwPOV)}); 53 | } 54 | if (old.dwButtons != current.dwButtons) { 55 | for (int i = 0; i < gamepad->num_buttons; ++i) { 56 | bool was_pressed = old.dwButtons & (1 << i); 57 | bool is_pressed = current.dwButtons & (1 << i); 58 | if (was_pressed != is_pressed) { 59 | events.push_back( 60 | {time, "button", "button-" + std::to_string(i), is_pressed}); 61 | } 62 | } 63 | } 64 | return events; 65 | } 66 | 67 | bool Gamepads::are_states_different(const JOYINFOEX& a, const JOYINFOEX& b) { 68 | return a.dwXpos != b.dwXpos || a.dwYpos != b.dwYpos || a.dwZpos != b.dwZpos || 69 | a.dwRpos != b.dwRpos || a.dwUpos != b.dwUpos || a.dwVpos != b.dwVpos || 70 | a.dwButtons != b.dwButtons || a.dwPOV != b.dwPOV; 71 | } 72 | 73 | void Gamepads::read_gamepad(Gamepad* gamepad) { 74 | JOYINFOEX state; 75 | state.dwSize = sizeof(JOYINFOEX); 76 | state.dwFlags = JOY_RETURNALL; 77 | 78 | int joy_id = gamepad->joy_id; 79 | 80 | std::cout << "Listening to gamepad " << joy_id << std::endl; 81 | 82 | while (gamepad->alive) { 83 | JOYINFOEX previous_state = state; 84 | MMRESULT result = joyGetPosEx(joy_id, &state); 85 | if (result == JOYERR_NOERROR) { 86 | if (are_states_different(previous_state, state)) { 87 | std::list events = diff_states(gamepad, previous_state, state); 88 | for (auto joy_event : events) { 89 | if (event_emitter.has_value()) { 90 | (*event_emitter)(gamepad, joy_event); 91 | } 92 | } 93 | } 94 | } else { 95 | std::cout << "Fail to listen to gamepad " << joy_id << std::endl; 96 | gamepad->alive = false; 97 | gamepads.erase(joy_id); 98 | } 99 | } 100 | } 101 | 102 | void Gamepads::connect_gamepad(UINT joy_id, std::string name, int num_buttons) { 103 | gamepads[joy_id] = {joy_id, name, num_buttons, true}; 104 | std::thread read_thread( 105 | [this, joy_id]() { read_gamepad(&gamepads[joy_id]); }); 106 | read_thread.detach(); 107 | } 108 | 109 | void Gamepads::update_gamepads() { 110 | std::cout << "Updating gamepads..." << std::endl; 111 | UINT max_joysticks = joyGetNumDevs(); 112 | JOYCAPSW joy_caps; 113 | for (UINT joy_id = 0; joy_id < max_joysticks; ++joy_id) { 114 | MMRESULT result = joyGetDevCapsW(joy_id, &joy_caps, sizeof(JOYCAPSW)); 115 | if (result == JOYERR_NOERROR) { 116 | std::string name = to_string(joy_caps.szPname); 117 | int num_buttons = static_cast(joy_caps.wNumButtons); 118 | std::optional gamepad = gamepads[joy_id]; 119 | if (gamepad) { 120 | if (gamepad->name != name) { 121 | std::cout << "Updated gamepad " << joy_id << std::endl; 122 | gamepad->alive = false; 123 | gamepads.erase(joy_id); 124 | 125 | connect_gamepad(joy_id, name, num_buttons); 126 | } 127 | } else { 128 | std::cout << "New gamepad connected " << joy_id << std::endl; 129 | connect_gamepad(joy_id, name, num_buttons); 130 | } 131 | } 132 | } 133 | } 134 | 135 | std::set connected_devices; 136 | 137 | std::optional CALLBACK GamepadListenerProc(HWND hwnd, 138 | UINT uMsg, 139 | WPARAM wParam, 140 | LPARAM lParam) { 141 | switch (uMsg) { 142 | case WM_DEVICECHANGE: { 143 | if (lParam != NULL) { 144 | PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam; 145 | if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { 146 | PDEV_BROADCAST_DEVICEINTERFACE pDevInterface = 147 | (PDEV_BROADCAST_DEVICEINTERFACE)pHdr; 148 | if (IsEqualGUID(pDevInterface->dbcc_classguid, 149 | GUID_DEVINTERFACE_HID)) { 150 | std::wstring device_path = pDevInterface->dbcc_name; 151 | bool is_connected = 152 | connected_devices.find(device_path) != connected_devices.end(); 153 | if (!is_connected && wParam == DBT_DEVICEARRIVAL) { 154 | connected_devices.insert(device_path); 155 | gamepads.update_gamepads(); 156 | } else if (is_connected && wParam == DBT_DEVICEREMOVECOMPLETE) { 157 | connected_devices.erase(device_path); 158 | gamepads.update_gamepads(); 159 | } 160 | } 161 | } 162 | } 163 | return 0; 164 | } 165 | case WM_DESTROY: { 166 | PostQuitMessage(0); 167 | return 0; 168 | } 169 | } 170 | return std::nullopt; 171 | } 172 | -------------------------------------------------------------------------------- /packages/gamepads_linux/linux/gamepads_linux_plugin.cc: -------------------------------------------------------------------------------- 1 | #include "include/gamepads_linux/gamepads_linux_plugin.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "connection_listener.h" 12 | #include "gamepad.h" 13 | 14 | #define GAMEPADS_LINUX_PLUGIN(obj) \ 15 | (G_TYPE_CHECK_INSTANCE_CAST((obj), gamepads_linux_plugin_get_type(), \ 16 | GamepadsLinuxPlugin)) 17 | 18 | struct _GamepadsLinuxPlugin { 19 | GObject parent_instance; 20 | }; 21 | 22 | G_DEFINE_TYPE(GamepadsLinuxPlugin, gamepads_linux_plugin, g_object_get_type()) 23 | 24 | static FlMethodChannel* channel; 25 | 26 | bool keep_reading_events = false; 27 | std::map gamepads = {}; 28 | 29 | static std::string parse_event_type(js_event event) { 30 | switch (event.type & ~JS_EVENT_INIT) { 31 | case JS_EVENT_BUTTON: { 32 | return "button"; 33 | } 34 | case JS_EVENT_AXIS: { 35 | return "analog"; 36 | } 37 | default: { 38 | std::cerr << "Unknown event type " << event.type << std::endl; 39 | throw std::invalid_argument("Unknown event type"); 40 | } 41 | } 42 | } 43 | 44 | static void emit_gamepad_event(gamepad::GamepadInfo* gamepad, 45 | const js_event& event) { 46 | if (channel) { 47 | g_autoptr(FlValue) map = fl_value_new_map(); 48 | fl_value_set_string(map, "gamepadId", 49 | fl_value_new_string(gamepad->device_id.c_str())); 50 | fl_value_set_string(map, "time", fl_value_new_int(event.time)); 51 | fl_value_set_string(map, "type", 52 | fl_value_new_string(parse_event_type(event).c_str())); 53 | fl_value_set_string( 54 | map, "key", fl_value_new_string(std::to_string(event.number).c_str())); 55 | fl_value_set_string(map, "value", fl_value_new_float(event.value)); 56 | fl_method_channel_invoke_method(channel, "onGamepadEvent", map, nullptr, 57 | nullptr, nullptr); 58 | } 59 | } 60 | 61 | static void respond_not_found(FlMethodCall* method_call) { 62 | g_autoptr(FlMethodResponse) response = 63 | FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); 64 | fl_method_call_respond(method_call, response, nullptr); 65 | } 66 | 67 | static void respond(FlMethodCall* method_call, FlValue* value) { 68 | g_autoptr(FlMethodResponse) response = 69 | FL_METHOD_RESPONSE(fl_method_success_response_new(value)); 70 | fl_method_call_respond(method_call, response, nullptr); 71 | } 72 | 73 | static void gamepads_linux_plugin_handle_method_call( 74 | GamepadsLinuxPlugin* self, 75 | FlMethodCall* method_call) { 76 | const gchar* method = fl_method_call_get_name(method_call); 77 | 78 | if (strcmp(method, "listGamepads") == 0) { 79 | g_autoptr(FlValue) list = fl_value_new_list(); 80 | for (auto [device_id, gamepad] : gamepads) { 81 | g_autoptr(FlValue) map = fl_value_new_map(); 82 | fl_value_set(map, fl_value_new_string("id"), 83 | fl_value_new_string(device_id.c_str())); 84 | fl_value_set(map, fl_value_new_string("name"), 85 | fl_value_new_string(gamepad.name.c_str())); 86 | fl_value_append(list, map); 87 | } 88 | respond(method_call, list); 89 | } else { 90 | respond_not_found(method_call); 91 | } 92 | } 93 | 94 | static void method_call_cb([[maybe_unused]] FlMethodChannel* flutter_channel, 95 | FlMethodCall* method_call, 96 | gpointer user_data) { 97 | GamepadsLinuxPlugin* plugin = GAMEPADS_LINUX_PLUGIN(user_data); 98 | gamepads_linux_plugin_handle_method_call(plugin, method_call); 99 | } 100 | 101 | void gamepads_linux_plugin_register_with_registrar( 102 | FlPluginRegistrar* registrar) { 103 | GamepadsLinuxPlugin* plugin = GAMEPADS_LINUX_PLUGIN( 104 | g_object_new(gamepads_linux_plugin_get_type(), nullptr)); 105 | 106 | g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); 107 | channel = fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), 108 | "xyz.luan/gamepads", FL_METHOD_CODEC(codec)); 109 | 110 | fl_method_channel_set_method_call_handler( 111 | channel, method_call_cb, g_object_ref(plugin), g_object_unref); 112 | 113 | g_object_unref(plugin); 114 | } 115 | 116 | void process_connection_event(gamepad::GamepadInfo* gamepad) { 117 | gamepad::listen(gamepad, [gamepad](const js_event& value) { 118 | emit_gamepad_event(gamepad, value); 119 | }); 120 | } 121 | 122 | void event_loop_start() { 123 | connection_listener::listen( 124 | &keep_reading_events, 125 | [](const connection_listener::ConnectionEvent& event) { 126 | std::string key = event.device_id; 127 | std::optional existingGamepad = gamepads[key]; 128 | if (event.type == connection_listener::ConnectionEventType::CONNECTED) { 129 | if (existingGamepad && existingGamepad->alive) { 130 | std::cout << "Existing gamepad found; skipping" << std::endl; 131 | return; 132 | } 133 | 134 | std::optional info = 135 | gamepad::get_gamepad_info(key); 136 | if (!info) { 137 | std::cerr << "Unable to open joystick for reading " << key 138 | << std::endl; 139 | return; 140 | } 141 | 142 | std::cout << "Gamepad connected " << key << " - " << info->name 143 | << std::endl; 144 | gamepads[key] = *info; 145 | 146 | std::thread input_thread(process_connection_event, &gamepads[key]); 147 | input_thread.detach(); 148 | } else { 149 | std::cout << "Gamepad disconnected " << key << std::endl; 150 | if (existingGamepad) { 151 | gamepads[key].alive = false; 152 | gamepads.erase(key); 153 | } 154 | } 155 | }); 156 | } 157 | 158 | static void gamepads_linux_plugin_dispose(GObject* object) { 159 | keep_reading_events = false; 160 | G_OBJECT_CLASS(gamepads_linux_plugin_parent_class)->dispose(object); 161 | } 162 | 163 | static void gamepads_linux_plugin_class_init(GamepadsLinuxPluginClass* klass) { 164 | G_OBJECT_CLASS(klass)->dispose = gamepads_linux_plugin_dispose; 165 | } 166 | 167 | static void gamepads_linux_plugin_init(GamepadsLinuxPlugin* self) { 168 | keep_reading_events = true; 169 | 170 | std::thread event_loop_thread(event_loop_start); 171 | event_loop_thread.detach(); 172 | } 173 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # Default state for all rules 2 | default: true 3 | 4 | # MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time 5 | MD001: true 6 | 7 | # MD002/first-heading-h1/first-header-h1 - First heading should be a top-level heading 8 | MD002: false 9 | 10 | # MD003/heading-style/header-style - Heading style 11 | MD003: 12 | # Heading style 13 | style: "atx" 14 | 15 | # MD004/ul-style - Unordered list style 16 | MD004: 17 | # List style 18 | style: "dash" 19 | 20 | # MD005/list-indent - Inconsistent indentation for list items at the same level 21 | MD005: true 22 | 23 | # MD006/ul-start-left - Consider starting bulleted lists at the beginning of the line 24 | MD006: true 25 | 26 | # MD007/ul-indent - Unordered list indentation 27 | MD007: 28 | # Spaces for indent 29 | indent: 2 30 | # Whether to indent the first level of the list 31 | start_indented: false 32 | # Spaces for first level indent (when start_indented is set) 33 | start_indent: 2 34 | 35 | # MD009/no-trailing-spaces - Trailing spaces 36 | MD009: 37 | # Spaces for line break 38 | br_spaces: 2 39 | # Allow spaces for empty lines in list items 40 | list_item_empty_lines: false 41 | # Include unnecessary breaks 42 | strict: false 43 | 44 | # MD010/no-hard-tabs - Hard tabs 45 | MD010: 46 | # Include code blocks 47 | code_blocks: true 48 | # Fenced code languages to ignore 49 | ignore_code_languages: [] 50 | # Number of spaces for each hard tab 51 | spaces_per_tab: 1 52 | 53 | # MD011/no-reversed-links - Reversed link syntax 54 | MD011: true 55 | 56 | # MD012/no-multiple-blanks - Multiple consecutive blank lines 57 | MD012: 58 | # Consecutive blank lines 59 | maximum: 2 60 | 61 | # MD013/line-length - Line length 62 | MD013: 63 | # Number of characters 64 | line_length: 100 65 | # Number of characters for headings 66 | heading_line_length: 80 67 | # Number of characters for code blocks 68 | code_block_line_length: 80 69 | # Include code blocks 70 | code_blocks: true 71 | # Include tables 72 | tables: true 73 | # Include headings 74 | headings: true 75 | # Include headings 76 | headers: true 77 | # Strict length checking 78 | strict: false 79 | # Stern length checking 80 | stern: false 81 | 82 | # MD014/commands-show-output - Dollar signs used before commands without showing output 83 | MD014: true 84 | 85 | # MD018/no-missing-space-atx - No space after hash on atx style heading 86 | MD018: true 87 | 88 | # MD019/no-multiple-space-atx - Multiple spaces after hash on atx style heading 89 | MD019: true 90 | 91 | # MD020/no-missing-space-closed-atx - No space inside hashes on closed atx style heading 92 | MD020: true 93 | 94 | # MD021/no-multiple-space-closed-atx - Multiple spaces inside hashes on closed atx style heading 95 | MD021: true 96 | 97 | # MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines 98 | MD022: 99 | # Blank lines above heading 100 | lines_above: 2 101 | # Blank lines below heading 102 | lines_below: 1 103 | 104 | # MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line 105 | MD023: true 106 | 107 | # MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content 108 | MD024: 109 | # Only check sibling headings 110 | allow_different_nesting: false 111 | # Only check sibling headings 112 | siblings_only: false 113 | 114 | # MD025/single-title/single-h1 - Multiple top-level headings in the same document 115 | MD025: 116 | # Heading level 117 | level: 1 118 | # RegExp for matching title in front matter 119 | front_matter_title: "^\\s*title\\s*[:=]" 120 | 121 | # MD026/no-trailing-punctuation - Trailing punctuation in heading 122 | MD026: 123 | # Punctuation characters 124 | punctuation: ".,;:!。,;:!" 125 | 126 | # MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol 127 | MD027: true 128 | 129 | # MD028/no-blanks-blockquote - Blank line inside blockquote 130 | MD028: true 131 | 132 | # MD029/ol-prefix - Ordered list item prefix 133 | MD029: 134 | # List style 135 | style: "one_or_ordered" 136 | 137 | # MD030/list-marker-space - Spaces after list markers 138 | MD030: 139 | # Spaces for single-line unordered list items 140 | ul_single: 1 141 | # Spaces for single-line ordered list items 142 | ol_single: 1 143 | # Spaces for multi-line unordered list items 144 | ul_multi: 1 145 | # Spaces for multi-line ordered list items 146 | ol_multi: 1 147 | 148 | # MD031/blanks-around-fences - Fenced code blocks should be surrounded by blank lines 149 | MD031: 150 | # Include list items 151 | list_items: true 152 | 153 | # MD032/blanks-around-lists - Lists should be surrounded by blank lines 154 | MD032: true 155 | 156 | # MD033/no-inline-html - Inline HTML 157 | MD033: 158 | # Allowed elements 159 | allowed_elements: [p, a, img, h2] 160 | 161 | # MD034/no-bare-urls - Bare URL used 162 | MD034: true 163 | 164 | # MD035/hr-style - Horizontal rule style 165 | MD035: 166 | # Horizontal rule style 167 | style: "---" 168 | 169 | # MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading 170 | MD036: 171 | # Punctuation characters 172 | punctuation: ".,;:!?。,;:!?" 173 | 174 | # MD037/no-space-in-emphasis - Spaces inside emphasis markers 175 | MD037: true 176 | 177 | # MD038/no-space-in-code - Spaces inside code span elements 178 | MD038: true 179 | 180 | # MD039/no-space-in-links - Spaces inside link text 181 | MD039: true 182 | 183 | # MD040/fenced-code-language - Fenced code blocks should have a language specified 184 | MD040: true 185 | 186 | # MD041/first-line-heading - First line in a file should be a top-level heading 187 | MD041: false 188 | 189 | # MD042/no-empty-links - No empty links 190 | MD042: true 191 | 192 | # MD044/proper-names - Proper names should have the correct capitalization 193 | MD044: 194 | # List of proper names 195 | names: [] 196 | # Include code blocks 197 | code_blocks: true 198 | # Include HTML elements 199 | html_elements: true 200 | 201 | # MD045/no-alt-text - Images should have alternate text (alt text) 202 | MD045: true 203 | 204 | # MD046/code-block-style - Code block style 205 | MD046: 206 | # Block style 207 | style: "fenced" 208 | 209 | # MD047/single-trailing-newline - Files should end with a single newline character 210 | MD047: true 211 | 212 | # MD048/code-fence-style - Code fence style 213 | MD048: 214 | # Code fence style 215 | style: "backtick" 216 | 217 | # MD049/emphasis-style - Emphasis style should be consistent 218 | MD049: 219 | # Emphasis style should be consistent 220 | style: "asterisk" 221 | 222 | # MD050/strong-style - Strong style should be consistent 223 | MD050: 224 | # Strong style should be consistent 225 | style: "asterisk" 226 | 227 | # MD051/link-fragments - Link fragments should be valid 228 | MD051: true 229 | 230 | # MD052/reference-links-images - Reference links and images should use a label that is defined 231 | MD052: true 232 | 233 | # MD053/link-image-reference-definitions - Link and image reference definitions should be needed 234 | MD053: 235 | # Ignored definitions 236 | ignored_definitions: ["//"] 237 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | **Note:** If these contribution guidelines are not followed your issue or PR might be closed, so 4 | please read these instructions carefully. 5 | 6 | 7 | ## Contribution types 8 | 9 | 10 | ### Bug Reports 11 | 12 | - If you find a bug, please first report it using [Github issues]. 13 | - First check if there is not already an issue for it; duplicated issues will be closed. 14 | 15 | 16 | ### Bug Fix 17 | 18 | - If you'd like to submit a fix for a bug, please read the [How To](#how-to-contribute) for how to 19 | send a Pull Request. 20 | - Indicate on the open issue that you are working on fixing the bug and the issue will be assigned 21 | to you. 22 | - Write `Fixes #xxxx` in your PR text, where xxxx is the issue number (if there is one). 23 | - Include a test that isolates the bug and verifies that it was fixed. 24 | 25 | 26 | ### New Features 27 | 28 | - If you'd like to add a feature to the library that doesn't already exist, feel free to describe 29 | the feature in a new [GitHub issue]. 30 | - You can also join us on [Discord] to discuss some initials thoughts. 31 | - If you'd like to implement the new feature, please wait for feedback from the project maintainers 32 | before spending too much time writing the code. In some cases, enhancements may not align well 33 | with the project future development direction. 34 | - Implement the code for the new feature and please read the [How To](#how-to-contribute). 35 | 36 | 37 | ### Documentation & Miscellaneous 38 | 39 | - If you have suggestions for improvements to the documentation, tutorial or examples (or something 40 | else), we would love to hear about it. 41 | - As always first file a [Github issue]. 42 | - Implement the changes to the documentation, please read the [How To](#how-to-contribute). 43 | 44 | 45 | ## How To Contribute 46 | 47 | 48 | ### Requirements 49 | 50 | For a contribution to be accepted: 51 | 52 | - Follow the [Style Guide] when writing the code; 53 | - Format the code using `dart format .`; 54 | - Lint the code with `melos run analyze`; 55 | - Check that all tests pass: `melos run test`; 56 | - Documentation should always be updated or added (if applicable); 57 | - Examples should always be updated or added (if applicable); 58 | - Tests should always be updated or added (if applicable) -- check the [Test writing guide] for 59 | more details; 60 | - The PR title should start with a [conventional commit] prefix (`feat:`, `fix:` etc). 61 | 62 | If the contribution doesn't meet these criteria, a maintainer will discuss it with you on the issue 63 | or PR. You can still continue to add more commits to the branch you have sent the Pull Request from 64 | and it will be automatically reflected in the PR. 65 | 66 | 67 | ## Open an issue and fork the repository 68 | 69 | - If it is a bigger change or a new feature, first of all 70 | [file a bug or feature report][GitHub issue], so that we can discuss what direction to follow. 71 | - [Fork the project][fork guide] on GitHub. 72 | - Clone the forked repository to your local development machine 73 | (e.g. `git clone git@github.com:/flame.git`). 74 | 75 | 76 | ### Environment Setup 77 | 78 | Flame is setup to run with the most recent `stable` version of Flutter, so make sure your version 79 | matches that: 80 | 81 | ```shell 82 | flutter channel stable 83 | ``` 84 | 85 | Also, Flame uses [Melos] to manage the project and dependencies. 86 | 87 | To install Melos, run the following command from your terminal: 88 | 89 | ```shell 90 | flutter pub global activate melos 91 | ``` 92 | 93 | Next, at the root of your locally cloned repository bootstrap the projects dependencies: 94 | 95 | ```shell 96 | melos bootstrap 97 | ``` 98 | 99 | The bootstrap command locally links all dependencies within the project without having to 100 | provide manual [`dependency_overrides`][pubspec doc]. This allows all 101 | plugins, examples and tests to build from the local clone project. You should only need to run this 102 | command once. 103 | 104 | > You do not need to run `flutter pub get` once bootstrap has been completed. 105 | 106 | 107 | #### CSpell 108 | 109 | If you want to run the spellchecker locally, you will have to install 110 | [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell); 111 | you can do so using npm or yarn: 112 | 113 | ```bash 114 | npm install -g cspell 115 | ``` 116 | 117 | Then you can run it with the following arguments: 118 | 119 | ```bash 120 | cspell --no-progress -c .github/cspell.json "**/*.{md,dart}" 121 | ``` 122 | 123 | 124 | #### Markdown Lint 125 | 126 | If you want to lint the markdown files you have to install 127 | [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) and once that is installed you 128 | can run `melos markdown-check` to check if the markdown follows the rules. Some markdown linting 129 | errors can be automatically fixed with `melos markdown-fix`. 130 | 131 | Note that, sadly, a particularly laborious rule, MD013, [does not provide an auto-fix 132 | option](https://github.com/DavidAnson/markdownlint/issues/535). However, you can use other tools to 133 | circumvent this. For example, the extension [Rewrap](https://stkb.github.io/Rewrap/) for VSCode, when 134 | [configured with](https://stkb.github.io/Rewrap/configuration/) `rewrap.wrappingColumn=100`, will do 135 | the trick for you. 136 | 137 | 138 | ### Performing changes 139 | 140 | - Create a new local branch from `main` (e.g. `git checkout -b my-new-feature`) 141 | - Make your changes (try to split them up with one PR per feature/fix). 142 | - When committing your changes, make sure that each commit message is clear 143 | (e.g. `git commit -m 'Take in an optional Camera as a parameter to FlameGame'`). 144 | - Push your new branch to your own fork into the same remote branch 145 | (e.g. `git push origin my-username.my-new-feature`, replace `origin` if you use another remote.) 146 | 147 | 148 | ### Breaking changes 149 | 150 | When doing breaking changes a deprecation tag should be added when possible and contain a message 151 | that conveys to the user what which version that the deprecated method/field will be removed in and 152 | what method they should use instead to perform the task. The version specified should be at least 153 | two versions after the current one, such that there will be at least one stable release where the 154 | users get to see the deprecation warning and in the version after that (or a later version) the 155 | deprecated entity should be removed. 156 | 157 | Example (if the current version is v1.3.0): 158 | 159 | ```dart 160 | @Deprecated('Will be removed in v1.5.0, use nonDeprecatedFeature() instead') 161 | void deprecatedFeature() {} 162 | ``` 163 | 164 | 165 | ### Open a pull request 166 | 167 | Go to the [pull request page of Flame][PRs] and in the top 168 | of the page it will ask you if you want to open a pull request from your newly created branch. 169 | 170 | The title of the pull request should start with a [conventional commit] type. 171 | 172 | Allowed types are: 173 | 174 | - `fix:` -- patches a bug and is not a new feature; 175 | - `feat:` -- introduces a new feature; 176 | - `docs:` -- updates or adds documentation or examples; 177 | - `test:` -- updates or adds tests; 178 | - `refactor:` -- refactors code but doesn't introduce any changes or additions to the public API; 179 | - `perf:` -- code change that improves performance; 180 | - `build:` -- code change that affects the build system or external dependencies; 181 | - `ci:` -- changes to the CI configuration files and scripts; 182 | - `chore:` -- other changes that don't modify source or test files; 183 | - `revert:` -- reverts a previous commit. 184 | 185 | If you introduce a **breaking change** the conventional commit type MUST end with an exclamation 186 | mark (e.g. `feat!: Remove the position argument from PositionComponent`). 187 | 188 | Examples of PR titles: 189 | 190 | - feat: Component.childrenFactory can be used to set up a global ComponentSet factory 191 | - fix: Avoid infinite loop in `FlameGame` 192 | - docs: Add a `JoystickComponent` example 193 | - docs: Improve the Mandarin README 194 | - test: Add infinity test for `MoveEffect.to` 195 | - refactor: Optimize the structure of the game loop 196 | 197 | 198 | ## Maintainers 199 | 200 | These instructions are for the maintainers of Flame. 201 | 202 | 203 | ### Merging a pull request 204 | 205 | When merging a pull request, make sure that the title of the merge commit has the correct 206 | conventional commit tag and a descriptive title. This is extra important since sometimes the title 207 | of the PR doesn't reflect what GitHub defaults to for the merge commit title (if the title has been 208 | changed during the life time of the PR for example). 209 | 210 | All the default text should be removed from the commit message and the PR description and the 211 | instructions from the "Migration instruction" (if the PR is breaking) should be copied into the 212 | commit message. 213 | 214 | 215 | ### Creating a release 216 | 217 | There are a few things to think about when doing a release: 218 | 219 | - Search through the codebase for `@Deprecated` methods/fields and remove the ones that are marked 220 | for removal in the version that you are intending to release. 221 | - Create a PR containing the changes for removing the deprecated entities. 222 | - Run `melos version -V : -V :` for Melos to generate 223 | `CHANGELOG.md` files. 224 | - Go through the PRs with breaking changes and add migration documentation to the changelog. 225 | There should be migration docs on each PR, if they haven't been copied to the commit message. 226 | - Run `melos publish` to make sure that there aren't any problems with any of the packages and make 227 | sure that all the versions are correct. 228 | - Once you are satisfied with the result of the dry run, run `melos publish --no-dry-run` 229 | - Create a PR containing the updated changelog and `pubspec.yaml` files. 230 | 231 | 232 | [GitHub issue]: https://github.com/flame-engine/flame/issues 233 | [GitHub issues]: https://github.com/flame-engine/flame/issues 234 | [PRs]: https://github.com/flame-engine/flame/pulls 235 | [fork guide]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects 236 | [Discord]: https://discord.com/invite/pxrBmy4 237 | [Melos]: https://github.com/invertase/melos 238 | [pubspec doc]: https://dart.dev/tools/pub/pubspec 239 | [conventional commit]: https://www.conventionalcommits.org 240 | [style guide]: https://docs.flame-engine.org/main/development/style_guide 241 | [test writing guide]: https://docs.flame-engine.org/main/development/testing_guide 242 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## 2025-10-28 7 | 8 | ### Changes 9 | 10 | --- 11 | 12 | Packages with breaking changes: 13 | 14 | - There are no breaking changes in this release. 15 | 16 | Packages with other changes: 17 | 18 | - [`gamepads` - `v0.1.9`](#gamepads---v019) 19 | 20 | --- 21 | 22 | #### `gamepads` - `v0.1.9` 23 | 24 | - **FEAT**: Support for Web ([#48](https://github.com/flame-engine/gamepads/issues/48)). ([eae56125](https://github.com/flame-engine/gamepads/commit/eae56125423acd3beea7387dbae0085a24ea5995)) 25 | 26 | 27 | ## 2025-09-12 28 | 29 | ### Changes 30 | 31 | --- 32 | 33 | Packages with breaking changes: 34 | 35 | - There are no breaking changes in this release. 36 | 37 | Packages with other changes: 38 | 39 | - [`gamepads_android` - `v0.1.6`](#gamepads_android---v016) 40 | - [`gamepads` - `v0.1.8+2`](#gamepads---v0182) 41 | 42 | Packages with dependency updates only: 43 | 44 | > Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. 45 | 46 | - `gamepads` - `v0.1.8+2` 47 | 48 | --- 49 | 50 | #### `gamepads_android` - `v0.1.6` 51 | 52 | - **FIX**: Fix incorrect handling of unhandled MotionEvent axes on android ([#74](https://github.com/flame-engine/gamepads/issues/74)). ([dbd2ced1](https://github.com/flame-engine/gamepads/commit/dbd2ced145a8d009e68696b090a55664d25b2a4a)) 53 | - **FEAT**: Add support for AXIS_WHEEL on Android gamepads ([#75](https://github.com/flame-engine/gamepads/issues/75)). ([f3c03351](https://github.com/flame-engine/gamepads/commit/f3c033513e573e9777c1491aba3be971d2dd4cf5)) 54 | 55 | 56 | ## 2025-08-30 57 | 58 | ### Changes 59 | 60 | --- 61 | 62 | Packages with breaking changes: 63 | 64 | - There are no breaking changes in this release. 65 | 66 | Packages with other changes: 67 | 68 | - [`gamepads_android` - `v0.1.5`](#gamepads_android---v015) 69 | - [`gamepads_ios` - `v0.1.3+1`](#gamepads_ios---v0131) 70 | - [`gamepads_windows` - `v0.1.4+1`](#gamepads_windows---v0141) 71 | - [`gamepads` - `v0.1.8+1`](#gamepads---v0181) 72 | 73 | Packages with dependency updates only: 74 | 75 | > Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. 76 | 77 | - `gamepads` - `v0.1.8+1` 78 | 79 | --- 80 | 81 | #### `gamepads_android` - `v0.1.5` 82 | 83 | - **FIX**: Bluetooth keyboards should not be recognized as gamepads ([#66](https://github.com/flame-engine/gamepads/issues/66)). ([9c657182](https://github.com/flame-engine/gamepads/commit/9c65718227e3638bfc036e32d05cb3e5b88e4448)) 84 | - **FEAT**: Add AXIS_BRAKE and AXIS_GAS as supported axes on Android. ([#50](https://github.com/flame-engine/gamepads/issues/50)). ([adfb8d1f](https://github.com/flame-engine/gamepads/commit/adfb8d1fa2206571d6c59315697d3cf9c951b423)) 85 | 86 | #### `gamepads_ios` - `v0.1.3+1` 87 | 88 | - **FIX**: Adding rightThumbstickButton and leftThumbstickButton keys for iOS ([#71](https://github.com/flame-engine/gamepads/issues/71)). ([244e16cb](https://github.com/flame-engine/gamepads/commit/244e16cbd0b2b1e0c193f8397dcae91c93416bba)) 89 | - **FIX**: Correct dpad axis mapping and add support for start/select/home buttons (iOS) ([#65](https://github.com/flame-engine/gamepads/issues/65)). ([1aef0c28](https://github.com/flame-engine/gamepads/commit/1aef0c2881db84b78f68f23b754912a2625b7902)) 90 | 91 | #### `gamepads_windows` - `v0.1.4+1` 92 | 93 | - **FIX**: Use `std::optional` for return from `GamepadListenerProc` ([#58](https://github.com/flame-engine/gamepads/issues/58)). ([fbf52fd2](https://github.com/flame-engine/gamepads/commit/fbf52fd281cec345b110c8a5c63053cafd4571cd)) 94 | - **FIX**: Window resizing bug ([#56](https://github.com/flame-engine/gamepads/issues/56)). ([ae7c8f3d](https://github.com/flame-engine/gamepads/commit/ae7c8f3d7f670c7cbb3d9c55119736cbf4a8f54a)) 95 | - **FIX**: Update gamepad.cpp includes to fix windows compilation error ([#51](https://github.com/flame-engine/gamepads/issues/51)). ([d6b8ab43](https://github.com/flame-engine/gamepads/commit/d6b8ab4346b9e5f617dde5fcb54457721a54cb73)) 96 | 97 | 98 | ## 2025-07-09 99 | 100 | ### Changes 101 | 102 | --- 103 | 104 | Packages with breaking changes: 105 | 106 | - There are no breaking changes in this release. 107 | 108 | Packages with other changes: 109 | 110 | - [`gamepads` - `v0.1.8`](#gamepads---v018) 111 | - [`gamepads_android` - `v0.1.4`](#gamepads_android---v014) 112 | - [`gamepads_ios` - `v0.1.3`](#gamepads_ios---v013) 113 | 114 | --- 115 | 116 | #### `gamepads` - `v0.1.8` 117 | 118 | - Bumped dependencies. 119 | 120 | #### `gamepads_android` - `v0.1.4` 121 | 122 | - **FIX**: Bluetooth keyboards should not be recognized as gamepads ([#66](https://github.com/flame-engine/gamepads/issues/66)). ([9c657182](https://github.com/flame-engine/gamepads/commit/9c65718227e3638bfc036e32d05cb3e5b88e4448)) 123 | 124 | #### `gamepads_ios` - `v0.1.3` 125 | 126 | - **FIX**: Correct dpad axis mapping and add support for start/select/home buttons (iOS) ([#65](https://github.com/flame-engine/gamepads/issues/65)). ([1aef0c28](https://github.com/flame-engine/gamepads/commit/1aef0c2881db84b78f68f23b754912a2625b7902)) 127 | 128 | 129 | ## 2025-04-25 130 | 131 | ### Changes 132 | 133 | --- 134 | 135 | Packages with breaking changes: 136 | 137 | - There are no breaking changes in this release. 138 | 139 | Packages with other changes: 140 | 141 | - [`gamepads` - `v0.1.7`](#gamepads---v017) 142 | - [`gamepads_windows` - `v0.1.4`](#gamepads_windows---v014) 143 | 144 | --- 145 | 146 | #### `gamepads` - `v0.1.7` 147 | 148 | - Bumped dependencies. 149 | 150 | #### `gamepads_windows` - `v0.1.4` 151 | 152 | - **FIX**: Use `std::optional` for return from `GamepadListenerProc` ([#58](https://github.com/flame-engine/gamepads/issues/58)). ([fbf52fd2](https://github.com/flame-engine/gamepads/commit/fbf52fd281cec345b110c8a5c63053cafd4571cd)) 153 | 154 | 155 | ## 2025-04-25 156 | 157 | ### Changes 158 | 159 | --- 160 | 161 | Packages with breaking changes: 162 | 163 | - There are no breaking changes in this release. 164 | 165 | Packages with other changes: 166 | 167 | - [`gamepads` - `v0.1.6`](#gamepads---v016) 168 | - [`gamepads_windows` - `v0.1.3`](#gamepads_windows---v013) 169 | 170 | --- 171 | 172 | #### `gamepads` - `v0.1.6` 173 | 174 | - Bump dependencies. 175 | 176 | #### `gamepads_windows` - `v0.1.3` 177 | 178 | - **FIX**: Window resizing bug ([#56](https://github.com/flame-engine/gamepads/issues/56)). ([ae7c8f3d](https://github.com/flame-engine/gamepads/commit/ae7c8f3d7f670c7cbb3d9c55119736cbf4a8f54a)) 179 | 180 | 181 | ## 2025-02-11 182 | 183 | ### Changes 184 | 185 | --- 186 | 187 | Packages with breaking changes: 188 | 189 | - There are no breaking changes in this release. 190 | 191 | Packages with other changes: 192 | 193 | - [`gamepads` - `v0.1.5`](#gamepads---v015) 194 | 195 | --- 196 | 197 | #### `gamepads` - `v0.1.5` 198 | 199 | - Bump version to 0.1.5 (due to previous manual versioning) 200 | 201 | 202 | ## 2025-02-10 203 | 204 | ### Changes 205 | 206 | --- 207 | 208 | Packages with breaking changes: 209 | 210 | - There are no breaking changes in this release. 211 | 212 | Packages with other changes: 213 | 214 | - [`gamepads` - `v0.1.4`](#gamepads---v014) 215 | - [`gamepads_windows` - `v0.1.2`](#gamepads_windows---v012) 216 | - [`gamepads_android` - `v0.1.3`](#gamepads_android---v013) 217 | 218 | --- 219 | 220 | #### `gamepads` - `v0.1.4` 221 | 222 | - Bump "gamepads" to `0.1.4`. 223 | 224 | #### `gamepads_windows` - `v0.1.2` 225 | 226 | - **FIX**: Update gamepad.cpp includes to fix windows compilation error ([#51](https://github.com/flame-engine/gamepads/issues/51)). ([d6b8ab43](https://github.com/flame-engine/gamepads/commit/d6b8ab4346b9e5f617dde5fcb54457721a54cb73)) 227 | 228 | #### `gamepads_android` - `v0.1.3` 229 | 230 | - **FEAT**: Add AXIS_BRAKE and AXIS_GAS as supported axes on Android. ([#50](https://github.com/flame-engine/gamepads/issues/50)). ([adfb8d1f](https://github.com/flame-engine/gamepads/commit/adfb8d1fa2206571d6c59315697d3cf9c951b423)) 231 | 232 | 233 | ## 2024-10-22 234 | 235 | ### Changes 236 | 237 | --- 238 | 239 | Packages with breaking changes: 240 | 241 | - There are no breaking changes in this release. 242 | 243 | Packages with other changes: 244 | 245 | - [`gamepads` - `v0.1.4`](#gamepads---v014) 246 | - [`gamepads_platform_interface` - `v0.1.2+1`](#gamepads_platform_interface---v0121) 247 | - [`gamepads_linux` - `v0.1.1+3`](#gamepads_linux---v0113) 248 | - [`gamepads_windows` - `v0.1.1+3`](#gamepads_windows---v0113) 249 | - [`gamepads_darwin` - `v0.1.2+2`](#gamepads_darwin---v0122) 250 | - [`gamepads_ios` - `v0.1.2+2`](#gamepads_ios---v0122) 251 | - [`gamepads_android` - `v0.1.2+2`](#gamepads_android---v0122) 252 | 253 | Packages with dependency updates only: 254 | 255 | > Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. 256 | 257 | - `gamepads_linux` - `v0.1.1+3` 258 | - `gamepads_windows` - `v0.1.1+3` 259 | - `gamepads_darwin` - `v0.1.2+2` 260 | - `gamepads_ios` - `v0.1.2+2` 261 | - `gamepads_android` - `v0.1.2+2` 262 | 263 | --- 264 | 265 | #### `gamepads` - `v0.1.4` 266 | 267 | - fix: Take other values than 1 into consideration for pressed buttons 268 | 269 | #### `gamepads_platform_interface` - `v0.1.2+1` 270 | 271 | - **FIX**: Take other values than 1 into consideration for pressed buttons ([#46](https://github.com/flame-engine/gamepads/issues/46)). ([8c27112d](https://github.com/flame-engine/gamepads/commit/8c27112ddf1f2d0ea8e07bdcdd13c84546a72836)) 272 | 273 | 274 | ## 2024-10-21 275 | 276 | ### Changes 277 | 278 | --- 279 | 280 | Packages with breaking changes: 281 | 282 | - There are no breaking changes in this release. 283 | 284 | Packages with other changes: 285 | 286 | - [`gamepads` - `v0.1.3`](#gamepads---v013) 287 | - [`gamepads_platform_interface` - `v0.1.2`](#gamepads_platform_interface---v012) 288 | - [`gamepads_linux` - `v0.1.1+2`](#gamepads_linux---v0112) 289 | - [`gamepads_windows` - `v0.1.1+2`](#gamepads_windows---v0112) 290 | - [`gamepads_darwin` - `v0.1.2+1`](#gamepads_darwin---v0121) 291 | - [`gamepads_ios` - `v0.1.2+1`](#gamepads_ios---v0121) 292 | - [`gamepads_android` - `v0.1.2+1`](#gamepads_android---v0121) 293 | 294 | Packages with dependency updates only: 295 | 296 | > Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. 297 | 298 | - `gamepads_linux` - `v0.1.1+2` 299 | - `gamepads_windows` - `v0.1.1+2` 300 | - `gamepads_darwin` - `v0.1.2+1` 301 | - `gamepads_ios` - `v0.1.2+1` 302 | - `gamepads_android` - `v0.1.2+1` 303 | 304 | --- 305 | 306 | #### `gamepads` - `v0.1.3` 307 | 308 | - **FEAT**: Added GamepadState that can be updated ([#43](https://github.com/flame-engine/gamepads/issues/43)). ([0c9890e8](https://github.com/flame-engine/gamepads/commit/0c9890e80c423621c52226521612e307d8419308)) 309 | 310 | #### `gamepads_platform_interface` - `v0.1.2` 311 | 312 | - **FEAT**: Added GamepadState that can be updated ([#43](https://github.com/flame-engine/gamepads/issues/43)). ([0c9890e8](https://github.com/flame-engine/gamepads/commit/0c9890e80c423621c52226521612e307d8419308)) 313 | 314 | 315 | ## 2024-07-13 316 | 317 | ### Changes 318 | 319 | --- 320 | 321 | Packages with breaking changes: 322 | 323 | - There are no breaking changes in this release. 324 | 325 | Packages with other changes: 326 | 327 | - [`gamepads` - `v0.1.2`](#gamepads---v012) 328 | - [`gamepads_android` - `v0.1.2`](#gamepads_android---v012) 329 | - [`gamepads_darwin` - `v0.1.2`](#gamepads_darwin---v012) 330 | - [`gamepads_ios` - `v0.1.2`](#gamepads_ios---v012) 331 | - [`gamepads_linux` - `v0.1.1+1`](#gamepads_linux---v0111) 332 | - [`gamepads_windows` - `v0.1.1+1`](#gamepads_windows---v0111) 333 | 334 | --- 335 | 336 | #### `gamepads` - `v0.1.2` 337 | 338 | - **REFACTOR**: Lint Kotlin, C and C++ code ([#6](https://github.com/flame-engine/gamepads/issues/6)). ([6d3e9334](https://github.com/flame-engine/gamepads/commit/6d3e9334072d24525ed7ccf9f8c7fa481c8373fc)) 339 | - **FEAT**: Support for Android ([#35](https://github.com/flame-engine/gamepads/issues/35)). ([6996109e](https://github.com/flame-engine/gamepads/commit/6996109e4452406990191af1b1f10d18461c3bfc)) 340 | - **FEAT**: Support for iOS ([#30](https://github.com/flame-engine/gamepads/issues/30)). ([e8cb9777](https://github.com/flame-engine/gamepads/commit/e8cb9777d42cf35f4b67629a1e6b5f03517edd35)) 341 | 342 | #### `gamepads_android` - `v0.1.2` 343 | 344 | - **FEAT**: Support for Android ([#35](https://github.com/flame-engine/gamepads/issues/35)). ([6996109e](https://github.com/flame-engine/gamepads/commit/6996109e4452406990191af1b1f10d18461c3bfc)) 345 | 346 | #### `gamepads_darwin` - `v0.1.2` 347 | 348 | - **FIX**: Remove extendedGamepad from gamepads array on disconnect ([#39](https://github.com/flame-engine/gamepads/issues/39)). ([b24257d3](https://github.com/flame-engine/gamepads/commit/b24257d3e467385351bf5ba14780eacfa318cd0d)) 349 | - **FIX**: Update GamepadsDarwinPlugin.swift to conditionally reference sfSymbolsName ([#23](https://github.com/flame-engine/gamepads/issues/23)). ([cfe9d339](https://github.com/flame-engine/gamepads/commit/cfe9d339f5db69b67f93179a092cd70466ecd4e1)) 350 | - **FIX**: Fix for old mac os support ([#1](https://github.com/flame-engine/gamepads/issues/1)). ([090c3be8](https://github.com/flame-engine/gamepads/commit/090c3be8313aab791160e53450f163d1104f579c)) 351 | - **FEAT**: Support for iOS ([#30](https://github.com/flame-engine/gamepads/issues/30)). ([e8cb9777](https://github.com/flame-engine/gamepads/commit/e8cb9777d42cf35f4b67629a1e6b5f03517edd35)) 352 | 353 | #### `gamepads_ios` - `v0.1.2` 354 | 355 | - **FIX**: Remove extendedGamepad from gamepads array on disconnect ([#39](https://github.com/flame-engine/gamepads/issues/39)). ([b24257d3](https://github.com/flame-engine/gamepads/commit/b24257d3e467385351bf5ba14780eacfa318cd0d)) 356 | - **FEAT**: Support for Android ([#35](https://github.com/flame-engine/gamepads/issues/35)). ([6996109e](https://github.com/flame-engine/gamepads/commit/6996109e4452406990191af1b1f10d18461c3bfc)) 357 | - **FEAT**: Support for iOS ([#30](https://github.com/flame-engine/gamepads/issues/30)). ([e8cb9777](https://github.com/flame-engine/gamepads/commit/e8cb9777d42cf35f4b67629a1e6b5f03517edd35)) 358 | 359 | #### `gamepads_linux` - `v0.1.1+1` 360 | 361 | - **REFACTOR**: Lint Kotlin, C and C++ code ([#6](https://github.com/flame-engine/gamepads/issues/6)). ([6d3e9334](https://github.com/flame-engine/gamepads/commit/6d3e9334072d24525ed7ccf9f8c7fa481c8373fc)) 362 | 363 | #### `gamepads_windows` - `v0.1.1+1` 364 | 365 | - **REFACTOR**: Lint Kotlin, C and C++ code ([#6](https://github.com/flame-engine/gamepads/issues/6)). ([6d3e9334](https://github.com/flame-engine/gamepads/commit/6d3e9334072d24525ed7ccf9f8c7fa481c8373fc)) 366 | 367 | 368 | ## 2023-04-04 369 | 370 | ### Changes 371 | 372 | --- 373 | 374 | Packages with breaking changes: 375 | 376 | - There are no breaking changes in this release. 377 | 378 | Packages with other changes: 379 | 380 | - [`gamepads` - `v0.1.1`](#gamepads---v011) 381 | - [`gamepads_platform_interface` - `v0.1.1`](#gamepads_platform_interface---v011) 382 | - [`gamepads_linux` - `v0.1.1`](#gamepads_linux---v011) 383 | - [`gamepads_windows` - `v0.1.1`](#gamepads_windows---v011) 384 | - [`gamepads_darwin` - `v0.1.1`](#gamepads_darwin---v011) 385 | 386 | --- 387 | 388 | #### `gamepads` - `v0.1.1` 389 | 390 | - Bump "gamepads" to `0.1.1`. 391 | 392 | #### `gamepads_platform_interface` - `v0.1.1` 393 | 394 | - Bump "gamepads_platform_interface" to `0.1.1`. 395 | 396 | #### `gamepads_linux` - `v0.1.1` 397 | 398 | - Bump "gamepads_linux" to `0.1.1`. 399 | 400 | #### `gamepads_windows` - `v0.1.1` 401 | 402 | - Bump "gamepads_windows" to `0.1.1`. 403 | 404 | #### `gamepads_darwin` - `v0.1.1` 405 | 406 | - Bump "gamepads_darwin" to `0.1.1`. 407 | 408 | 409 | ## 2023-04-04 410 | 411 | ### Changes 412 | 413 | --- 414 | 415 | Packages with breaking changes: 416 | 417 | - There are no breaking changes in this release. 418 | 419 | Packages with other changes: 420 | 421 | - [`gamepads` - `v0.1.0`](#gamepads---v010) 422 | - [`gamepads_platform_interface` - `v0.1.0`](#gamepads_platform_interface---v010) 423 | - [`gamepads_linux` - `v0.1.0`](#gamepads_linux---v010) 424 | - [`gamepads_windows` - `v0.1.0`](#gamepads_windows---v010) 425 | - [`gamepads_darwin` - `v0.1.0`](#gamepads_darwin---v010) 426 | 427 | --- 428 | 429 | #### `gamepads` - `v0.1.0` 430 | 431 | - Bump "gamepads" to `0.1.0`. 432 | 433 | #### `gamepads_platform_interface` - `v0.1.0` 434 | 435 | - Bump "gamepads_platform_interface" to `0.1.0`. 436 | 437 | #### `gamepads_linux` - `v0.1.0` 438 | 439 | #### `gamepads_windows` - `v0.1.0` 440 | 441 | - Bump "gamepads_windows" to `0.1.0`. 442 | 443 | #### `gamepads_darwin` - `v0.1.0` 444 | 445 | - Bump "gamepads_darwin" to `0.1.0`. 446 | 447 | --------------------------------------------------------------------------------