Add joystick plugin (#39)

https://github.com/sony/flutter-elinux-plugins/issues/35
This commit is contained in:
Hidenori Matsubayashi
2021-08-30 15:58:08 +09:00
committed by GitHub
parent bb10db6bdb
commit fc39cb676c
27 changed files with 1320 additions and 1 deletions
+2 -1
View File
@@ -12,6 +12,7 @@ Basically, the plugins for elinux are designed to be API compatible with the the
| [camera_elinux](packages/camera) | [camera](https://github.com/flutter/plugins/tree/master/packages/camera) | | [camera_elinux](packages/camera) | [camera](https://github.com/flutter/plugins/tree/master/packages/camera) |
| [path_provider_elinux](packages/path_provider) | [path_provider](https://github.com/flutter/plugins/tree/master/packages/path_provider) | | [path_provider_elinux](packages/path_provider) | [path_provider](https://github.com/flutter/plugins/tree/master/packages/path_provider) |
| [shared_preferences_elinux](packages/shared_preferences) | [shared_preferences](https://github.com/flutter/plugins/tree/master/packages/shared_preferences) | | [shared_preferences_elinux](packages/shared_preferences) | [shared_preferences](https://github.com/flutter/plugins/tree/master/packages/shared_preferences) |
| [joystick](packages/joystick) | - |
## Getting Started ## Getting Started
@@ -23,6 +24,6 @@ For help getting started with Flutter for eLinux, view our online
| Repo | Purpose | | Repo | Purpose |
| ------------- | ------------- | | ------------- | ------------- |
| [flutter-elinux](https://github.com/sony/flutter-elinux) | Flutter tools for eLinux | | [flutter-elinux](https://github.com/sony/flutter-elinux) | Flutter tools for eLinux |
| [flutter-elinux-plugins](https://github.com/sony/flutter-elinux-plugins) | Flutter plugins for eLinux | | flutter-elinux-plugins | Flutter plugins for eLinux |
| [flutter-embedded-linux](https://github.com/sony/flutter-embedded-linux) | eLinux embedding for Flutter | | [flutter-embedded-linux](https://github.com/sony/flutter-embedded-linux) | eLinux embedding for Flutter |
| [meta-flutter](https://github.com/sony/meta-flutter) | Yocto recipes of eLinux embedding for Flutter | | [meta-flutter](https://github.com/sony/meta-flutter) | Yocto recipes of eLinux embedding for Flutter |
+7
View File
@@ -0,0 +1,7 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/
+2
View File
@@ -0,0 +1,2 @@
## 1.0.0
* First version.
+26
View File
@@ -0,0 +1,26 @@
Copyright (c) 2021 Sony Group Corporation. All rights reserved.
Copyright (c) 2013 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the names of the copyright holders nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+21
View File
@@ -0,0 +1,21 @@
# joystick
The implementation of the joystick plugin for eLinux.
## Usage
### pubspec.yaml
```yaml
dependencies:
joystick:
git:
url: https://github.com/sony/flutter-elinux-plugins.git
path: packages/joystick
ref: main
```
### Source code
Import `joystick` in your Dart code:
```dart
import 'package:joystick/joystick.dart';
```
+1
View File
@@ -0,0 +1 @@
flutter/
+25
View File
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.10)
set(PROJECT_NAME "joystick")
project(${PROJECT_NAME} LANGUAGES CXX)
# This value is used when generating builds using this plugin, so it must
# not be changed
set(PLUGIN_NAME "joystick_plugin")
add_library(${PLUGIN_NAME} SHARED
"joystick_plugin.cc"
"linux_joystick.cc"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
# List of absolute paths to libraries that should be bundled with the plugin
set(joystick_bundled_libraries
""
PARENT_SCOPE
)
@@ -0,0 +1,23 @@
#ifndef FLUTTER_PLUGIN_JOYSTICK_PLUGIN_H_
#define FLUTTER_PLUGIN_JOYSTICK_PLUGIN_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
#else
#define FLUTTER_PLUGIN_EXPORT
#endif
#if defined(__cplusplus)
extern "C" {
#endif
FLUTTER_PLUGIN_EXPORT void JoystickPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_JOYSTICK_PLUGIN_H_
@@ -0,0 +1,41 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/joystick/joystick_plugin.h"
#include <flutter/plugin_registrar.h>
#include <memory>
#include <sstream>
namespace {
class JoystickPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrar *registrar);
JoystickPlugin();
virtual ~JoystickPlugin();
};
// static
void JoystickPlugin::RegisterWithRegistrar(
flutter::PluginRegistrar *registrar) {
auto plugin = std::make_unique<JoystickPlugin>();
registrar->AddPlugin(std::move(plugin));
}
JoystickPlugin::JoystickPlugin() {}
JoystickPlugin::~JoystickPlugin() {}
} // namespace
void JoystickPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
JoystickPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrar>(registrar));
}
@@ -0,0 +1,27 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <linux/joystick.h>
#include <stdio.h>
#include <unistd.h>
extern "C" __attribute__((visibility("default"))) int joystick_open(
const char* device) {
int fd = open(device, O_NONBLOCK);
if (fd < 0) {
fprintf(stderr, "Failed to open %s (%d)\n", device, errno);
}
return fd;
}
extern "C" __attribute__((visibility("default"))) int joystick_read(
int fd, js_event* ev) {
int bytes = read(fd, ev, sizeof(*ev));
if (bytes < 0) {
return -1;
}
return bytes == sizeof(*ev);
}
+46
View File
@@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
+9
View File
@@ -0,0 +1,9 @@
# joystick_example
Demonstrates how to use the joystick plugin for eLinux.
## Getting Started
For help getting started with Flutter for eLinux, view our online
[documentation](https://github.com/sony/flutter-elinux/wiki).
@@ -0,0 +1 @@
flutter/ephemeral/
@@ -0,0 +1,110 @@
cmake_minimum_required(VERSION 3.15)
project(runner LANGUAGES CXX)
set(BINARY_NAME "joystick_example")
cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# Basically we use this include when we got the following error:
# fatal error: 'bits/c++config.h' file not found
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
include_directories(SYSTEM ${FLUTTER_SYSTEM_INCLUDE_DIRECTORIES})
endif()
endif()
# Configure build options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Configure build option to target backend.
if (NOT FLUTTER_TARGET_BACKEND_TYPE)
set(FLUTTER_TARGET_BACKEND_TYPE "wayland" CACHE
STRING "Flutter target backend type" FORCE)
set_property(CACHE FLUTTER_TARGET_BACKEND_TYPE PROPERTY STRINGS
"wayland" "gbm" "eglstream" "x11")
endif()
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_EMBEDDER_LIBRARY}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
@@ -0,0 +1,108 @@
cmake_minimum_required(VERSION 3.15)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_engine.so")
if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm")
set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_gbm.so")
elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream")
set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_eglstream.so")
elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11")
set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_x11.so")
else()
set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_wayland.so")
endif()
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_EMBEDDER_LIBRARY ${FLUTTER_EMBEDDER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/elinux/" PARENT_SCOPE)
set(AOT_LIBRARY "${EPHEMERAL_DIR}/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_plugin_registrar.h"
"flutter_messenger.h"
"flutter_texture_registrar.h"
"flutter_elinux.h"
"flutter_platform_views.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE "${FLUTTER_EMBEDDER_LIBRARY}")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_CORE "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_PLUGIN "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_APP "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
"${FLUTTER_EMBEDDER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)
@@ -0,0 +1,13 @@
//
// Generated file. Do not edit.
//
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
@@ -0,0 +1,16 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
joystick
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/elinux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.15)
project(runner LANGUAGES CXX)
if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm")
add_definitions(-DFLUTTER_TARGET_BACKEND_GBM)
elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream")
add_definitions(-DFLUTTER_TARGET_BACKEND_EGLSTREAM)
elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11")
add_definitions(-DFLUTTER_TARGET_BACKEND_X11)
else()
add_definitions(-DFLUTTER_TARGET_BACKEND_WAYLAND)
endif()
add_executable(${BINARY_NAME}
"flutter_window.cc"
"main.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
apply_standard_settings(${BINARY_NAME})
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
add_dependencies(${BINARY_NAME} flutter_assemble)
@@ -0,0 +1,367 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMMAND_OPTIONS_
#define COMMAND_OPTIONS_
#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
// todo: Supports other types besides int, string.
namespace commandline {
namespace {
constexpr char kOptionStyleNormal[] = "--";
constexpr char kOptionStyleShort[] = "-";
constexpr char kOptionValueForHelpMessage[] = "=<value>";
} // namespace
class Exception : public std::exception {
public:
Exception(const std::string& msg) : msg_(msg) {}
~Exception() throw() {}
const char* what() const throw() { return msg_.c_str(); }
private:
std::string msg_;
};
class CommandOptions {
public:
CommandOptions() = default;
~CommandOptions() = default;
void AddWithoutValue(const std::string& name, const std::string& short_name,
const std::string& description, bool required) {
Add<std::string, ReaderString>(name, short_name, description, "",
ReaderString(), required, false);
}
void AddInt(const std::string& name, const std::string& short_name,
const std::string& description, const int& default_value,
bool required) {
Add<int, ReaderInt>(name, short_name, description, default_value,
ReaderInt(), required, true);
}
void AddString(const std::string& name, const std::string& short_name,
const std::string& description,
const std::string& default_value, bool required) {
Add<std::string, ReaderString>(name, short_name, description, default_value,
ReaderString(), required, true);
}
template <typename T, typename F>
void Add(const std::string& name, const std::string& short_name,
const std::string& description, const T default_value,
F reader = F(), bool required = true, bool required_value = true) {
if (options_.find(name) != options_.end()) {
std::cerr << "Already registered option: " << name << std::endl;
return;
}
if (lut_short_options_.find(short_name) != lut_short_options_.end()) {
std::cerr << short_name << "is already registered" << std::endl;
return;
}
lut_short_options_[short_name] = name;
options_[name] = std::make_unique<OptionValueReader<T, F>>(
name, short_name, description, default_value, reader, required,
required_value);
// register to show help message.
registration_order_options_.push_back(options_[name].get());
}
bool Exist(const std::string& name) {
auto itr = options_.find(name);
return itr != options_.end() && itr->second->HasValue();
}
template <typename T>
const T& GetValue(const std::string& name) {
auto itr = options_.find(name);
if (itr == options_.end()) {
throw Exception("Not found: " + name);
}
auto* option_value = dynamic_cast<const OptionValue<T>*>(itr->second.get());
if (!option_value) {
throw Exception("Type mismatch: " + name);
}
return option_value->GetValue();
}
bool Parse(int argc, const char* const* argv) {
if (argc < 1) {
errors_.push_back("No options");
return false;
}
command_name_ = argv[0];
for (auto i = 1; i < argc; i++) {
const std::string arg(argv[i]);
// normal options: e.g. --bundle=/data/sample/bundle --fullscreen
if (arg.length() > 2 &&
arg.substr(0, 2).compare(kOptionStyleNormal) == 0) {
const size_t option_value_len = arg.find("=") != std::string::npos
? (arg.length() - arg.find("="))
: 0;
const bool has_value = option_value_len != 0;
std::string option_name =
arg.substr(2, arg.length() - 2 - option_value_len);
if (options_.find(option_name) == options_.end()) {
errors_.push_back("Not found option: " + option_name);
continue;
}
if (!has_value && options_[option_name]->IsRequiredValue()) {
errors_.push_back(option_name + " requres an option value");
continue;
}
if (has_value && !options_[option_name]->IsRequiredValue()) {
errors_.push_back(option_name + " doesn't requres an option value");
continue;
}
if (has_value) {
SetOptionValue(option_name, arg.substr(arg.find("=") + 1));
} else {
SetOption(option_name);
}
}
// short options: e.g. -f /foo/file.txt -h 640 -abc
else if (arg.length() > 1 &&
arg.substr(0, 1).compare(kOptionStyleShort) == 0) {
for (size_t j = 1; j < arg.length(); j++) {
const std::string option_name{argv[i][j]};
if (lut_short_options_.find(option_name) ==
lut_short_options_.end()) {
errors_.push_back("Not found short option: " + option_name);
break;
}
if (j == arg.length() - 1 &&
options_[lut_short_options_[option_name]]->IsRequiredValue()) {
if (i == argc - 1) {
errors_.push_back("Invalid format option: " + option_name);
break;
}
SetOptionValue(lut_short_options_[option_name], argv[++i]);
} else {
SetOption(lut_short_options_[option_name]);
}
}
} else {
errors_.push_back("Invalid format option: " + arg);
}
}
for (size_t i = 0; i < registration_order_options_.size(); i++) {
if (registration_order_options_[i]->IsRequired() &&
!registration_order_options_[i]->HasValue()) {
errors_.push_back(
std::string(registration_order_options_[i]->GetName()) +
" option is mandatory.");
}
}
return errors_.size() == 0;
}
std::string GetError() { return errors_.size() > 0 ? errors_[0] : ""; }
std::vector<std::string>& GetErrors() { return errors_; }
std::string ShowHelp() {
std::ostringstream ostream;
ostream << "Usage: " << command_name_ << " ";
for (size_t i = 0; i < registration_order_options_.size(); i++) {
if (registration_order_options_[i]->IsRequired()) {
ostream << registration_order_options_[i]->GetHelpShortMessage() << " ";
}
}
ostream << std::endl;
ostream << "Global options:" << std::endl;
size_t max_name_len = 0;
for (size_t i = 0; i < registration_order_options_.size(); i++) {
max_name_len = std::max(
max_name_len, registration_order_options_[i]->GetName().length());
}
for (size_t i = 0; i < registration_order_options_.size(); i++) {
if (!registration_order_options_[i]->GetShortName().empty()) {
ostream << kOptionStyleShort
<< registration_order_options_[i]->GetShortName() << ", ";
} else {
ostream << std::string(4, ' ');
}
size_t index_adjust = 0;
constexpr int kSpacerNum = 5;
auto need_value = registration_order_options_[i]->IsRequiredValue();
ostream << kOptionStyleNormal
<< registration_order_options_[i]->GetName();
if (need_value) {
ostream << kOptionValueForHelpMessage;
index_adjust += std::string(kOptionValueForHelpMessage).length();
}
ostream << std::string(
max_name_len + kSpacerNum - index_adjust -
registration_order_options_[i]->GetName().length(),
' ');
ostream << registration_order_options_[i]->GetDescription() << std::endl;
}
return ostream.str();
}
private:
struct ReaderInt {
int operator()(const std::string& value) { return std::stoi(value); }
};
struct ReaderString {
std::string operator()(const std::string& value) { return value; }
};
class Option {
public:
Option(const std::string& name, const std::string& short_name,
const std::string& description, bool required, bool required_value)
: name_(name),
short_name_(short_name),
description_(description),
is_required_(required),
is_required_value_(required_value),
value_set_(false){};
virtual ~Option() = default;
const std::string& GetName() const { return name_; };
const std::string& GetShortName() const { return short_name_; };
const std::string& GetDescription() const { return description_; };
const std::string GetHelpShortMessage() const {
std::string message = kOptionStyleNormal + name_;
if (is_required_value_) {
message += kOptionValueForHelpMessage;
}
return message;
}
bool IsRequired() const { return is_required_; };
bool IsRequiredValue() const { return is_required_value_; };
void Set() { value_set_ = true; };
virtual bool SetValue(const std::string& value) = 0;
virtual bool HasValue() const = 0;
protected:
std::string name_;
std::string short_name_;
std::string description_;
bool is_required_;
bool is_required_value_;
bool value_set_;
};
template <typename T>
class OptionValue : public Option {
public:
OptionValue(const std::string& name, const std::string& short_name,
const std::string& description, const T& default_value,
bool required, bool required_value)
: Option(name, short_name, description, required, required_value),
default_value_(default_value),
value_(default_value){};
virtual ~OptionValue() = default;
bool SetValue(const std::string& value) {
value_ = Read(value);
value_set_ = true;
return true;
}
bool HasValue() const { return value_set_; }
const T& GetValue() const { return value_; }
protected:
virtual T Read(const std::string& s) = 0;
T default_value_;
T value_;
};
template <typename T, typename F>
class OptionValueReader : public OptionValue<T> {
public:
OptionValueReader(const std::string& name, const std::string& short_name,
const std::string& description, const T default_value,
F reader, bool required, bool required_value)
: OptionValue<T>(name, short_name, description, default_value, required,
required_value),
reader_(reader) {}
~OptionValueReader() = default;
private:
T Read(const std::string& value) { return reader_(value); }
F reader_;
};
bool SetOption(const std::string& name) {
auto itr = options_.find(name);
if (itr == options_.end()) {
errors_.push_back("Unknown option: " + name);
return false;
}
itr->second->Set();
return true;
}
bool SetOptionValue(const std::string& name, const std::string& value) {
auto itr = options_.find(name);
if (itr == options_.end()) {
errors_.push_back("Unknown option: " + name);
return false;
}
if (!itr->second->SetValue(value)) {
errors_.push_back("Invalid option value: " + name + " = " + value);
return false;
}
return true;
}
std::string command_name_;
std::unordered_map<std::string, std::unique_ptr<Option>> options_;
std::unordered_map<std::string, std::string> lut_short_options_;
std::vector<Option*> registration_order_options_;
std::vector<std::string> errors_;
};
} // namespace commandline
#endif // COMMAND_OPTIONS_
@@ -0,0 +1,103 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_EMBEDDER_OPTIONS_
#define FLUTTER_EMBEDDER_OPTIONS_
#include <flutter/flutter_view_controller.h>
#include <string>
#include "command_options.h"
class FlutterEmbedderOptions {
public:
FlutterEmbedderOptions() {
options_.AddString("bundle", "b", "Path to Flutter app bundle", "./",
false);
options_.AddWithoutValue("no-cursor", "n", "No mouse cursor/pointer",
false);
#if defined(FLUTTER_TARGET_BACKEND_GBM) || \
defined(FLUTTER_TARGET_BACKEND_EGLSTREAM)
// no more options.
#elif defined(FLUTTER_TARGET_BACKEND_X11)
options_.AddWithoutValue("fullscreen", "f", "Always full-screen display",
false);
options_.AddInt("width", "w", "Flutter app window width", 1280, false);
options_.AddInt("height", "h", "Flutter app window height", 720, false);
#else // FLUTTER_TARGET_BACKEND_WAYLAND
options_.AddWithoutValue("onscreen-keyboard", "k",
"Enable on-screen keyboard", false);
options_.AddWithoutValue("window-decoration", "d",
"Enable window decorations", false);
options_.AddWithoutValue("fullscreen", "f", "Always full-screen display",
false);
options_.AddInt("width", "w", "Flutter app window width", 1280, false);
options_.AddInt("height", "h", "Flutter app window height", 720, false);
#endif
}
~FlutterEmbedderOptions() = default;
bool Parse(int argc, char** argv) {
if (!options_.Parse(argc, argv)) {
std::cerr << options_.GetError() << std::endl;
std::cout << options_.ShowHelp();
return false;
}
bundle_path_ = options_.GetValue<std::string>("bundle");
use_mouse_cursor_ = !options_.Exist("no-cursor");
#if defined(FLUTTER_TARGET_BACKEND_GBM) || \
defined(FLUTTER_TARGET_BACKEND_EGLSTREAM)
use_onscreen_keyboard_ = false;
use_window_decoration_ = false;
window_view_mode_ = flutter::FlutterViewController::ViewMode::kFullscreen;
#elif defined(FLUTTER_TARGET_BACKEND_X11)
use_onscreen_keyboard_ = false;
use_window_decoration_ = false;
window_view_mode_ =
options_.Exist("fullscreen")
? flutter::FlutterViewController::ViewMode::kFullscreen
: flutter::FlutterViewController::ViewMode::kNormal;
window_width_ = options_.GetValue<int>("width");
window_height_ = options_.GetValue<int>("height");
#else // FLUTTER_TARGET_BACKEND_WAYLAND
use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard");
use_window_decoration_ = options_.Exist("window-decoration");
window_view_mode_ =
options_.Exist("fullscreen")
? flutter::FlutterViewController::ViewMode::kFullscreen
: flutter::FlutterViewController::ViewMode::kNormal;
window_width_ = options_.GetValue<int>("width");
window_height_ = options_.GetValue<int>("height");
#endif
return true;
}
std::string BundlePath() const { return bundle_path_; }
bool IsUseMouseCursor() const { return use_mouse_cursor_; }
bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; }
bool IsUseWindowDecoraation() const { return use_window_decoration_; }
flutter::FlutterViewController::ViewMode WindowViewMode() const {
return window_view_mode_;
}
int WindowWidth() const { return window_width_; }
int WindowHeight() const { return window_height_; }
private:
commandline::CommandOptions options_;
std::string bundle_path_;
bool use_mouse_cursor_ = true;
bool use_onscreen_keyboard_ = false;
bool use_window_decoration_ = false;
flutter::FlutterViewController::ViewMode window_view_mode_ =
flutter::FlutterViewController::ViewMode::kNormal;
int window_width_ = 1280;
int window_height_ = 720;
};
#endif // FLUTTER_EMBEDDER_OPTIONS_
@@ -0,0 +1,79 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter_window.h"
#include <chrono>
#include <cmath>
#include <iostream>
#include <thread>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(
const flutter::FlutterViewController::ViewProperties view_properties,
const flutter::DartProject project)
: view_properties_(view_properties), project_(project) {}
bool FlutterWindow::OnCreate() {
flutter_view_controller_ = std::make_unique<flutter::FlutterViewController>(
view_properties_, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_view_controller_->engine() ||
!flutter_view_controller_->view()) {
return false;
}
// Register Flutter plugins.
RegisterPlugins(flutter_view_controller_->engine());
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_view_controller_) {
flutter_view_controller_ = nullptr;
}
}
void FlutterWindow::Run() {
// Main loop.
auto next_flutter_event_time =
std::chrono::steady_clock::time_point::clock::now();
while (flutter_view_controller_->view()->DispatchEvent()) {
// Wait until the next event.
{
auto wait_duration =
std::max(std::chrono::nanoseconds(0),
next_flutter_event_time -
std::chrono::steady_clock::time_point::clock::now());
std::this_thread::sleep_for(
std::chrono::duration_cast<std::chrono::milliseconds>(wait_duration));
}
// Processes any pending events in the Flutter engine, and returns the
// number of nanoseconds until the next scheduled event (or max, if none).
auto wait_duration = flutter_view_controller_->engine()->ProcessMessages();
{
auto next_event_time = std::chrono::steady_clock::time_point::max();
if (wait_duration != std::chrono::nanoseconds::max()) {
next_event_time =
std::min(next_event_time,
std::chrono::steady_clock::time_point::clock::now() +
wait_duration);
} else {
// Wait for the next frame if no events.
auto frame_rate = flutter_view_controller_->view()->GetFrameRate();
next_event_time = std::min(
next_event_time,
std::chrono::steady_clock::time_point::clock::now() +
std::chrono::milliseconds(
static_cast<int>(std::trunc(1000000.0 / frame_rate))));
}
next_flutter_event_time =
std::max(next_flutter_event_time, next_event_time);
}
}
}
@@ -0,0 +1,34 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_WINDOW_
#define FLUTTER_WINDOW_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
class FlutterWindow {
public:
explicit FlutterWindow(
const flutter::FlutterViewController::ViewProperties view_properties,
const flutter::DartProject project);
~FlutterWindow() = default;
// Prevent copying.
FlutterWindow(FlutterWindow const&) = delete;
FlutterWindow& operator=(FlutterWindow const&) = delete;
bool OnCreate();
void OnDestroy();
void Run();
private:
flutter::FlutterViewController::ViewProperties view_properties_;
flutter::DartProject project_;
std::unique_ptr<flutter::FlutterViewController> flutter_view_controller_;
};
#endif // FLUTTER_WINDOW_
@@ -0,0 +1,45 @@
// Copyright 2021 Sony Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <iostream>
#include <memory>
#include <string>
#include "flutter_embedder_options.h"
#include "flutter_window.h"
int main(int argc, char** argv) {
FlutterEmbedderOptions options;
if (!options.Parse(argc, argv)) {
return 0;
}
// Creates the Flutter project.
const auto bundle_path = options.BundlePath();
const std::wstring fl_path(bundle_path.begin(), bundle_path.end());
flutter::DartProject project(fl_path);
auto command_line_arguments = std::vector<std::string>();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
flutter::FlutterViewController::ViewProperties view_properties = {};
view_properties.width = options.WindowWidth();
view_properties.height = options.WindowHeight();
view_properties.view_mode = options.WindowViewMode();
view_properties.use_mouse_cursor = options.IsUseMouseCursor();
view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard();
view_properties.use_window_decoration = options.IsUseWindowDecoraation();
// The Flutter instance hosted by this window.
FlutterWindow window(view_properties, project);
if (!window.OnCreate()) {
return 0;
}
window.Run();
window.OnDestroy();
return 0;
}
+72
View File
@@ -0,0 +1,72 @@
import 'dart:async';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'package:joystick/joystick.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _fd = -1;
int _ev_time = 0;
int _ev_value = 0;
int _ev_type = 0;
int _ev_number = 0;
@override
void initState() {
super.initState();
const String device = '/dev/input/js0';
_fd = joystickOpen(device.toNativeUtf8());
if (_fd < 0) {
return;
}
Timer.periodic(
const Duration(milliseconds: 13),
_onPolling,
);
}
void _onPolling(Timer timer) {
final Pointer<JSEvent> pEv = malloc<JSEvent>();
if (joystickRead(_fd, pEv) < 0) {
return;
}
setState(() {
_ev_time = pEv.ref.type;
_ev_value = pEv.ref.value;
_ev_type = pEv.ref.type;
_ev_number = pEv.ref.number;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Joystick example app'),
),
body: Center(
child: Text('Joystick event: \n'
'time = $_ev_time\n'
'value = $_ev_value\n'
'type = $_ev_type\n'
'number = $_ev_number\n'),
),
),
);
}
}
+25
View File
@@ -0,0 +1,25 @@
name: joystick_example
description: Demonstrates how to use the joystick plugin for eLinux.
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
cupertino_icons: ^1.0.2
flutter:
sdk: flutter
joystick:
# When depending on this package from a real application you should use:
# joystick: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
dev_dependencies:
flutter_lints: ^1.0.0
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
+73
View File
@@ -0,0 +1,73 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: public_member_api_docs
import 'dart:ffi';
import 'package:ffi/ffi.dart';
DynamicLibrary _dylib = DynamicLibrary.open('libjoystick_plugin.so');
/// See: <linux/joystick.h>
/// struct js_event {
/// __u32 time; /* event timestamp in milliseconds */
/// __s16 value; /* value */
/// __u8 type; /* event type */
/// __u8 number; /* axis/button number */
/// };
class JSEvent extends Struct {
@Uint32()
external int time;
@Int16()
external int value;
@Uint8()
external int type;
@Uint8()
external int number;
}
/// button pressed/released. See [JS_EVENT_BUTTON] in <linux/joystick.h>
const int JS_EVENT_BUTTON = 0x01;
/// joystick moved. See [JS_EVENT_AXIS] in <linux/joystick.h>
const int JS_EVENT_AXIS = 0x02;
/// initial state of device. See [JS_EVENT_INIT] in <linux/joystick.h>
const int JS_EVENT_INIT = 0x80;
typedef JoystickOpenNative = Int32 Function(Pointer<Utf8>);
typedef JoystickOpen = int Function(Pointer<Utf8>);
/// Opens joystick device.
final JoystickOpen joystickOpen = _dylib
.lookup<NativeFunction<JoystickOpenNative>>('joystick_open')
.asFunction();
typedef JoystickReadNative = Int32 Function(Int32 fd, Pointer<JSEvent>);
typedef JoystickRead = int Function(int fd, Pointer<JSEvent>);
/// Reads joystick input data.
final JoystickRead joystickRead = _dylib
.lookup<NativeFunction<JoystickReadNative>>('joystick_read')
.asFunction();
/// Returns true if no events.
bool joystickInputIsInactive(JSEvent ev) {
return (ev.type & JS_EVENT_INIT) != 0;
}
/// Returns true if the event was caused by a button press.
bool joystickInputIsButton(JSEvent ev) {
return (ev.type & JS_EVENT_BUTTON) != 0;
}
/// Returns true if the event was caused by an axis movement.
bool joystickInputIsAxis(JSEvent ev) {
return (ev.type & JS_EVENT_AXIS) != 0;
}
/// Returns true if the button is pressed.
bool joystickButtonIsPressed(JSEvent ev) {
return (ev.value & 1) != 0;
}
+21
View File
@@ -0,0 +1,21 @@
name: joystick
description: A Flutter plugin for getting information about and controlling the
joystick on eLinux.
version: 1.0.0
homepage: https://github.com/sony/flutter-elinux-plugins
repository: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/joystick
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.20.0"
dependencies:
ffi: ^1.1.2
flutter:
sdk: flutter
flutter:
plugin:
platforms:
elinux:
pluginClass: JoystickPlugin