mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-08 16:01:19 +12:00
Add all the files
This commit is contained in:
parent
e3db07a16a
commit
d60742f52b
1445 changed files with 430238 additions and 0 deletions
98
src/input/CMakeLists.txt
Normal file
98
src/input/CMakeLists.txt
Normal file
|
@ -0,0 +1,98 @@
|
|||
project(CemuInput)
|
||||
|
||||
include_directories(".")
|
||||
|
||||
add_library(CemuInput
|
||||
InputManager.cpp
|
||||
InputManager.h
|
||||
ControllerFactory.cpp
|
||||
ControllerFactory.h
|
||||
api/ControllerState.h
|
||||
api/Controller.cpp
|
||||
api/Controller.h
|
||||
api/ControllerState.cpp
|
||||
api/InputAPI.h
|
||||
api/ControllerProvider.h
|
||||
emulated/ProController.cpp
|
||||
emulated/EmulatedController.h
|
||||
emulated/EmulatedController.cpp
|
||||
emulated/ProController.h
|
||||
emulated/WPADController.cpp
|
||||
emulated/WPADController.h
|
||||
emulated/WiimoteController.h
|
||||
emulated/VPADController.cpp
|
||||
emulated/WiimoteController.cpp
|
||||
emulated/VPADController.h
|
||||
emulated/ClassicController.cpp
|
||||
emulated/ClassicController.h
|
||||
)
|
||||
|
||||
set_property(TARGET CemuInput PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
|
||||
# SDL
|
||||
target_sources(CemuInput PRIVATE
|
||||
api/SDL/SDLController.cpp
|
||||
api/SDL/SDLControllerProvider.cpp
|
||||
api/SDL/SDLController.h
|
||||
api/SDL/SDLControllerProvider.h
|
||||
)
|
||||
|
||||
# DSU
|
||||
target_sources(CemuInput PRIVATE
|
||||
api/DSU/DSUController.h
|
||||
api/DSU/DSUControllerProvider.cpp
|
||||
api/DSU/DSUController.cpp
|
||||
api/DSU/DSUControllerProvider.h
|
||||
api/DSU/DSUMessages.h
|
||||
api/DSU/DSUMessages.cpp
|
||||
)
|
||||
|
||||
# Keyboard controller
|
||||
target_sources(CemuInput PRIVATE
|
||||
api/Keyboard/KeyboardControllerProvider.h
|
||||
api/Keyboard/KeyboardControllerProvider.cpp
|
||||
api/Keyboard/KeyboardController.cpp
|
||||
api/Keyboard/KeyboardController.h
|
||||
)
|
||||
|
||||
# Native gamecube
|
||||
target_sources(CemuInput PRIVATE
|
||||
api/GameCube/GameCubeController.cpp
|
||||
api/GameCube/GameCubeControllerProvider.h
|
||||
api/GameCube/GameCubeControllerProvider.cpp
|
||||
api/GameCube/GameCubeController.h
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
# Native wiimote (Win32 only for now)
|
||||
target_sources(CemuInput PRIVATE
|
||||
api/Wiimote/WiimoteControllerProvider.h
|
||||
api/Wiimote/windows/WinWiimoteDevice.cpp
|
||||
api/Wiimote/windows/WinWiimoteDevice.h
|
||||
api/Wiimote/WiimoteControllerProvider.cpp
|
||||
api/Wiimote/WiimoteMessages.h
|
||||
api/Wiimote/NativeWiimoteController.h
|
||||
api/Wiimote/NativeWiimoteController.cpp
|
||||
api/Wiimote/WiimoteDevice.h
|
||||
)
|
||||
|
||||
# XInput
|
||||
target_sources(CemuInput PRIVATE
|
||||
api/XInput/XInputControllerProvider.cpp
|
||||
api/XInput/XInputControllerProvider.h
|
||||
api/XInput/XInputController.cpp
|
||||
api/XInput/XInputController.h
|
||||
)
|
||||
|
||||
# DirectInput
|
||||
target_sources(CemuInput PRIVATE
|
||||
api/DirectInput/DirectInputControllerProvider.cpp
|
||||
api/DirectInput/DirectInputController.h
|
||||
api/DirectInput/DirectInputControllerProvider.h
|
||||
api/DirectInput/DirectInputController.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
target_precompile_headers(CemuInput PRIVATE ../Common/precompiled.h)
|
||||
|
||||
target_include_directories(CemuInput PRIVATE ../)
|
180
src/input/ControllerFactory.cpp
Normal file
180
src/input/ControllerFactory.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
#include "input/ControllerFactory.h"
|
||||
|
||||
#include "input/emulated/VPADController.h"
|
||||
#include "input/emulated/ProController.h"
|
||||
#include "input/emulated/ClassicController.h"
|
||||
#include "input/emulated/WiimoteController.h"
|
||||
|
||||
#include "input/api/SDL/SDLController.h"
|
||||
#include "input/api/Keyboard/KeyboardController.h"
|
||||
#include "input/api/DSU/DSUController.h"
|
||||
#include "input/api/GameCube/GameCubeController.h"
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
#include "input/api/XInput/XInputController.h"
|
||||
#include "input/api/DirectInput/DirectInputController.h"
|
||||
#endif
|
||||
|
||||
#if HAS_WIIMOTE
|
||||
#include "input/api/Wiimote/NativeWiimoteController.h"
|
||||
#endif
|
||||
|
||||
ControllerPtr ControllerFactory::CreateController(InputAPI::Type api, std::string_view uuid,
|
||||
std::string_view display_name)
|
||||
{
|
||||
switch (api)
|
||||
{
|
||||
#if HAS_KEYBOARD
|
||||
case InputAPI::Keyboard:
|
||||
return std::make_shared<KeyboardController>();
|
||||
#endif
|
||||
#if HAS_DIRECTINPUT
|
||||
case InputAPI::DirectInput:
|
||||
{
|
||||
GUID guid;
|
||||
// Workaround for mouse2joystick users, which has 0 as it's uuid in it's profile and counts on Cemu applying it to the first directinput controller. GUIDFromString also doesn't allow for invalid uuids either.
|
||||
if (uuid == "0")
|
||||
{
|
||||
const auto provider = InputManager::instance().get_api_provider(InputAPI::DirectInput);
|
||||
const auto controllers = provider->get_controllers();
|
||||
if (controllers.empty())
|
||||
throw std::invalid_argument(fmt::format(
|
||||
"can't apply non-uuid-specific directinput profile when no controllers are available"));
|
||||
if (!GUIDFromString(controllers.front()->uuid().c_str(), guid))
|
||||
throw std::invalid_argument(fmt::format("invalid guid format: {}", uuid));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!GUIDFromString(uuid.data(), guid))
|
||||
throw std::invalid_argument(fmt::format("invalid guid format: {}", uuid));
|
||||
}
|
||||
|
||||
return std::make_shared<DirectInputController>(guid);
|
||||
}
|
||||
#endif
|
||||
#if HAS_XINPUT
|
||||
case InputAPI::XInput:
|
||||
{
|
||||
const auto index = ConvertString<uint32>(uuid);
|
||||
return std::make_shared<XInputController>(index);
|
||||
}
|
||||
#endif
|
||||
#if HAS_SDL
|
||||
case InputAPI::SDLController:
|
||||
{
|
||||
// diid_guid
|
||||
const auto index = uuid.find_first_of('_');
|
||||
if (index == std::string_view::npos)
|
||||
throw std::invalid_argument(fmt::format("invalid sdl uuid format: {}", uuid));
|
||||
|
||||
const auto guid_index = ConvertString<size_t>(uuid.substr(0, index));
|
||||
const auto guid = SDL_JoystickGetGUIDFromString(std::string{uuid.substr(index + 1)}.c_str());
|
||||
|
||||
if (display_name.empty())
|
||||
return std::make_shared<SDLController>(guid, guid_index);
|
||||
else
|
||||
return std::make_shared<SDLController>(guid, guid_index, display_name);
|
||||
}
|
||||
#endif
|
||||
#if HAS_DSU
|
||||
case InputAPI::DSUClient:
|
||||
{
|
||||
const auto index = ConvertString<uint32>(uuid);
|
||||
return std::make_shared<DSUController>(index);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GAMECUBE
|
||||
case InputAPI::GameCube:
|
||||
{
|
||||
const auto index = uuid.find_first_of('_');
|
||||
if (index == std::string_view::npos)
|
||||
throw std::invalid_argument(fmt::format("invalid gamecube uuid format: {}", uuid));
|
||||
|
||||
const auto adapter = ConvertString<int>(uuid.substr(0, index));
|
||||
const auto controller_index = ConvertString<int>(uuid.substr(index + 1));
|
||||
return std::make_shared<GameCubeController>(adapter, controller_index);
|
||||
}
|
||||
#endif
|
||||
#if HAS_WIIMOTE
|
||||
case InputAPI::Wiimote:
|
||||
{
|
||||
const auto index = ConvertString<uint32>(uuid);
|
||||
return std::make_shared<NativeWiimoteController>(index);
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
throw std::invalid_argument(fmt::format("unhandled controller api: {}", api));
|
||||
}
|
||||
/*
|
||||
case InputAPI::WGIGamepad: break;
|
||||
case InputAPI::WGIRawController: break;
|
||||
*/
|
||||
}
|
||||
|
||||
EmulatedControllerPtr
|
||||
ControllerFactory::CreateEmulatedController(size_t player_index, EmulatedController::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EmulatedController::Type::VPAD:
|
||||
return std::make_shared<VPADController>(player_index);
|
||||
case EmulatedController::Type::Pro:
|
||||
return std::make_shared<ProController>(player_index);
|
||||
case EmulatedController::Type::Classic:
|
||||
return std::make_shared<ClassicController>(player_index);
|
||||
case EmulatedController::Type::Wiimote:
|
||||
return std::make_shared<WiimoteController>(player_index);
|
||||
default:
|
||||
throw std::runtime_error(fmt::format("unknown emulated controller type: {}", type));
|
||||
}
|
||||
}
|
||||
|
||||
ControllerProviderPtr ControllerFactory::CreateControllerProvider(InputAPI::Type api, const ControllerProviderSettings& settings)
|
||||
{
|
||||
switch (api)
|
||||
{
|
||||
#if HAS_KEYBOARD
|
||||
case InputAPI::Keyboard:
|
||||
return std::make_shared<KeyboardControllerProvider>();
|
||||
#endif
|
||||
#if HAS_SDL
|
||||
case InputAPI::SDLController:
|
||||
return std::make_shared<SDLControllerProvider>();
|
||||
#endif
|
||||
#if HAS_XINPUT
|
||||
case InputAPI::XInput:
|
||||
return std::make_shared<XInputControllerProvider>();
|
||||
#endif
|
||||
#if HAS_DIRECTINPUT
|
||||
case InputAPI::DirectInput:
|
||||
return std::make_shared<DirectInputControllerProvider>();
|
||||
#endif
|
||||
#if HAS_DSU
|
||||
case InputAPI::DSUClient:
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto& dsu_settings = dynamic_cast<const DSUProviderSettings&>(settings);
|
||||
return std::make_shared<DSUControllerProvider>(dsu_settings);
|
||||
}
|
||||
catch (const std::bad_cast&)
|
||||
{
|
||||
cemuLog_force("failing to cast ControllerProviderSettings class to DSUControllerProvider");
|
||||
return std::make_shared<DSUControllerProvider>();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#if HAS_GAMECUBE
|
||||
case InputAPI::GameCube:
|
||||
return std::make_shared<GameCubeControllerProvider>();
|
||||
#endif
|
||||
#if HAS_WIIMOTE
|
||||
case InputAPI::Wiimote:
|
||||
return std::make_shared<WiimoteControllerProvider>();
|
||||
#endif
|
||||
default:
|
||||
cemu_assert_debug(false);
|
||||
return {};
|
||||
}
|
||||
}
|
13
src/input/ControllerFactory.h
Normal file
13
src/input/ControllerFactory.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/InputAPI.h"
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/emulated/EmulatedController.h"
|
||||
|
||||
class ControllerFactory
|
||||
{
|
||||
public:
|
||||
static ControllerPtr CreateController(InputAPI::Type api, std::string_view uuid, std::string_view display_name);
|
||||
static EmulatedControllerPtr CreateEmulatedController(size_t player_index, EmulatedController::Type type);
|
||||
static ControllerProviderPtr CreateControllerProvider(InputAPI::Type api, const ControllerProviderSettings& settings);
|
||||
};
|
954
src/input/InputManager.cpp
Normal file
954
src/input/InputManager.cpp
Normal file
|
@ -0,0 +1,954 @@
|
|||
#include "input/InputManager.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "input/ControllerFactory.h"
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include "Cafe/GameProfile/GameProfile.h"
|
||||
#include "util/EventService.h"
|
||||
|
||||
InputManager::InputManager()
|
||||
{
|
||||
/*
|
||||
auto create_provider = []
|
||||
template <typename TProvider>
|
||||
()
|
||||
{
|
||||
static_assert(std::is_base_of_v<ControllerProvider, TProvider>);
|
||||
try
|
||||
{
|
||||
auto controller = std::make_shared<TProvider>();
|
||||
m_api_available[controller->api()] = controller;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_force(ex.what());
|
||||
}
|
||||
}
|
||||
*/
|
||||
#if HAS_KEYBOARD
|
||||
create_provider<KeyboardControllerProvider>();
|
||||
#endif
|
||||
#if HAS_SDL
|
||||
create_provider<SDLControllerProvider>();
|
||||
#endif
|
||||
#if HAS_XINPUT
|
||||
create_provider<XInputControllerProvider>();
|
||||
#endif
|
||||
#if HAS_DIRECTINPUT
|
||||
create_provider<DirectInputControllerProvider>();
|
||||
#endif
|
||||
#if HAS_DSU
|
||||
create_provider<DSUControllerProvider>();
|
||||
#endif
|
||||
#if HAS_GAMECUBE
|
||||
create_provider<GameCubeControllerProvider>();
|
||||
#endif
|
||||
#if HAS_WIIMOTE
|
||||
create_provider<WiimoteControllerProvider>();
|
||||
#endif
|
||||
|
||||
m_update_thread_shutdown.store(false);
|
||||
m_update_thread = std::thread(&InputManager::update_thread, this);
|
||||
}
|
||||
|
||||
InputManager::~InputManager()
|
||||
{
|
||||
m_update_thread_shutdown.store(true);
|
||||
m_update_thread.join();
|
||||
}
|
||||
|
||||
void InputManager::load() noexcept
|
||||
{
|
||||
for (size_t i = 0; i < kMaxController; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
load(i);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_force("can't load controller profile: {}", ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool InputManager::load(size_t player_index, std::string_view filename)
|
||||
{
|
||||
fs::path file_path;
|
||||
if (filename.empty())
|
||||
file_path = ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}", player_index));
|
||||
else
|
||||
file_path = ActiveSettings::GetPath(fmt::format("controllerProfiles/{}", filename));
|
||||
|
||||
auto old_file = file_path;
|
||||
old_file.replace_extension(".txt"); // test .txt extension
|
||||
file_path.replace_extension(".xml"); // force .xml extension
|
||||
|
||||
if (fs::exists(old_file) && !fs::exists(file_path))
|
||||
migrate_config(old_file);
|
||||
|
||||
if (!fs::exists(file_path))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load(file))
|
||||
return false;
|
||||
|
||||
const pugi::xml_node root = doc.document_element();
|
||||
|
||||
const auto type_node = root.child("type");
|
||||
if (!type_node)
|
||||
return false;
|
||||
|
||||
const auto emulate = EmulatedController::type_from_string(type_node.child_value());
|
||||
auto emulated_controller = ControllerFactory::CreateEmulatedController(player_index, emulate);
|
||||
|
||||
|
||||
if (const auto profile_name_node = root.child("profile"))
|
||||
emulated_controller->m_profile_name = profile_name_node.child_value();
|
||||
|
||||
// custom settings
|
||||
emulated_controller->load(root);
|
||||
|
||||
for (const auto controller_node : root.select_nodes("controller"))
|
||||
{
|
||||
const auto cnode = controller_node.node();
|
||||
const auto api_node = cnode.child("api");
|
||||
if (!api_node)
|
||||
continue;
|
||||
|
||||
const auto uuid_node = cnode.child("uuid");
|
||||
if (!uuid_node)
|
||||
continue;
|
||||
|
||||
const auto* display_name = cnode.child_value("display_name");
|
||||
|
||||
try
|
||||
{
|
||||
const auto api = InputAPI::from_string(api_node.child_value());
|
||||
auto controller = ControllerFactory::CreateController(api, uuid_node.child_value(), display_name);
|
||||
emulated_controller->add_controller(controller);
|
||||
|
||||
// load optional settings
|
||||
auto settings = controller->get_settings();
|
||||
if (const auto axis_node = cnode.child("axis"))
|
||||
{
|
||||
if (const auto value = axis_node.child("deadzone"))
|
||||
settings.axis.deadzone = ConvertString<float>(value.child_value());
|
||||
|
||||
if (const auto value = axis_node.child("range"))
|
||||
settings.axis.range = ConvertString<float>(value.child_value());
|
||||
}
|
||||
if (const auto rotation_node = cnode.child("rotation"))
|
||||
{
|
||||
if (const auto value = rotation_node.child("deadzone"))
|
||||
settings.rotation.deadzone = ConvertString<float>(value.child_value());
|
||||
|
||||
if (const auto value = rotation_node.child("range"))
|
||||
settings.rotation.range = ConvertString<float>(value.child_value());
|
||||
}
|
||||
if (const auto trigger_node = cnode.child("trigger"))
|
||||
{
|
||||
if (const auto value = trigger_node.child("deadzone"))
|
||||
settings.trigger.deadzone = ConvertString<float>(value.child_value());
|
||||
|
||||
if (const auto value = trigger_node.child("range"))
|
||||
settings.trigger.range = ConvertString<float>(value.child_value());
|
||||
}
|
||||
|
||||
if (const auto value = cnode.child("rumble"))
|
||||
settings.rumble = ConvertString<float>(value.child_value());
|
||||
|
||||
if (const auto value = cnode.child("motion"))
|
||||
settings.motion = ConvertString<bool>(value.child_value());
|
||||
|
||||
controller->set_settings(settings);
|
||||
|
||||
// custom settings
|
||||
controller->load(cnode);
|
||||
|
||||
|
||||
// mappings
|
||||
if (const auto mappings_node = cnode.child("mappings"))
|
||||
{
|
||||
for (const auto& entry : mappings_node.select_nodes("entry"))
|
||||
{
|
||||
const auto enode = entry.node();
|
||||
|
||||
const auto mapping_node = enode.child("mapping");
|
||||
if (!mapping_node)
|
||||
continue;
|
||||
|
||||
const auto button_node = enode.child("button");
|
||||
if (!button_node)
|
||||
continue;
|
||||
|
||||
const auto mapping = ConvertString<uint64>(mapping_node.child_value());
|
||||
const auto button = ConvertString<uint64>(button_node.child_value());
|
||||
|
||||
emulated_controller->set_mapping(mapping, controller, button);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_force("can't load controller: {}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
set_controller(emulated_controller);
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_force("can't load config file: {}", ex.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool InputManager::migrate_config(const fs::path& file_path)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
||||
boost::property_tree::ptree m_data;
|
||||
read_ini(file, m_data);
|
||||
|
||||
const auto emulate_string = m_data.get<std::string>("General.emulate");
|
||||
const auto api_string = m_data.get<std::string>("General.api");
|
||||
auto uuid_opt = m_data.get_optional<std::string>("General.controller");
|
||||
const auto display_name = m_data.get_optional<std::string>("General.display");
|
||||
|
||||
std::string uuid;
|
||||
if (api_string == to_string(InputAPI::Keyboard))
|
||||
uuid = to_string(InputAPI::Keyboard);
|
||||
else
|
||||
{
|
||||
if (!uuid_opt)
|
||||
return false;
|
||||
|
||||
uuid = uuid_opt.value();
|
||||
if (api_string == to_string(InputAPI::SDLController))
|
||||
{
|
||||
uuid += "_0";
|
||||
}
|
||||
}
|
||||
|
||||
fs::path out_file = file_path;
|
||||
out_file.replace_extension(".xml");
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto declaration_node = doc.append_child(pugi::node_declaration);
|
||||
declaration_node.append_attribute("version") = "1.0";
|
||||
declaration_node.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
auto emulated_controller = doc.append_child("emulated_controller");
|
||||
emulated_controller.append_child("type").append_child(pugi::node_pcdata).set_value(emulate_string.c_str());
|
||||
|
||||
bool has_keyboard = api_string == to_string(InputAPI::Keyboard);
|
||||
if (!has_keyboard) // test if only keyboard configured
|
||||
{
|
||||
auto controller = emulated_controller.append_child("controller");
|
||||
controller.append_child("api").append_child(pugi::node_pcdata).set_value(api_string.c_str());
|
||||
controller.append_child("uuid").append_child(pugi::node_pcdata).set_value(uuid.c_str());
|
||||
if (display_name.has_value() && !display_name->empty())
|
||||
controller.append_child("display_name").append_child(pugi::node_pcdata).set_value(
|
||||
display_name.value().c_str());
|
||||
|
||||
|
||||
controller.append_child("rumble").append_child(pugi::node_pcdata).set_value(
|
||||
m_data.get<std::string>("Controller.rumble").c_str());
|
||||
|
||||
auto axis_node = controller.append_child("axis");
|
||||
axis_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value(
|
||||
m_data.get<std::string>("Controller.leftDeadzone").c_str());
|
||||
axis_node.append_child("range").append_child(pugi::node_pcdata).set_value(
|
||||
m_data.get<std::string>("Controller.leftRange").c_str());
|
||||
|
||||
auto rotation_node = controller.append_child("rotation");
|
||||
rotation_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value(
|
||||
m_data.get<std::string>("Controller.rightDeadzone").c_str());
|
||||
rotation_node.append_child("range").append_child(pugi::node_pcdata).set_value(
|
||||
m_data.get<std::string>("Controller.rightRange").c_str());
|
||||
|
||||
auto mappings_node = controller.append_child("mappings");
|
||||
for (int i = 1; i < 28; ++i) // test all possible mappings (max is 27 for vpad controller)
|
||||
{
|
||||
auto mapping = m_data.get_optional<std::string>(fmt::format("Controller.{}", i));
|
||||
if (!mapping || mapping->empty())
|
||||
continue;
|
||||
|
||||
if (!boost::starts_with(mapping.value(), "button_"))
|
||||
{
|
||||
if (boost::starts_with(mapping.value(), "key_"))
|
||||
has_keyboard = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto button = ConvertString<uint64>(mapping.value().substr(7), 16);
|
||||
|
||||
uint64 flag_bit = 0;
|
||||
for (auto b = 0; b < 64; ++b)
|
||||
{
|
||||
if (HAS_BIT(button, b))
|
||||
{
|
||||
flag_bit = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// fix old flag layout to new one for all kind of axis stuff
|
||||
if (flag_bit >= 24 && flag_bit <= 31)
|
||||
flag_bit += 8;
|
||||
else if (flag_bit == 32) flag_bit = kTriggerXP;
|
||||
else if (flag_bit == 33) flag_bit = kRotationXP;
|
||||
else if (flag_bit == 34) flag_bit = kRotationYP;
|
||||
else if (flag_bit == 35) flag_bit = kTriggerYP;
|
||||
else if (flag_bit == 36) flag_bit = kAxisXN;
|
||||
else if (flag_bit == 37) flag_bit = kAxisYN;
|
||||
else if (flag_bit == 38) flag_bit = kTriggerXN;
|
||||
else if (flag_bit == 39) flag_bit = kRotationXN;
|
||||
else if (flag_bit == 40) flag_bit = kRotationYN;
|
||||
else if (flag_bit == 41) flag_bit = kTriggerYN;
|
||||
|
||||
// fix old api mappings
|
||||
if (api_string == to_string(InputAPI::XInput))
|
||||
{
|
||||
const std::unordered_map<uint64, uint64> xinput =
|
||||
{
|
||||
{kButton0, 12}, // XINPUT_GAMEPAD_A
|
||||
{kButton1, 13}, // XINPUT_GAMEPAD_B
|
||||
{kButton2, 14}, // XINPUT_GAMEPAD_X
|
||||
{kButton3, 15}, // XINPUT_GAMEPAD_Y
|
||||
|
||||
{kButton4, 8}, // XINPUT_GAMEPAD_LEFT_SHOULDER
|
||||
{kButton5, 9}, // XINPUT_GAMEPAD_LEFT_SHOULDER
|
||||
|
||||
{kButton6, 4}, // XINPUT_GAMEPAD_START
|
||||
{kButton7, 5}, // XINPUT_GAMEPAD_BACK
|
||||
|
||||
{kButton8, 6}, // XINPUT_GAMEPAD_LEFT_THUMB
|
||||
{kButton9, 7}, // XINPUT_GAMEPAD_RIGHT_THUMB
|
||||
|
||||
{kButton10, 0}, // XINPUT_GAMEPAD_DPAD_UP
|
||||
{kButton11, 1}, // XINPUT_GAMEPAD_DPAD_DOWN
|
||||
{kButton12, 2}, // XINPUT_GAMEPAD_DPAD_LEFT
|
||||
{kButton13, 3}, // XINPUT_GAMEPAD_DPAD_RIGHT
|
||||
};
|
||||
|
||||
const auto it = xinput.find(flag_bit);
|
||||
if (it != xinput.cend())
|
||||
flag_bit = it->second;
|
||||
}
|
||||
else if (api_string == "DSU")
|
||||
{
|
||||
const std::unordered_map<uint64, uint64> dsu =
|
||||
{
|
||||
{7, kButton0}, // ButtonSelect
|
||||
{8, kButton1}, // ButtonLStick
|
||||
{9, kButton2}, // ButtonRStick
|
||||
{6, kButton3}, // ButtonStart
|
||||
|
||||
{4, kButton10}, // ButtonL
|
||||
{5, kButton11}, // ButtonR
|
||||
|
||||
{0, kButton14}, // ButtonA
|
||||
{1, kButton13}, // ButtonB
|
||||
{2, kButton15}, // ButtonX
|
||||
{3, kButton12}, // ButtonY
|
||||
};
|
||||
|
||||
const auto it = dsu.find(flag_bit);
|
||||
if (it != dsu.cend())
|
||||
flag_bit = it->second;
|
||||
}
|
||||
|
||||
|
||||
auto entry_node = mappings_node.append_child("entry");
|
||||
entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", i).c_str());
|
||||
entry_node.append_child("button").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", flag_bit).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (has_keyboard)
|
||||
{
|
||||
auto controller = emulated_controller.append_child("controller");
|
||||
controller.append_child("api").append_child(pugi::node_pcdata).set_value("Keyboard");
|
||||
controller.append_child("uuid").append_child(pugi::node_pcdata).set_value("Keyboard");
|
||||
|
||||
auto mappings_node = controller.append_child("mappings");
|
||||
for (int i = 1; i < 28; ++i) // test all possible mappings (max is 27 for vpad controller)
|
||||
{
|
||||
auto mapping = m_data.get_optional<std::string>(fmt::format("Controller.{}", i));
|
||||
if (!mapping || mapping->empty())
|
||||
continue;
|
||||
|
||||
if (!boost::starts_with(mapping.value(), "key_"))
|
||||
continue;
|
||||
|
||||
const auto button = ConvertString<uint64>(mapping.value().substr(4));
|
||||
|
||||
auto entry_node = mappings_node.append_child("entry");
|
||||
entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", i).c_str());
|
||||
entry_node.append_child("button").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", button).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream write_file(out_file, std::ios::out | std::ios::trunc);
|
||||
if (write_file.is_open())
|
||||
{
|
||||
doc.save(write_file);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_force("can't migrate config file {}: {}", file_path.string(), ex.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void InputManager::save() noexcept
|
||||
{
|
||||
for (size_t i = 0; i < kMaxController; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
save(i);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_force("can't save controller profile: {}", ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool InputManager::save(size_t player_index, std::string_view filename)
|
||||
{
|
||||
// dont overwrite files if set by gameprofile
|
||||
if (m_is_gameprofile_set[player_index])
|
||||
return true;
|
||||
|
||||
auto emulated_controller = get_controller(player_index);
|
||||
if (!emulated_controller)
|
||||
return false;
|
||||
|
||||
fs::path file_path = ActiveSettings::GetPath("controllerProfiles");
|
||||
fs::create_directories(file_path);
|
||||
|
||||
const auto is_default_file = filename.empty();
|
||||
if (is_default_file)
|
||||
file_path /= fmt::format("controller{}", player_index);
|
||||
else
|
||||
file_path /= filename;
|
||||
|
||||
file_path.replace_extension(".xml"); // force .xml extension
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto declaration_node = doc.append_child(pugi::node_declaration);
|
||||
declaration_node.append_attribute("version") = "1.0";
|
||||
declaration_node.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
auto emulated_controller_node = doc.append_child("emulated_controller");
|
||||
emulated_controller_node.append_child("type").append_child(pugi::node_pcdata).set_value(std::string{
|
||||
emulated_controller->type_string()
|
||||
}.c_str());
|
||||
|
||||
if (emulated_controller->has_profile_name())
|
||||
emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value(
|
||||
emulated_controller->get_profile_name().c_str());
|
||||
else if (!is_default_file)
|
||||
{
|
||||
emulated_controller->m_profile_name = std::string{filename};
|
||||
emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value(
|
||||
emulated_controller->get_profile_name().c_str());
|
||||
}
|
||||
|
||||
// custom settings
|
||||
emulated_controller->save(emulated_controller_node);
|
||||
|
||||
for (const auto& controller : emulated_controller->get_controllers())
|
||||
{
|
||||
auto controller_node = emulated_controller_node.append_child("controller");
|
||||
|
||||
// general
|
||||
controller_node.append_child("api").append_child(pugi::node_pcdata).set_value(std::string{
|
||||
controller->api_name()
|
||||
}.c_str());
|
||||
controller_node.append_child("uuid").append_child(pugi::node_pcdata).set_value(controller->uuid().c_str());
|
||||
controller_node.append_child("display_name").append_child(pugi::node_pcdata).set_value(
|
||||
controller->display_name().c_str());
|
||||
|
||||
// settings
|
||||
const auto& settings = controller->get_settings();
|
||||
|
||||
if (controller->has_motion())
|
||||
controller_node.append_child("motion").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", settings.motion).c_str());
|
||||
|
||||
if (controller->has_rumble())
|
||||
controller_node.append_child("rumble").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", settings.rumble).c_str());
|
||||
|
||||
auto axis_node = controller_node.append_child("axis");
|
||||
axis_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", settings.axis.deadzone).c_str());
|
||||
axis_node.append_child("range").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", settings.axis.range).c_str());
|
||||
|
||||
auto rotation_node = controller_node.append_child("rotation");
|
||||
rotation_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", settings.rotation.deadzone).c_str());
|
||||
rotation_node.append_child("range").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", settings.rotation.range).c_str());
|
||||
|
||||
auto trigger_node = controller_node.append_child("trigger");
|
||||
trigger_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", settings.trigger.deadzone).c_str());
|
||||
trigger_node.append_child("range").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", settings.trigger.range).c_str());
|
||||
|
||||
// custom settings
|
||||
controller->save(controller_node);
|
||||
|
||||
// mappings for current controller
|
||||
auto mappings_node = controller_node.append_child("mappings");
|
||||
for (const auto& mapping : emulated_controller->m_mappings)
|
||||
{
|
||||
if (!mapping.second.controller.expired() && *controller == *mapping.second.controller.lock())
|
||||
{
|
||||
auto entry_node = mappings_node.append_child("entry");
|
||||
entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", mapping.first).c_str());
|
||||
entry_node.append_child("button").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", mapping.second.button).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::ofstream file(file_path, std::ios::out | std::ios::trunc);
|
||||
if (file.is_open())
|
||||
{
|
||||
doc.save(file);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputManager::is_gameprofile_set(size_t player_index) const
|
||||
{
|
||||
return m_is_gameprofile_set[player_index];
|
||||
}
|
||||
|
||||
EmulatedControllerPtr InputManager::set_controller(EmulatedControllerPtr controller)
|
||||
{
|
||||
auto prev_controller = delete_controller(controller->player_index());
|
||||
|
||||
// assign controllers to new emulated controller if empty
|
||||
if (prev_controller && controller->get_controllers().empty())
|
||||
{
|
||||
for (const auto& c : prev_controller->get_controllers())
|
||||
{
|
||||
controller->add_controller(c);
|
||||
}
|
||||
}
|
||||
|
||||
// try to connect all controllers
|
||||
/*for (auto& c : controller->get_controllers())
|
||||
{
|
||||
c->connect();
|
||||
}*/
|
||||
|
||||
std::scoped_lock lock(m_mutex);
|
||||
switch (controller->type())
|
||||
{
|
||||
case EmulatedController::Type::VPAD:
|
||||
for (auto& pad : m_vpad)
|
||||
{
|
||||
if (!pad)
|
||||
{
|
||||
pad.swap(controller);
|
||||
return prev_controller;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
for (auto& pad : m_wpad)
|
||||
{
|
||||
if (!pad)
|
||||
{
|
||||
pad.swap(controller);
|
||||
return prev_controller;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
cemu_assert_debug(false);
|
||||
return prev_controller;
|
||||
}
|
||||
|
||||
EmulatedControllerPtr InputManager::set_controller(size_t player_index, EmulatedController::Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto emulated_controller = ControllerFactory::CreateEmulatedController(player_index, type);
|
||||
set_controller(emulated_controller);
|
||||
return emulated_controller;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_force("Unable to set controller type {} on player index {}: {}", type, player_index, ex.what());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
EmulatedControllerPtr InputManager::set_controller(size_t player_index, EmulatedController::Type type,
|
||||
const std::shared_ptr<ControllerBase>& controller)
|
||||
{
|
||||
auto result = set_controller(player_index, type);
|
||||
if (result)
|
||||
result->add_controller(controller);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EmulatedControllerPtr InputManager::get_controller(size_t player_index) const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& pad : m_vpad)
|
||||
{
|
||||
if (pad && pad->player_index() == player_index)
|
||||
return pad;
|
||||
}
|
||||
|
||||
for (const auto& pad : m_wpad)
|
||||
{
|
||||
if (pad && pad->player_index() == player_index)
|
||||
return pad;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
EmulatedControllerPtr InputManager::delete_controller(size_t player_index, bool delete_profile)
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
for (auto& controller : m_vpad)
|
||||
{
|
||||
auto result = controller;
|
||||
if (result && result->player_index() == player_index)
|
||||
{
|
||||
controller = {};
|
||||
|
||||
if(delete_profile)
|
||||
{
|
||||
std::error_code ec{};
|
||||
fs::remove(ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}.xml", player_index)), ec);
|
||||
fs::remove(ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}.txt", player_index)), ec);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& controller : m_wpad)
|
||||
{
|
||||
auto result = controller;
|
||||
if (result && result->player_index() == player_index)
|
||||
{
|
||||
controller = {};
|
||||
|
||||
std::error_code ec{};
|
||||
fs::remove(ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}.xml", player_index)), ec);
|
||||
fs::remove(ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}.txt", player_index)), ec);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<VPADController> InputManager::get_vpad_controller(size_t index) const
|
||||
{
|
||||
if (index >= m_vpad.size())
|
||||
return {};
|
||||
|
||||
std::shared_lock lock(m_mutex);
|
||||
return std::static_pointer_cast<VPADController>(m_vpad[index]);
|
||||
}
|
||||
|
||||
std::shared_ptr<WPADController> InputManager::get_wpad_controller(size_t index) const
|
||||
{
|
||||
if (index >= m_wpad.size())
|
||||
return {};
|
||||
|
||||
std::shared_lock lock(m_mutex);
|
||||
return std::static_pointer_cast<WPADController>(m_wpad[index]);
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> InputManager::get_controller_count() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
const size_t vpad = std::count_if(m_vpad.cbegin(), m_vpad.cend(), [](const auto& v) { return v != nullptr; });
|
||||
const size_t wpad = std::count_if(m_wpad.cbegin(), m_wpad.cend(), [](const auto& v) { return v != nullptr; });
|
||||
return std::make_pair(vpad, wpad);
|
||||
}
|
||||
|
||||
void InputManager::on_device_changed()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (auto& pad : m_vpad)
|
||||
{
|
||||
if (pad)
|
||||
pad->connect();
|
||||
}
|
||||
|
||||
for (auto& pad : m_wpad)
|
||||
{
|
||||
if (pad)
|
||||
pad->connect();
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
EventService::instance().signal<Events::ControllerChanged>();
|
||||
}
|
||||
|
||||
ControllerProviderPtr InputManager::get_api_provider(InputAPI::Type api) const
|
||||
{
|
||||
if(!m_api_available[api].empty())
|
||||
return *(m_api_available[api].begin());
|
||||
|
||||
cemu_assert_debug(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
ControllerProviderPtr InputManager::get_api_provider(InputAPI::Type api, const ControllerProviderSettings& settings)
|
||||
{
|
||||
for(const auto& p : m_api_available[api])
|
||||
{
|
||||
if(*p == settings)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
const auto result = ControllerFactory::CreateControllerProvider(api, settings);
|
||||
m_api_available[api].emplace_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void InputManager::apply_game_profile()
|
||||
{
|
||||
const auto& profiles = g_current_game_profile->GetControllerProfile();
|
||||
for (int i = 0; i < kMaxController; ++i)
|
||||
{
|
||||
if (profiles[i] && !profiles[i]->empty())
|
||||
{
|
||||
if (load(i, profiles[i].value()))
|
||||
{
|
||||
m_is_gameprofile_set[i] = true;
|
||||
if (const auto controller = get_controller(i))
|
||||
{
|
||||
if (!controller->has_profile_name())
|
||||
controller->m_profile_name = profiles[i].value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> InputManager::get_profiles()
|
||||
{
|
||||
const auto path = ActiveSettings::GetPath("controllerProfiles");
|
||||
if (!exists(path))
|
||||
return {};
|
||||
|
||||
std::set<std::string> tmp;
|
||||
for (const auto& entry : fs::directory_iterator(path))
|
||||
{
|
||||
const auto& p = entry.path();
|
||||
if (p.has_extension() && (p.extension() == ".xml" || p.extension() == ".txt"))
|
||||
{
|
||||
auto stem = p.filename().stem().string();
|
||||
if (is_valid_profilename(stem))
|
||||
{
|
||||
tmp.emplace(stem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> result;
|
||||
result.reserve(tmp.size());
|
||||
result.insert(result.end(), tmp.begin(), tmp.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
bool InputManager::is_valid_profilename(const std::string& name)
|
||||
{
|
||||
if (!boost::filesystem::windows_name(name))
|
||||
return false;
|
||||
|
||||
// dont allow default profile names
|
||||
for (size_t i = 0; i < kMaxController; i++)
|
||||
{
|
||||
if (name == fmt::format("controller{}", i))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
glm::ivec2 InputManager::get_mouse_position(bool pad_window) const
|
||||
{
|
||||
if (pad_window)
|
||||
{
|
||||
std::shared_lock lock(m_pad_mouse.m_mutex);
|
||||
return m_pad_mouse.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::shared_lock lock(m_main_mouse.m_mutex);
|
||||
return m_main_mouse.position;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<glm::ivec2> InputManager::get_left_down_mouse_info(bool* is_pad)
|
||||
{
|
||||
if (is_pad)
|
||||
*is_pad = false;
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_main_mouse.m_mutex);
|
||||
if (std::exchange(m_main_mouse.left_down_toggle, false))
|
||||
return m_main_mouse.position;
|
||||
|
||||
if (m_main_mouse.left_down)
|
||||
return m_main_mouse.position;
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_main_touch.m_mutex);
|
||||
if (std::exchange(m_main_touch.left_down_toggle, false))
|
||||
return m_main_touch.position;
|
||||
|
||||
if (m_main_touch.left_down)
|
||||
return m_main_touch.position;
|
||||
}
|
||||
|
||||
if (is_pad)
|
||||
*is_pad = true;
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_pad_mouse.m_mutex);
|
||||
if (std::exchange(m_pad_mouse.left_down_toggle, false))
|
||||
return m_pad_mouse.position;
|
||||
|
||||
if (m_pad_mouse.left_down)
|
||||
return m_pad_mouse.position;
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_pad_touch.m_mutex);
|
||||
if (std::exchange(m_pad_touch.left_down_toggle, false))
|
||||
return m_pad_touch.position;
|
||||
|
||||
if (m_pad_touch.left_down)
|
||||
return m_pad_touch.position;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<glm::ivec2> InputManager::get_right_down_mouse_info(bool* is_pad)
|
||||
{
|
||||
if (is_pad)
|
||||
*is_pad = false;
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_main_mouse.m_mutex);
|
||||
if (std::exchange(m_main_mouse.right_down_toggle, false))
|
||||
return m_main_mouse.position;
|
||||
|
||||
if (m_main_mouse.right_down)
|
||||
return m_main_mouse.position;
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_main_touch.m_mutex);
|
||||
if (std::exchange(m_main_touch.right_down_toggle, false))
|
||||
return m_main_touch.position;
|
||||
|
||||
if (m_main_touch.right_down)
|
||||
return m_main_touch.position;
|
||||
}
|
||||
|
||||
if (is_pad)
|
||||
*is_pad = true;
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_pad_mouse.m_mutex);
|
||||
if (std::exchange(m_pad_mouse.right_down_toggle, false))
|
||||
return m_pad_mouse.position;
|
||||
|
||||
if (m_pad_mouse.right_down)
|
||||
return m_pad_mouse.position;
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_pad_touch.m_mutex);
|
||||
if (std::exchange(m_pad_touch.right_down_toggle, false))
|
||||
return m_pad_touch.position;
|
||||
|
||||
if (m_pad_touch.right_down)
|
||||
return m_pad_touch.position;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void InputManager::update_thread()
|
||||
{
|
||||
SetThreadName("InputManager::update_thread");
|
||||
while (!m_update_thread_shutdown.load(std::memory_order::relaxed))
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (auto& pad : m_vpad)
|
||||
{
|
||||
if (pad)
|
||||
pad->update();
|
||||
}
|
||||
|
||||
for (auto& pad : m_wpad)
|
||||
{
|
||||
if (pad)
|
||||
pad->update();
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
120
src/input/InputManager.h
Normal file
120
src/input/InputManager.h
Normal file
|
@ -0,0 +1,120 @@
|
|||
#pragma once
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
#include "input/api/DirectInput/DirectInputControllerProvider.h"
|
||||
#include "input/api/XInput/XInputControllerProvider.h"
|
||||
#include "input/api/Wiimote/WiimoteControllerProvider.h"
|
||||
#endif
|
||||
|
||||
#include "util/helpers/Singleton.h"
|
||||
|
||||
#include "input/api/SDL/SDLControllerProvider.h"
|
||||
#include "input/api/Keyboard/KeyboardControllerProvider.h"
|
||||
#include "input/api/DSU/DSUControllerProvider.h"
|
||||
#include "input/api/GameCube/GameCubeControllerProvider.h"
|
||||
|
||||
#include "input/emulated/VPADController.h"
|
||||
#include "input/emulated/WPADController.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
|
||||
class InputManager : public Singleton<InputManager>
|
||||
{
|
||||
friend class Singleton<InputManager>;
|
||||
InputManager();
|
||||
~InputManager();
|
||||
|
||||
friend class MainWindow;
|
||||
friend class PadViewFrame;
|
||||
|
||||
public:
|
||||
constexpr static size_t kMaxController = 8;
|
||||
constexpr static size_t kMaxVPADControllers = 2;
|
||||
constexpr static size_t kMaxWPADControllers = 7;
|
||||
|
||||
void load() noexcept;
|
||||
bool load(size_t player_index, std::string_view filename = {});
|
||||
|
||||
bool migrate_config(const fs::path& file_path);
|
||||
|
||||
void save() noexcept;
|
||||
bool save(size_t player_index, std::string_view filename = {});
|
||||
|
||||
bool is_gameprofile_set(size_t player_index) const;
|
||||
|
||||
EmulatedControllerPtr set_controller(EmulatedControllerPtr controller);
|
||||
EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type);
|
||||
EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type, const std::shared_ptr<ControllerBase>& controller);
|
||||
|
||||
EmulatedControllerPtr delete_controller(size_t player_index, bool delete_profile = false);
|
||||
|
||||
EmulatedControllerPtr get_controller(size_t player_index) const;
|
||||
std::shared_ptr<VPADController> get_vpad_controller(size_t index) const;
|
||||
std::shared_ptr<WPADController> get_wpad_controller(size_t index) const;
|
||||
std::pair<size_t, size_t> get_controller_count() const;
|
||||
|
||||
bool is_api_available(InputAPI::Type api) const { return !m_api_available[api].empty(); }
|
||||
|
||||
ControllerProviderPtr get_api_provider(std::string_view api_name) const;
|
||||
ControllerProviderPtr get_api_provider(InputAPI::Type api) const;
|
||||
// will create the provider with the given settings if it doesn't exist yet
|
||||
ControllerProviderPtr get_api_provider(InputAPI::Type api, const ControllerProviderSettings& settings);
|
||||
|
||||
const auto& get_api_providers() const
|
||||
{
|
||||
return m_api_available;
|
||||
}
|
||||
|
||||
void apply_game_profile();
|
||||
void on_device_changed();
|
||||
|
||||
|
||||
static std::vector<std::string> get_profiles();
|
||||
static bool is_valid_profilename(const std::string& name);
|
||||
|
||||
struct MouseInfo
|
||||
{
|
||||
mutable std::shared_mutex m_mutex;
|
||||
glm::ivec2 position{};
|
||||
bool left_down = false;
|
||||
bool right_down = false;
|
||||
|
||||
bool left_down_toggle = false;
|
||||
bool right_down_toggle = false;
|
||||
} m_main_mouse{}, m_pad_mouse{}, m_main_touch{}, m_pad_touch{};
|
||||
glm::ivec2 get_mouse_position(bool pad_window) const;
|
||||
std::optional<glm::ivec2> get_left_down_mouse_info(bool* is_pad);
|
||||
std::optional<glm::ivec2> get_right_down_mouse_info(bool* is_pad);
|
||||
|
||||
std::atomic<float> m_mouse_wheel;
|
||||
|
||||
private:
|
||||
void update_thread();
|
||||
|
||||
std::thread m_update_thread;
|
||||
std::atomic<bool> m_update_thread_shutdown{false};
|
||||
|
||||
std::array<std::vector<ControllerProviderPtr>, InputAPI::MAX> m_api_available{ };
|
||||
|
||||
mutable std::shared_mutex m_mutex;
|
||||
std::array<EmulatedControllerPtr, kMaxVPADControllers> m_vpad;
|
||||
std::array<EmulatedControllerPtr, kMaxWPADControllers> m_wpad;
|
||||
|
||||
std::array<bool, kMaxController> m_is_gameprofile_set{};
|
||||
|
||||
template <typename TProvider>
|
||||
void create_provider() // lambda templates only work in c++20 -> define locally in ctor
|
||||
{
|
||||
static_assert(std::is_base_of_v<ControllerProviderBase, TProvider>);
|
||||
try
|
||||
{
|
||||
auto controller = std::make_shared<TProvider>();
|
||||
m_api_available[controller->api()] = std::vector<ControllerProviderPtr>{ controller };
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_force(ex.what());
|
||||
}
|
||||
}
|
||||
};
|
246
src/input/api/Controller.cpp
Normal file
246
src/input/api/Controller.cpp
Normal file
|
@ -0,0 +1,246 @@
|
|||
#include "input/api/Controller.h"
|
||||
|
||||
#include "gui/guiWrapper.h"
|
||||
|
||||
ControllerBase::ControllerBase(std::string_view uuid, std::string_view display_name)
|
||||
: m_uuid{uuid}, m_display_name{display_name}
|
||||
{
|
||||
}
|
||||
|
||||
const ControllerState& ControllerBase::update_state()
|
||||
{
|
||||
if (!m_is_calibrated)
|
||||
calibrate();
|
||||
|
||||
ControllerState result = raw_state();
|
||||
|
||||
// ignore default buttons
|
||||
result.buttons &= ~m_default_state.buttons;
|
||||
|
||||
// apply deadzone and range and ignore default axis values
|
||||
apply_axis_setting(result.axis, m_default_state.axis, m_settings.axis);
|
||||
apply_axis_setting(result.rotation, m_default_state.rotation, m_settings.rotation);
|
||||
apply_axis_setting(result.trigger, m_default_state.trigger, m_settings.trigger);
|
||||
|
||||
#define APPLY_AXIS_BUTTON(_axis_, _flag_) \
|
||||
if (result._axis_.x < -ControllerState::kAxisThreshold) \
|
||||
result.buttons.set((_flag_) + (kAxisXN - kAxisXP)); \
|
||||
else if (result._axis_.x > ControllerState::kAxisThreshold) \
|
||||
result.buttons.set((_flag_)); \
|
||||
if (result._axis_.y < -ControllerState::kAxisThreshold) \
|
||||
result.buttons.set((_flag_) + 1 + (kAxisXN - kAxisXP)); \
|
||||
else if (result._axis_.y > ControllerState::kAxisThreshold) \
|
||||
result.buttons.set((_flag_) + 1);
|
||||
|
||||
if (result.axis.x < -ControllerState::kAxisThreshold)
|
||||
result.buttons.set((kAxisXP) + (kAxisXN - kAxisXP));
|
||||
else if (result.axis.x > ControllerState::kAxisThreshold)
|
||||
result.buttons.set((kAxisXP));
|
||||
if (result.axis.y < -ControllerState::kAxisThreshold)
|
||||
result.buttons.set((kAxisXP) + 1 + (kAxisXN - kAxisXP));
|
||||
else if (result.axis.y > ControllerState::kAxisThreshold)
|
||||
result.buttons.set((kAxisXP) + 1);;
|
||||
APPLY_AXIS_BUTTON(rotation, kRotationXP);
|
||||
APPLY_AXIS_BUTTON(trigger, kTriggerXP);
|
||||
|
||||
/*
|
||||
// positive values
|
||||
kAxisXP,
|
||||
kAxisYP,
|
||||
|
||||
kRotationXP,
|
||||
kRotationYP,
|
||||
|
||||
kTriggerXP,
|
||||
kTriggerYP,
|
||||
|
||||
// negative values
|
||||
kAxisXN,
|
||||
kAxisYN,
|
||||
|
||||
kRotationXN,
|
||||
kRotationYN,
|
||||
|
||||
kTriggerXN,
|
||||
kTriggerYN,
|
||||
*/
|
||||
|
||||
|
||||
#undef APPLY_AXIS_BUTTON
|
||||
|
||||
m_last_state = result;
|
||||
return m_last_state;
|
||||
}
|
||||
|
||||
void ControllerBase::apply_axis_setting(glm::vec2& axis, const glm::vec2& default_value,
|
||||
const AxisSetting& setting) const
|
||||
{
|
||||
constexpr float kMaxValue = 1.0f + ControllerState::kMinAxisValue;
|
||||
if (setting.deadzone < 1.0f)
|
||||
{
|
||||
if (axis.x < default_value.x)
|
||||
axis.x = (axis.x - default_value.x) / (kMaxValue + default_value.x);
|
||||
else
|
||||
axis.x = (axis.x - default_value.x) / (kMaxValue - default_value.x);
|
||||
|
||||
if (axis.y < default_value.y)
|
||||
axis.y = (axis.y - default_value.y) / (kMaxValue + default_value.y);
|
||||
else
|
||||
axis.y = (axis.y - default_value.y) / (kMaxValue - default_value.y);
|
||||
|
||||
auto len = length(axis);
|
||||
if (len >= setting.deadzone)
|
||||
{
|
||||
axis *= setting.range;
|
||||
len = length(axis);
|
||||
|
||||
// Scaled Radial Dead Zone: stickInput = stickInput.normalized * ((stickInput.magnitude - deadzone) / (1 - deadzone));
|
||||
if (len > 0)
|
||||
{
|
||||
axis = normalize(axis);
|
||||
axis *= ((len - setting.deadzone) / (kMaxValue - setting.deadzone));
|
||||
|
||||
if (length(axis) > 1.0f)
|
||||
axis = normalize(axis);
|
||||
}
|
||||
|
||||
if (axis.x != 0 || axis.y != 0)
|
||||
{
|
||||
if (std::abs(axis.x) < ControllerState::kMinAxisValue)
|
||||
axis.x = ControllerState::kMinAxisValue;
|
||||
|
||||
if (std::abs(axis.y) < ControllerState::kMinAxisValue)
|
||||
axis.y = ControllerState::kMinAxisValue;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
axis = {0, 0};
|
||||
}
|
||||
|
||||
bool ControllerBase::operator==(const ControllerBase& c) const
|
||||
{
|
||||
return api() == c.api() && uuid() == c.uuid();
|
||||
}
|
||||
|
||||
float ControllerBase::get_axis_value(uint64 button) const
|
||||
{
|
||||
if (m_last_state.buttons.test(button))
|
||||
{
|
||||
if (button <= kButtonNoneAxisMAX || !has_axis())
|
||||
return 1.0f;
|
||||
|
||||
switch (button)
|
||||
{
|
||||
case kAxisXP:
|
||||
case kAxisXN:
|
||||
return std::abs(m_last_state.axis.x);
|
||||
case kAxisYP:
|
||||
case kAxisYN:
|
||||
return std::abs(m_last_state.axis.y);
|
||||
|
||||
case kRotationXP:
|
||||
case kRotationXN:
|
||||
return std::abs(m_last_state.rotation.x);
|
||||
case kRotationYP:
|
||||
case kRotationYN:
|
||||
return std::abs(m_last_state.rotation.y);
|
||||
|
||||
case kTriggerXP:
|
||||
case kTriggerXN:
|
||||
return std::abs(m_last_state.trigger.x);
|
||||
case kTriggerYP:
|
||||
case kTriggerYN:
|
||||
return std::abs(m_last_state.trigger.y);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ControllerState& ControllerBase::calibrate()
|
||||
{
|
||||
m_default_state = raw_state();
|
||||
m_is_calibrated = is_connected();
|
||||
return m_default_state;
|
||||
}
|
||||
|
||||
|
||||
std::string ControllerBase::get_button_name(uint64 button) const
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case kButtonZL: return "ZL";
|
||||
case kButtonZR: return "ZR";
|
||||
|
||||
case kButtonUp: return "DPAD-Up";
|
||||
case kButtonDown: return "DPAD-Down";
|
||||
case kButtonLeft: return "DPAD-Left";
|
||||
case kButtonRight: return "DPAD-Right";
|
||||
|
||||
case kAxisXP: return "X-Axis+";
|
||||
case kAxisYP: return "Y-Axis+";
|
||||
|
||||
case kAxisXN: return "X-Axis-";
|
||||
case kAxisYN: return "Y-Axis-";
|
||||
|
||||
case kRotationXP: return "X-Rotation+";
|
||||
case kRotationYP: return "Y-Rotation+";
|
||||
|
||||
case kRotationXN: return "X-Rotation-";
|
||||
case kRotationYN: return "Y-Rotation-";
|
||||
|
||||
case kTriggerXP: return "X-Trigger+";
|
||||
case kTriggerYP: return "Y-Trigger+";
|
||||
|
||||
case kTriggerXN: return "X-Trigger-";
|
||||
case kTriggerYN: return "y-Trigger-";
|
||||
}
|
||||
|
||||
|
||||
return fmt::format("Button {}", (uint64)button);
|
||||
}
|
||||
|
||||
ControllerBase::Settings ControllerBase::get_settings() const
|
||||
{
|
||||
std::scoped_lock lock(m_settings_mutex);
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
void ControllerBase::set_settings(const Settings& settings)
|
||||
{
|
||||
std::scoped_lock lock(m_settings_mutex);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
void ControllerBase::set_axis_settings(const AxisSetting& settings)
|
||||
{
|
||||
std::scoped_lock lock(m_settings_mutex);
|
||||
m_settings.axis = settings;
|
||||
}
|
||||
|
||||
void ControllerBase::set_rotation_settings(const AxisSetting& settings)
|
||||
{
|
||||
std::scoped_lock lock(m_settings_mutex);
|
||||
m_settings.rotation = settings;
|
||||
}
|
||||
|
||||
void ControllerBase::set_trigger_settings(const AxisSetting& settings)
|
||||
{
|
||||
std::scoped_lock lock(m_settings_mutex);
|
||||
m_settings.trigger = settings;
|
||||
}
|
||||
|
||||
void ControllerBase::set_rumble(float rumble)
|
||||
{
|
||||
std::scoped_lock lock(m_settings_mutex);
|
||||
m_settings.rumble = rumble;
|
||||
}
|
||||
|
||||
void ControllerBase::set_use_motion(bool state)
|
||||
{
|
||||
std::scoped_lock lock(m_settings_mutex);
|
||||
m_settings.motion = state;
|
||||
}
|
201
src/input/api/Controller.h
Normal file
201
src/input/api/Controller.h
Normal file
|
@ -0,0 +1,201 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/InputManager.h"
|
||||
#include "input/motion/MotionSample.h"
|
||||
|
||||
namespace pugi
|
||||
{
|
||||
class xml_node;
|
||||
}
|
||||
|
||||
enum Buttons2 : uint64
|
||||
{
|
||||
// General
|
||||
kButton0,
|
||||
kButton1,
|
||||
kButton2,
|
||||
kButton3,
|
||||
kButton4,
|
||||
kButton5,
|
||||
kButton6,
|
||||
kButton7,
|
||||
kButton8,
|
||||
kButton9,
|
||||
kButton10,
|
||||
kButton11,
|
||||
kButton12,
|
||||
kButton13,
|
||||
kButton14,
|
||||
kButton15,
|
||||
kButton16,
|
||||
kButton17,
|
||||
kButton18,
|
||||
kButton19,
|
||||
kButton20,
|
||||
kButton21,
|
||||
kButton22,
|
||||
kButton23,
|
||||
kButton24,
|
||||
kButton25,
|
||||
kButton26,
|
||||
kButton27,
|
||||
kButton28,
|
||||
kButton29,
|
||||
kButton30,
|
||||
kButton31,
|
||||
|
||||
// Trigger
|
||||
kButtonZL,
|
||||
kButtonZR,
|
||||
|
||||
// DPAD
|
||||
kButtonUp,
|
||||
kButtonDown,
|
||||
kButtonLeft,
|
||||
kButtonRight,
|
||||
|
||||
// positive values
|
||||
kAxisXP,
|
||||
kAxisYP,
|
||||
|
||||
kRotationXP,
|
||||
kRotationYP,
|
||||
|
||||
kTriggerXP,
|
||||
kTriggerYP,
|
||||
|
||||
// negative values
|
||||
kAxisXN,
|
||||
kAxisYN,
|
||||
|
||||
kRotationXN,
|
||||
kRotationYN,
|
||||
|
||||
kTriggerXN,
|
||||
kTriggerYN,
|
||||
|
||||
kButtonMAX,
|
||||
|
||||
kButtonNoneAxisMAX = kButtonRight,
|
||||
kButtonAxisStart = kAxisXP,
|
||||
};
|
||||
|
||||
class ControllerBase
|
||||
{
|
||||
public:
|
||||
ControllerBase(std::string_view uuid, std::string_view display_name);
|
||||
virtual ~ControllerBase() = default;
|
||||
|
||||
const std::string& uuid() const { return m_uuid; }
|
||||
const std::string& display_name() const { return m_display_name; }
|
||||
|
||||
virtual std::string_view api_name() const = 0;
|
||||
virtual InputAPI::Type api() const = 0;
|
||||
|
||||
virtual void update() {}
|
||||
|
||||
virtual bool connect() { return is_connected(); }
|
||||
virtual bool is_connected() = 0;
|
||||
|
||||
virtual bool has_battery() { return false; }
|
||||
virtual bool has_low_battery() { return false; }
|
||||
|
||||
const ControllerState& calibrate();
|
||||
const ControllerState& update_state();
|
||||
const ControllerState& get_state() const { return m_last_state; }
|
||||
const ControllerState& get_default_state() { return is_calibrated() ? m_default_state : calibrate(); }
|
||||
virtual ControllerState raw_state() = 0;
|
||||
|
||||
bool is_calibrated() const { return m_is_calibrated; }
|
||||
|
||||
float get_axis_value(uint64 button) const;
|
||||
virtual bool has_axis() const { return true; }
|
||||
|
||||
bool use_motion() { return has_motion() && m_settings.motion; }
|
||||
virtual bool has_motion() { return false; }
|
||||
virtual MotionSample get_motion_sample() { return {}; }
|
||||
|
||||
virtual bool has_position() { return false; }
|
||||
virtual glm::vec2 get_position() { return {}; }
|
||||
virtual glm::vec2 get_prev_position() { return {}; }
|
||||
|
||||
virtual bool has_rumble() { return false; }
|
||||
virtual void start_rumble() {}
|
||||
virtual void stop_rumble() {}
|
||||
|
||||
virtual std::string get_button_name(uint64 button) const;
|
||||
|
||||
virtual void save(pugi::xml_node& node){}
|
||||
virtual void load(const pugi::xml_node& node){}
|
||||
|
||||
struct AxisSetting
|
||||
{
|
||||
AxisSetting(float deadzone = 0.25f) : deadzone(deadzone) {}
|
||||
float deadzone;
|
||||
float range = 1.0f;
|
||||
};
|
||||
struct Settings
|
||||
{
|
||||
AxisSetting axis{}, rotation{}, trigger{};
|
||||
float rumble = 0;
|
||||
bool motion = false; // only valid when has_motion is true
|
||||
};
|
||||
Settings get_settings() const;
|
||||
void set_settings(const Settings& settings);
|
||||
void set_axis_settings(const AxisSetting& settings);
|
||||
void set_rotation_settings(const AxisSetting& settings);
|
||||
void set_trigger_settings(const AxisSetting& settings);
|
||||
void set_rumble(float rumble);
|
||||
void set_use_motion(bool state);
|
||||
|
||||
void apply_axis_setting(glm::vec2& axis, const glm::vec2& default_value, const AxisSetting& setting) const;
|
||||
|
||||
bool operator==(const ControllerBase& c) const;
|
||||
bool operator!=(const ControllerBase& c) const { return !(*this == c); }
|
||||
|
||||
protected:
|
||||
std::string m_uuid;
|
||||
std::string m_display_name;
|
||||
|
||||
ControllerState m_last_state{};
|
||||
|
||||
bool m_is_calibrated = false;
|
||||
ControllerState m_default_state{};
|
||||
|
||||
mutable std::mutex m_settings_mutex;
|
||||
Settings m_settings{};
|
||||
};
|
||||
|
||||
template<class TProvider>
|
||||
class Controller : public ControllerBase
|
||||
{
|
||||
public:
|
||||
Controller(std::string_view uuid, std::string_view display_name)
|
||||
: ControllerBase(uuid, display_name)
|
||||
{
|
||||
static_assert(std::is_base_of_v<ControllerProviderBase, TProvider>);
|
||||
m_provider = std::dynamic_pointer_cast<TProvider>(InputManager::instance().get_api_provider(TProvider::kAPIType));
|
||||
cemu_assert_debug(m_provider != nullptr);
|
||||
}
|
||||
|
||||
Controller(std::string_view uuid, std::string_view display_name, const ControllerProviderSettings& settings)
|
||||
: ControllerBase(uuid, display_name)
|
||||
{
|
||||
static_assert(std::is_base_of_v<ControllerProviderBase, TProvider>);
|
||||
m_provider = std::dynamic_pointer_cast<TProvider>(InputManager::instance().get_api_provider(TProvider::kAPIType, settings));
|
||||
cemu_assert_debug(m_provider != nullptr);
|
||||
}
|
||||
|
||||
// update provider if settings are different from default provider
|
||||
void update_provider(std::shared_ptr<TProvider> provider)
|
||||
{
|
||||
m_provider = provider;
|
||||
}
|
||||
|
||||
protected:
|
||||
using base_type = Controller<TProvider>;
|
||||
std::shared_ptr<TProvider> m_provider;
|
||||
};
|
||||
|
||||
using ControllerPtr = std::shared_ptr<ControllerBase>;
|
||||
|
88
src/input/api/ControllerProvider.h
Normal file
88
src/input/api/ControllerProvider.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/InputAPI.h"
|
||||
|
||||
class ControllerBase;
|
||||
|
||||
struct ControllerProviderSettings
|
||||
{
|
||||
virtual ~ControllerProviderSettings() = default;
|
||||
virtual bool operator==(const ControllerProviderSettings&) const = 0;
|
||||
};
|
||||
|
||||
class ControllerProviderBase
|
||||
{
|
||||
public:
|
||||
ControllerProviderBase() = default;
|
||||
virtual ~ControllerProviderBase() = default;
|
||||
|
||||
virtual InputAPI::Type api() const = 0;
|
||||
std::string_view api_name() const { return to_string(api()); }
|
||||
|
||||
virtual std::vector<std::shared_ptr<ControllerBase>> get_controllers() = 0;
|
||||
|
||||
virtual bool has_settings() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool operator==(const ControllerProviderBase& p) const
|
||||
{
|
||||
return api() == p.api();
|
||||
}
|
||||
virtual bool operator==(const ControllerProviderSettings& p) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
using ControllerProviderPtr = std::shared_ptr<ControllerProviderBase>;
|
||||
|
||||
template <typename TSettings>
|
||||
class ControllerProvider : public ControllerProviderBase
|
||||
{
|
||||
using base_type = ControllerProviderBase;
|
||||
public:
|
||||
ControllerProvider() = default;
|
||||
|
||||
ControllerProvider(const TSettings& settings)
|
||||
: m_settings(settings)
|
||||
{}
|
||||
|
||||
bool has_settings() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const TSettings& get_settings() const
|
||||
{
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
bool operator==(const ControllerProviderBase& p) const override
|
||||
{
|
||||
if (!base_type::operator==(p))
|
||||
return false;
|
||||
|
||||
if (!p.has_settings())
|
||||
return false;
|
||||
|
||||
auto* ptr = dynamic_cast<const ControllerProvider<TSettings>*>(&p);
|
||||
if (!ptr)
|
||||
return false;
|
||||
|
||||
return base_type::operator==(p) && m_settings == ptr->m_settings;
|
||||
}
|
||||
|
||||
bool operator==(const ControllerProviderSettings& p) const override
|
||||
{
|
||||
auto* ptr = dynamic_cast<const TSettings*>(&p);
|
||||
if (!ptr)
|
||||
return false;
|
||||
|
||||
return m_settings == *ptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
TSettings m_settings{};
|
||||
};
|
13
src/input/api/ControllerState.cpp
Normal file
13
src/input/api/ControllerState.cpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include "input/api/ControllerState.h"
|
||||
|
||||
bool ControllerState::operator==(const ControllerState& other) const
|
||||
{
|
||||
return buttons == other.buttons;
|
||||
/*&& (std::signbit(axis.x) == std::signbit(other.axis.x) && std::abs(axis.x - other.axis.x) <= kAxisThreshold)
|
||||
&& (std::signbit(axis.y) == std::signbit(other.axis.y) && std::abs(axis.y - other.axis.y) <= kAxisThreshold)
|
||||
&& (std::signbit(rotation.x) == std::signbit(other.rotation.x) && std::abs(rotation.x - other.rotation.x) <= kAxisThreshold)
|
||||
&& (std::signbit(rotation.y) == std::signbit(other.rotation.y) && std::abs(rotation.y - other.rotation.y) <= kAxisThreshold)
|
||||
&& (std::signbit(trigger.x) == std::signbit(other.trigger.x) && std::abs(trigger.x - other.trigger.x) <= kAxisThreshold)
|
||||
&& (std::signbit(trigger.y) == std::signbit(other.trigger.y) && std::abs(trigger.y - other.trigger.y) <= kAxisThreshold);*/
|
||||
|
||||
}
|
30
src/input/api/ControllerState.h
Normal file
30
src/input/api/ControllerState.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
struct ControllerState
|
||||
{
|
||||
// when does a axis counts as pressed
|
||||
constexpr static float kAxisThreshold = 0.1f;
|
||||
|
||||
// on the real console the stick x or y values never really reach 0.0 if one of the axis is moved
|
||||
// some games rely on this due to incorrectly checking if the stick is tilted via if (vstick.x != 0 && vstick.y != 0)
|
||||
// here we simulate a slight bias if the axis is almost perfectly centered
|
||||
constexpr static float kMinAxisValue = 0.0000001f;
|
||||
|
||||
// [-1; 1]
|
||||
glm::vec2 axis{ };
|
||||
glm::vec2 rotation{ };
|
||||
glm::vec2 trigger{ };
|
||||
|
||||
std::bitset<256> buttons{};
|
||||
|
||||
uint64 last_state = 0;
|
||||
|
||||
bool operator==(const ControllerState& other) const;
|
||||
bool operator!=(const ControllerState& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
168
src/input/api/DSU/DSUController.cpp
Normal file
168
src/input/api/DSU/DSUController.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
#include "input/api/DSU/DSUController.h"
|
||||
|
||||
#include <boost/program_options/value_semantic.hpp>
|
||||
|
||||
DSUController::DSUController(uint32 index)
|
||||
: base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)), m_index(index)
|
||||
{
|
||||
if (index >= DSUControllerProvider::kMaxClients)
|
||||
throw std::runtime_error(fmt::format("max {} dsu controllers are supported! given index: {}",
|
||||
DSUControllerProvider::kMaxClients, index));
|
||||
}
|
||||
|
||||
DSUController::DSUController(uint32 index, const DSUProviderSettings& settings)
|
||||
: base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1), settings), m_index(index)
|
||||
{
|
||||
if (index >= DSUControllerProvider::kMaxClients)
|
||||
throw std::runtime_error(fmt::format("max {} dsu controllers are supported! given index: {}",
|
||||
DSUControllerProvider::kMaxClients, index));
|
||||
}
|
||||
|
||||
void DSUController::save(pugi::xml_node& node)
|
||||
{
|
||||
base_type::save(node);
|
||||
|
||||
node.append_child("ip").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", m_provider->get_settings().ip).c_str());
|
||||
node.append_child("port").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", m_provider->get_settings().port).c_str());
|
||||
}
|
||||
|
||||
void DSUController::load(const pugi::xml_node& node)
|
||||
{
|
||||
base_type::load(node);
|
||||
|
||||
DSUProviderSettings settings;
|
||||
if (const auto value = node.child("ip"))
|
||||
settings.ip = value.child_value();
|
||||
if (const auto value = node.child("port"))
|
||||
settings.port = ConvertString<uint16>(value.child_value());
|
||||
|
||||
const auto provider = InputManager::instance().get_api_provider(api(), settings);
|
||||
update_provider(std::dynamic_pointer_cast<DSUControllerProvider>(provider));
|
||||
connect();
|
||||
}
|
||||
|
||||
bool DSUController::connect()
|
||||
{
|
||||
if (is_connected())
|
||||
return true;
|
||||
|
||||
m_provider->request_pad_data(m_index);
|
||||
return is_connected();
|
||||
}
|
||||
|
||||
bool DSUController::is_connected()
|
||||
{
|
||||
return m_provider->is_connected(m_index);
|
||||
}
|
||||
|
||||
MotionSample DSUController::get_motion_sample()
|
||||
{
|
||||
return m_provider->get_motion_sample(m_index);
|
||||
}
|
||||
|
||||
bool DSUController::has_position()
|
||||
{
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
return state.data.tpad1.active || state.data.tpad2.active;
|
||||
}
|
||||
|
||||
glm::vec2 DSUController::get_position()
|
||||
{
|
||||
// touchpad resolution is 1920x942
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
if (state.data.tpad1.active)
|
||||
return glm::vec2{(float)state.data.tpad1.x / 1920.0f, (float)state.data.tpad1.y / 942.0f};
|
||||
|
||||
if (state.data.tpad2.active)
|
||||
return glm::vec2{(float)state.data.tpad2.x / 1920.0f, (float)state.data.tpad2.y / 942.0f};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
glm::vec2 DSUController::get_prev_position()
|
||||
{
|
||||
const auto state = m_provider->get_prev_state(m_index);
|
||||
if (state.data.tpad1.active)
|
||||
return glm::vec2{(float)state.data.tpad1.x / 1920.0f, (float)state.data.tpad1.y / 942.0f};
|
||||
|
||||
if (state.data.tpad2.active)
|
||||
return glm::vec2{(float)state.data.tpad2.x / 1920.0f, (float)state.data.tpad2.y / 942.0f};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string DSUController::get_button_name(uint64 button) const
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case kButton0: return "Share";
|
||||
case kButton1: return "Stick L";
|
||||
case kButton2: return "Stick R";
|
||||
case kButton3: return "Options";
|
||||
case kButton4: return "Up";
|
||||
case kButton5: return "Right";
|
||||
case kButton6: return "Down";
|
||||
case kButton7: return "Left";
|
||||
|
||||
case kButton8: return "ZL";
|
||||
case kButton9: return "ZR";
|
||||
case kButton10: return "L";
|
||||
case kButton11: return "R";
|
||||
case kButton12: return "Triangle";
|
||||
case kButton13: return "Circle";
|
||||
case kButton14: return "Cross";
|
||||
case kButton15: return "Square";
|
||||
|
||||
case kButton16: return "Touch";
|
||||
}
|
||||
return base_type::get_button_name(button);
|
||||
}
|
||||
|
||||
ControllerState DSUController::raw_state()
|
||||
{
|
||||
ControllerState result{};
|
||||
|
||||
if (!is_connected())
|
||||
return result;
|
||||
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
// didn't read any data from the controller yet
|
||||
if (state.info.state != DsState::Connected)
|
||||
return result;
|
||||
|
||||
int bitindex = 0;
|
||||
for (int i = 0; i < 8; ++i, ++bitindex)
|
||||
{
|
||||
if (HAS_BIT(state.data.state1, i))
|
||||
{
|
||||
result.buttons.set(bitindex);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; ++i, ++bitindex)
|
||||
{
|
||||
if (HAS_BIT(state.data.state2, i))
|
||||
{
|
||||
result.buttons.set(bitindex);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.data.touch)
|
||||
result.buttons.set(kButton16);
|
||||
|
||||
result.axis.x = (float)state.data.lx / std::numeric_limits<uint8>::max();
|
||||
result.axis.x = (result.axis.x * 2.0f) - 1.0f;
|
||||
|
||||
result.axis.y = (float)state.data.ly / std::numeric_limits<uint8>::max();
|
||||
result.axis.y = (result.axis.y * 2.0f) - 1.0f;
|
||||
|
||||
result.rotation.x = (float)state.data.rx / std::numeric_limits<uint8>::max();
|
||||
result.rotation.x = (result.rotation.x * 2.0f) - 1.0f;
|
||||
|
||||
result.rotation.y = (float)state.data.ry / std::numeric_limits<uint8>::max();
|
||||
result.rotation.y = (result.rotation.y * 2.0f) - 1.0f;
|
||||
|
||||
return result;
|
||||
}
|
44
src/input/api/DSU/DSUController.h
Normal file
44
src/input/api/DSU/DSUController.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/DSU/DSUControllerProvider.h"
|
||||
#include "Cafe/HW/AI/AI.h"
|
||||
#include "Cafe/HW/AI/AI.h"
|
||||
#include "Cafe/HW/AI/AI.h"
|
||||
#include "Cafe/HW/AI/AI.h"
|
||||
|
||||
class DSUController : public Controller<DSUControllerProvider>
|
||||
{
|
||||
public:
|
||||
DSUController(uint32 index);
|
||||
DSUController(uint32 index, const DSUProviderSettings& settings);
|
||||
|
||||
std::string_view api_name() const override
|
||||
{
|
||||
static_assert(to_string(InputAPI::DSUClient) == "DSUController");
|
||||
return to_string(InputAPI::DSUClient);
|
||||
}
|
||||
InputAPI::Type api() const override { return InputAPI::DSUClient; }
|
||||
|
||||
void save(pugi::xml_node& node) override;
|
||||
void load(const pugi::xml_node& node) override;
|
||||
|
||||
bool connect() override;
|
||||
bool is_connected() override;
|
||||
|
||||
bool has_motion() override { return true; }
|
||||
MotionSample get_motion_sample() override;
|
||||
|
||||
bool has_position() override;
|
||||
glm::vec2 get_position() override;
|
||||
glm::vec2 get_prev_position() override;
|
||||
|
||||
std::string get_button_name(uint64 button) const override;
|
||||
|
||||
protected:
|
||||
ControllerState raw_state() override;
|
||||
|
||||
private:
|
||||
uint32 m_index;
|
||||
};
|
||||
|
448
src/input/api/DSU/DSUControllerProvider.cpp
Normal file
448
src/input/api/DSU/DSUControllerProvider.cpp
Normal file
|
@ -0,0 +1,448 @@
|
|||
#include "input/api/DSU/DSUControllerProvider.h"
|
||||
#include "input/api/DSU/DSUController.h"
|
||||
|
||||
DSUControllerProvider::DSUControllerProvider()
|
||||
: base_type(), m_uid(rand()), m_socket(m_io_service)
|
||||
{
|
||||
if (!connect())
|
||||
{
|
||||
throw std::runtime_error("dsu client can't open the udp connection");
|
||||
}
|
||||
|
||||
m_running = true;
|
||||
m_reader_thread = std::thread(&DSUControllerProvider::reader_thread, this);
|
||||
m_writer_thread = std::thread(&DSUControllerProvider::writer_thread, this);
|
||||
request_version();
|
||||
}
|
||||
|
||||
DSUControllerProvider::DSUControllerProvider(const DSUProviderSettings& settings)
|
||||
: base_type(settings), m_uid(rand()), m_socket(m_io_service)
|
||||
{
|
||||
if (!connect())
|
||||
{
|
||||
throw std::runtime_error("dsu client can't open the udp connection");
|
||||
}
|
||||
|
||||
m_running = true;
|
||||
m_reader_thread = std::thread(&DSUControllerProvider::reader_thread, this);
|
||||
m_writer_thread = std::thread(&DSUControllerProvider::writer_thread, this);
|
||||
request_version();
|
||||
}
|
||||
|
||||
DSUControllerProvider::~DSUControllerProvider()
|
||||
{
|
||||
if (m_running)
|
||||
{
|
||||
m_running = false;
|
||||
m_writer_thread.join();
|
||||
m_reader_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> DSUControllerProvider::get_controllers()
|
||||
{
|
||||
std::vector<ControllerPtr> result;
|
||||
|
||||
std::array<uint8_t, kMaxClients> indices;
|
||||
for (auto i = 0; i < kMaxClients; ++i)
|
||||
indices[i] = get_packet_index(i);
|
||||
|
||||
request_pad_info();
|
||||
|
||||
const auto controller_result = wait_update(indices, 3000);
|
||||
for (auto i = 0; i < kMaxClients; ++i)
|
||||
{
|
||||
if (controller_result[i] && is_connected(i))
|
||||
result.emplace_back(std::make_shared<DSUController>(i, m_settings));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DSUControllerProvider::connect()
|
||||
{
|
||||
// already connected?
|
||||
if (m_receiver_endpoint.address().to_string() == get_settings().ip && m_receiver_endpoint.port() == get_settings().port)
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
using namespace boost::asio;
|
||||
|
||||
ip::udp::resolver resolver(m_io_service);
|
||||
const ip::udp::resolver::query query(ip::udp::v4(), get_settings().ip, fmt::format("{}", get_settings().port));
|
||||
m_receiver_endpoint = *resolver.resolve(query);
|
||||
|
||||
if (m_socket.is_open())
|
||||
m_socket.close();
|
||||
|
||||
m_socket.open(ip::udp::v4());
|
||||
// set timeout for our threads to give a chance to exit
|
||||
m_socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{200});
|
||||
|
||||
// reset data
|
||||
m_state = {};
|
||||
m_prev_state = {};
|
||||
|
||||
// restart threads
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
forceLog_printf("dsu client connect error: %s", ex.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DSUControllerProvider::is_connected(uint8_t index) const
|
||||
{
|
||||
if (index >= kMaxClients)
|
||||
return false;
|
||||
|
||||
std::scoped_lock lock(m_mutex[index]);
|
||||
return m_state[index].info.state == DsState::Connected;
|
||||
}
|
||||
|
||||
DSUControllerProvider::ControllerState DSUControllerProvider::get_state(uint8_t index) const
|
||||
{
|
||||
if (index >= kMaxClients)
|
||||
return {};
|
||||
|
||||
std::scoped_lock lock(m_mutex[index]);
|
||||
return m_state[index];
|
||||
}
|
||||
|
||||
DSUControllerProvider::ControllerState DSUControllerProvider::get_prev_state(uint8_t index) const
|
||||
{
|
||||
if (index >= kMaxClients)
|
||||
return {};
|
||||
|
||||
std::scoped_lock lock(m_mutex[index]);
|
||||
return m_prev_state[index];
|
||||
}
|
||||
|
||||
std::array<bool, DSUControllerProvider::kMaxClients> DSUControllerProvider::wait_update(
|
||||
const std::array<uint8_t, kMaxClients>& indices, size_t timeout) const
|
||||
{
|
||||
std::array<bool, kMaxClients> result{false, false, false, false};
|
||||
|
||||
const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout);
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < kMaxClients; ++i)
|
||||
{
|
||||
if (result[i])
|
||||
continue;
|
||||
|
||||
std::unique_lock lock(m_mutex[i]);
|
||||
result[i] = indices[i] < m_state[i].packet_index;
|
||||
}
|
||||
|
||||
if (std::all_of(result.cbegin(), result.cend(), [](const bool& v) { return v == true; }))
|
||||
break;
|
||||
|
||||
//std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
std::this_thread::yield();
|
||||
}
|
||||
while (std::chrono::steady_clock::now() < end);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DSUControllerProvider::wait_update(uint8_t index, uint32_t packet_index, size_t timeout) const
|
||||
{
|
||||
if (index >= kMaxClients)
|
||||
return false;
|
||||
|
||||
std::unique_lock lock(m_mutex[index]);
|
||||
|
||||
if (packet_index < m_state[index].packet_index)
|
||||
return true;
|
||||
|
||||
const auto result = m_wait_cond[index].wait_for(lock, std::chrono::milliseconds(timeout),
|
||||
[this, index, packet_index]()
|
||||
{
|
||||
return packet_index < m_state[index].packet_index;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t DSUControllerProvider::get_packet_index(uint8_t index) const
|
||||
{
|
||||
std::scoped_lock lock(m_mutex[index]);
|
||||
return m_state[index].packet_index;
|
||||
}
|
||||
|
||||
void DSUControllerProvider::request_version()
|
||||
{
|
||||
auto msg = std::make_unique<VersionRequest>(m_uid);
|
||||
|
||||
std::scoped_lock lock(m_writer_mutex);
|
||||
m_writer_jobs.push(std::move(msg));
|
||||
m_writer_cond.notify_one();
|
||||
}
|
||||
|
||||
void DSUControllerProvider::request_pad_info()
|
||||
{
|
||||
auto msg = std::make_unique<ListPorts>(m_uid, 4, std::array<uint8_t, 4>{0, 1, 2, 3});
|
||||
|
||||
std::scoped_lock lock(m_writer_mutex);
|
||||
m_writer_jobs.push(std::move(msg));
|
||||
m_writer_cond.notify_one();
|
||||
}
|
||||
|
||||
void DSUControllerProvider::request_pad_info(uint8_t index)
|
||||
{
|
||||
if (index >= kMaxClients)
|
||||
return;
|
||||
|
||||
auto msg = std::make_unique<ListPorts>(m_uid, 1, std::array<uint8_t, 4>{index});
|
||||
|
||||
std::scoped_lock lock(m_writer_mutex);
|
||||
m_writer_jobs.push(std::move(msg));
|
||||
m_writer_cond.notify_one();
|
||||
}
|
||||
|
||||
void DSUControllerProvider::request_pad_data()
|
||||
{
|
||||
auto msg = std::make_unique<DataRequest>(m_uid);
|
||||
|
||||
std::scoped_lock lock(m_writer_mutex);
|
||||
m_writer_jobs.push(std::move(msg));
|
||||
m_writer_cond.notify_one();
|
||||
}
|
||||
|
||||
void DSUControllerProvider::request_pad_data(uint8_t index)
|
||||
{
|
||||
if (index >= kMaxClients)
|
||||
return;
|
||||
|
||||
auto msg = std::make_unique<DataRequest>(m_uid, index);
|
||||
|
||||
std::scoped_lock lock(m_writer_mutex);
|
||||
m_writer_jobs.push(std::move(msg));
|
||||
m_writer_cond.notify_one();
|
||||
}
|
||||
|
||||
MotionSample DSUControllerProvider::get_motion_sample(uint8_t index) const
|
||||
{
|
||||
if (index >= kMaxClients)
|
||||
return MotionSample();
|
||||
std::scoped_lock lock(m_mutex[index]);
|
||||
return m_state[index].motion_sample;
|
||||
}
|
||||
|
||||
|
||||
void DSUControllerProvider::reader_thread()
|
||||
{
|
||||
SetThreadName("DSUControllerProvider::reader_thread");
|
||||
bool first_read = true;
|
||||
while (m_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
ServerMessage* msg;
|
||||
//try
|
||||
//{
|
||||
std::array<char, 100> recv_buf; // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init)
|
||||
boost::asio::ip::udp::endpoint sender_endpoint;
|
||||
boost::system::error_code ec{};
|
||||
const size_t len = m_socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint, 0, ec);
|
||||
if (ec)
|
||||
{
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::ReaderThread: exception %s\n", ex.what());
|
||||
#endif
|
||||
|
||||
// there's probably no server listening on the given address:port
|
||||
if (first_read) // workaroud: first read always fails?
|
||||
first_read = false;
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
std::this_thread::yield();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::ReaderThread: received message with len: 0x%llx\n", len);
|
||||
#endif
|
||||
|
||||
if (len < sizeof(ServerMessage)) // cant be a valid message
|
||||
continue;
|
||||
|
||||
msg = (ServerMessage*)recv_buf.data();
|
||||
// }
|
||||
// catch (const std::exception&)
|
||||
// {
|
||||
//#ifdef DEBUG_DSU_CLIENT
|
||||
// printf(" DSUControllerProvider::ReaderThread: exception %s\n", ex.what());
|
||||
//#endif
|
||||
//
|
||||
// // there's probably no server listening on the given address:port
|
||||
// if (first_read) // workaroud: first read always fails?
|
||||
// first_read = false;
|
||||
// else
|
||||
// {
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
// std::this_thread::yield();
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
|
||||
uint8_t index = 0xFF;
|
||||
switch (msg->GetMessageType())
|
||||
{
|
||||
case MessageType::Version:
|
||||
{
|
||||
const auto rsp = (VersionResponse*)msg;
|
||||
if (!rsp->IsValid())
|
||||
{
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::ReaderThread: VersionResponse is invalid!\n");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::ReaderThread: server version is: 0x%x\n", rsp->GetVersion());
|
||||
#endif
|
||||
|
||||
m_server_version = rsp->GetVersion();
|
||||
// wdc
|
||||
break;
|
||||
}
|
||||
case MessageType::Information:
|
||||
{
|
||||
const auto info = (PortInfo*)msg;
|
||||
if (!info->IsValid())
|
||||
{
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::ReaderThread: PortInfo is invalid!\n");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
index = info->GetIndex();
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::ReaderThread: received PortInfo for index %d\n", index);
|
||||
#endif
|
||||
|
||||
auto& mutex = m_mutex[index];
|
||||
std::scoped_lock lock(mutex);
|
||||
m_prev_state[index] = m_state[index];
|
||||
m_state[index] = *info;
|
||||
m_wait_cond[index].notify_all();
|
||||
break;
|
||||
}
|
||||
case MessageType::Data:
|
||||
{
|
||||
const auto rsp = (DataResponse*)msg;
|
||||
if (!rsp->IsValid())
|
||||
{
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::ReaderThread: DataResponse is invalid!\n");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
index = rsp->GetIndex();
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::ReaderThread: received DataResponse for index %d\n", index);
|
||||
#endif
|
||||
|
||||
auto& mutex = m_mutex[index];
|
||||
std::scoped_lock lock(mutex);
|
||||
m_prev_state[index] = m_state[index];
|
||||
m_state[index] = *rsp;
|
||||
m_wait_cond[index].notify_all();
|
||||
// update motion info immediately, guaranteeing that we dont drop packets
|
||||
integrate_motion(index, *rsp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index != 0xFF)
|
||||
request_pad_data(index);
|
||||
}
|
||||
}
|
||||
|
||||
void DSUControllerProvider::writer_thread()
|
||||
{
|
||||
SetThreadName("DSUControllerProvider::writer_thread");
|
||||
while (m_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
std::unique_lock lock(m_writer_mutex);
|
||||
while (m_writer_jobs.empty())
|
||||
{
|
||||
if (m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout)
|
||||
{
|
||||
if (!m_running.load(std::memory_order_relaxed))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto msg = std::move(m_writer_jobs.front());
|
||||
m_writer_jobs.pop();
|
||||
lock.unlock();
|
||||
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::WriterThread: sending message: 0x%x (len: 0x%x)\n", (int)msg->GetMessageType(), msg->GetSize());
|
||||
#endif
|
||||
try
|
||||
{
|
||||
m_socket.send_to(boost::asio::buffer(msg.get(), msg->GetSize()), m_receiver_endpoint);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
#ifdef DEBUG_DSU_CLIENT
|
||||
printf(" DSUControllerProvider::WriterThread: exception %s\n", ex.what());
|
||||
#endif
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DSUControllerProvider::integrate_motion(uint8_t index, const DataResponse& data_response)
|
||||
{
|
||||
const uint64 ts = data_response.GetMotionTimestamp();
|
||||
if (ts <= m_last_motion_timestamp[index])
|
||||
{
|
||||
const uint64 dif = m_last_motion_timestamp[index] - ts;
|
||||
if (dif >= 10000000) // timestamp more than 10 seconds in the past, a controller reset probably happened
|
||||
m_last_motion_timestamp[index] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64 elapsedTime = ts - m_last_motion_timestamp[index];
|
||||
m_last_motion_timestamp[index] = ts;
|
||||
const double elapsedTimeD = (double)elapsedTime / 1000000.0;
|
||||
const auto& acc = data_response.GetAcceleration();
|
||||
const auto& gyro = data_response.GetGyro();
|
||||
|
||||
m_motion_handler[index].processMotionSample((float)elapsedTimeD,
|
||||
gyro.x * 0.0174533f,
|
||||
gyro.y * 0.0174533f,
|
||||
gyro.z * 0.0174533f,
|
||||
acc.x,
|
||||
-acc.y,
|
||||
-acc.z);
|
||||
|
||||
m_state[index].motion_sample = m_motion_handler[index].getMotionSample();
|
||||
}
|
||||
|
||||
DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::operator=(const PortInfo& port_info)
|
||||
{
|
||||
info = port_info.GetInfo();
|
||||
last_update = std::chrono::steady_clock::now();
|
||||
packet_index++; // increase packet index for every packet we assign/recv
|
||||
return *this;
|
||||
}
|
||||
|
||||
DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::operator=(
|
||||
const DataResponse& data_response)
|
||||
{
|
||||
this->operator=(static_cast<const PortInfo&>(data_response));
|
||||
data = data_response.GetData();
|
||||
return *this;
|
||||
}
|
118
src/input/api/DSU/DSUControllerProvider.h
Normal file
118
src/input/api/DSU/DSUControllerProvider.h
Normal file
|
@ -0,0 +1,118 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/motion/MotionHandler.h"
|
||||
#include "input/api/DSU/DSUMessages.h"
|
||||
|
||||
#include "input/api/ControllerProvider.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#ifndef HAS_DSU
|
||||
#define HAS_DSU 1
|
||||
#endif
|
||||
|
||||
// #define DEBUG_DSU_CLIENT
|
||||
|
||||
struct DSUProviderSettings : public ControllerProviderSettings
|
||||
{
|
||||
std::string ip;
|
||||
uint16 port;
|
||||
|
||||
DSUProviderSettings() : ip("127.0.0.1"), port(26760) {}
|
||||
|
||||
DSUProviderSettings(std::string ip, uint16 port)
|
||||
: ip(std::move(ip)), port(port)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const DSUProviderSettings& s) const
|
||||
{
|
||||
return port == s.port && ip == s.ip;
|
||||
}
|
||||
|
||||
bool operator==(const ControllerProviderSettings& s) const override
|
||||
{
|
||||
const auto* ptr = dynamic_cast<const DSUProviderSettings*>(&s);
|
||||
return ptr && *this == *ptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class DSUControllerProvider : public ControllerProvider<DSUProviderSettings>
|
||||
{
|
||||
friend class DSUController;
|
||||
using base_type = ControllerProvider<DSUProviderSettings>;
|
||||
public:
|
||||
constexpr static int kMaxClients = 8;
|
||||
|
||||
struct ControllerState
|
||||
{
|
||||
// when was info updated last time
|
||||
std::chrono::steady_clock::time_point last_update{};
|
||||
uint64_t packet_index = 0; // our packet index count
|
||||
|
||||
PortInfoData info{};
|
||||
DataResponseData data{};
|
||||
MotionSample motion_sample{};
|
||||
|
||||
ControllerState& operator=(const PortInfo& port_info);
|
||||
ControllerState& operator=(const DataResponse& data_response);
|
||||
};
|
||||
|
||||
DSUControllerProvider();
|
||||
DSUControllerProvider(const DSUProviderSettings& settings);
|
||||
~DSUControllerProvider();
|
||||
|
||||
inline static InputAPI::Type kAPIType = InputAPI::DSUClient;
|
||||
InputAPI::Type api() const override { return kAPIType; }
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
|
||||
|
||||
bool connect();
|
||||
|
||||
bool is_connected(uint8_t index) const;
|
||||
ControllerState get_state(uint8_t index) const;
|
||||
ControllerState get_prev_state(uint8_t index) const;
|
||||
MotionSample get_motion_sample(uint8_t index) const;
|
||||
|
||||
std::array<bool, kMaxClients> wait_update(const std::array<uint8_t, kMaxClients>& indices, size_t timeout) const;
|
||||
bool wait_update(uint8_t index, uint32_t packet_index, size_t timeout) const;
|
||||
|
||||
uint32_t get_packet_index(uint8_t index) const;
|
||||
// refresh pad info for all pads
|
||||
void request_pad_info();
|
||||
// refresh pad info for pad with given index
|
||||
void request_pad_info(uint8_t index);
|
||||
void request_version();
|
||||
void request_pad_data();
|
||||
void request_pad_data(uint8_t index);
|
||||
|
||||
|
||||
private:
|
||||
uint16 m_server_version = 0;
|
||||
|
||||
std::atomic_bool m_running = false;
|
||||
std::thread m_reader_thread, m_writer_thread;
|
||||
|
||||
void reader_thread();
|
||||
void writer_thread();
|
||||
void integrate_motion(uint8_t index, const DataResponse& data_response);
|
||||
|
||||
std::mutex m_writer_mutex;
|
||||
std::condition_variable m_writer_cond;
|
||||
|
||||
uint32 m_uid;
|
||||
boost::asio::io_service m_io_service;
|
||||
boost::asio::ip::udp::endpoint m_receiver_endpoint;
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
|
||||
std::array<ControllerState, kMaxClients> m_state{};
|
||||
std::array<ControllerState, kMaxClients> m_prev_state{};
|
||||
mutable std::array<std::mutex, kMaxClients> m_mutex;
|
||||
mutable std::array<std::condition_variable, kMaxClients> m_wait_cond;
|
||||
|
||||
std::queue<std::unique_ptr<ClientMessage>> m_writer_jobs;
|
||||
|
||||
std::array<WiiUMotionHandler, kMaxClients> m_motion_handler;
|
||||
std::array<uint64, kMaxClients> m_last_motion_timestamp{};
|
||||
};
|
97
src/input/api/DSU/DSUMessages.cpp
Normal file
97
src/input/api/DSU/DSUMessages.cpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
#include "input/api/DSU/DSUMessages.h"
|
||||
|
||||
#include <boost/crc.hpp>
|
||||
|
||||
constexpr uint32_t kMagicClient = 'CUSD';
|
||||
constexpr uint32_t kMagicServer = 'SUSD';
|
||||
constexpr uint16_t kProtocolVersion = 1001;
|
||||
|
||||
MessageHeader::MessageHeader(uint32_t magic, uint32_t uid)
|
||||
: m_magic(magic), m_protocol_version(kProtocolVersion), m_uid(uid) { }
|
||||
|
||||
void MessageHeader::Finalize(size_t size)
|
||||
{
|
||||
m_packet_size = (uint16_t)(size - sizeof(MessageHeader));
|
||||
m_crc32 = CRC32(size);
|
||||
}
|
||||
|
||||
uint32_t MessageHeader::CRC32(size_t size) const
|
||||
{
|
||||
const auto tmp = m_crc32;
|
||||
m_crc32 = 0;
|
||||
|
||||
boost::crc_32_type crc;
|
||||
crc.process_bytes(this, size);
|
||||
const auto result = crc.checksum();
|
||||
|
||||
m_crc32 = tmp;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MessageHeader::IsClientMessage() const { return m_magic == kMagicClient; }
|
||||
bool MessageHeader::IsServerMessage() const { return m_magic == kMagicServer; }
|
||||
|
||||
Message::Message(uint32_t magic, uint32_t uid, MessageType type)
|
||||
: MessageHeader(magic, uid), m_message_type(type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ClientMessage::ClientMessage(uint32_t uid, MessageType message_type)
|
||||
: Message(kMagicClient, uid, message_type) { }
|
||||
|
||||
VersionRequest::VersionRequest(uint32_t uid)
|
||||
: ClientMessage(uid, MessageType::Version)
|
||||
{
|
||||
Finalize(sizeof(VersionRequest));
|
||||
}
|
||||
|
||||
ListPorts::ListPorts(uint32_t uid, uint32_t num_pads_requests, const std::array<uint8_t, 4>& request_indices)
|
||||
: ClientMessage(uid, MessageType::Information), m_count(num_pads_requests), m_indices(request_indices)
|
||||
{
|
||||
Finalize(sizeof(ListPorts));
|
||||
}
|
||||
|
||||
DataRequest::DataRequest(uint32_t uid)
|
||||
: ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::AllPads), m_index(0), m_mac_address({})
|
||||
{
|
||||
Finalize(sizeof(DataRequest));
|
||||
}
|
||||
|
||||
DataRequest::DataRequest(uint32_t uid, uint8_t index)
|
||||
: ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::Index), m_index(index), m_mac_address({})
|
||||
{
|
||||
Finalize(sizeof(DataRequest));
|
||||
}
|
||||
|
||||
DataRequest::DataRequest(uint32_t uid, const MACAddress_t& mac_address)
|
||||
: ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::MACAddress), m_index(0), m_mac_address(mac_address)
|
||||
{
|
||||
Finalize(sizeof(DataRequest));
|
||||
}
|
||||
|
||||
DataRequest::DataRequest(uint32_t uid, uint8_t index, const MACAddress_t& mac_address)
|
||||
: ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::Index | RegisterFlag::MACAddress), m_index(index), m_mac_address(mac_address)
|
||||
{
|
||||
Finalize(sizeof(DataRequest));
|
||||
}
|
||||
|
||||
bool ServerMessage::ValidateCRC32(size_t size) const
|
||||
{
|
||||
return GetCRC32() == CRC32(size);
|
||||
}
|
||||
|
||||
bool VersionResponse::IsValid() const
|
||||
{
|
||||
return ValidateCRC32(sizeof(VersionResponse));
|
||||
}
|
||||
|
||||
bool PortInfo::IsValid() const
|
||||
{
|
||||
return ValidateCRC32(sizeof(PortInfo));
|
||||
}
|
||||
|
||||
bool DataResponse::IsValid() const
|
||||
{
|
||||
return ValidateCRC32(sizeof(DataResponse));
|
||||
}
|
275
src/input/api/DSU/DSUMessages.h
Normal file
275
src/input/api/DSU/DSUMessages.h
Normal file
|
@ -0,0 +1,275 @@
|
|||
#pragma once
|
||||
|
||||
// https://v1993.github.io/cemuhook-protocol/
|
||||
|
||||
#include "Common/enumFlags.h"
|
||||
#include "util/math/vector3.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
enum class DsState : uint8_t
|
||||
{
|
||||
Disconnected = 0x00,
|
||||
Reserved = 0x01,
|
||||
Connected = 0x02
|
||||
};
|
||||
|
||||
enum class DsConnection : uint8_t
|
||||
{
|
||||
None = 0x00,
|
||||
Usb = 0x01,
|
||||
Bluetooth = 0x02
|
||||
};
|
||||
|
||||
enum class DsModel : uint8_t
|
||||
{
|
||||
None = 0,
|
||||
DS3 = 1,
|
||||
DS4 = 2,
|
||||
Generic = 3
|
||||
};
|
||||
|
||||
enum class DsBattery : uint8_t
|
||||
{
|
||||
None = 0x00,
|
||||
Dying = 0x01,
|
||||
Low = 0x02,
|
||||
Medium = 0x03,
|
||||
High = 0x04,
|
||||
Full = 0x05,
|
||||
Charging = 0xEE,
|
||||
Charged = 0xEF
|
||||
};
|
||||
|
||||
enum class RegisterFlag : uint8_t
|
||||
{
|
||||
AllPads = 0x00,
|
||||
Index = 0x01,
|
||||
MACAddress = 0x02
|
||||
};
|
||||
ENABLE_BITMASK_OPERATORS(RegisterFlag);
|
||||
|
||||
enum class MessageType : uint32_t
|
||||
{
|
||||
Version = 0x100000,
|
||||
Information = 0x100001,
|
||||
Data = 0x100002,
|
||||
Rumble = 0x100003, // TODO
|
||||
};
|
||||
|
||||
using MACAddress_t = std::array<uint8_t, 6>;
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
class MessageHeader
|
||||
{
|
||||
public:
|
||||
MessageHeader(uint32_t magic, uint32_t uid);
|
||||
|
||||
[[nodiscard]] uint16_t GetSize() const { return sizeof(MessageHeader) + m_packet_size; }
|
||||
[[nodiscard]] bool IsClientMessage() const;
|
||||
[[nodiscard]] bool IsServerMessage() const;
|
||||
[[nodiscard]] uint32_t GetCRC32() const { return m_crc32; }
|
||||
|
||||
protected:
|
||||
void Finalize(size_t size);
|
||||
|
||||
[[nodiscard]] uint32_t CRC32(size_t size) const;
|
||||
private:
|
||||
uint32_t m_magic;
|
||||
uint16_t m_protocol_version;
|
||||
uint16_t m_packet_size = 0;
|
||||
mutable uint32_t m_crc32 = 0;
|
||||
uint32_t m_uid;
|
||||
};
|
||||
static_assert(sizeof(MessageHeader) == 0x10);
|
||||
|
||||
class Message : public MessageHeader
|
||||
{
|
||||
public:
|
||||
Message(uint32_t magic, uint32_t uid, MessageType type);
|
||||
|
||||
[[nodiscard]] MessageType GetMessageType() const { return m_message_type; }
|
||||
private:
|
||||
MessageType m_message_type;
|
||||
};
|
||||
static_assert(sizeof(Message) == 0x14);
|
||||
|
||||
// client messages
|
||||
class ClientMessage : public Message
|
||||
{
|
||||
public:
|
||||
ClientMessage(uint32_t uid, MessageType message_type);
|
||||
};
|
||||
static_assert(sizeof(ClientMessage) == sizeof(Message));
|
||||
|
||||
class VersionRequest : public ClientMessage
|
||||
{
|
||||
public:
|
||||
VersionRequest(uint32_t uid);
|
||||
};
|
||||
static_assert(sizeof(VersionRequest) == sizeof(ClientMessage));
|
||||
|
||||
class ListPorts : public ClientMessage
|
||||
{
|
||||
public:
|
||||
ListPorts(uint32_t uid, uint32_t num_pads_requests, const std::array<uint8_t, 4>& request_indices);
|
||||
|
||||
private:
|
||||
uint32_t m_count;
|
||||
std::array<uint8_t, 4> m_indices;
|
||||
};
|
||||
|
||||
class DataRequest : public ClientMessage
|
||||
{
|
||||
public:
|
||||
DataRequest(uint32_t uid);
|
||||
DataRequest(uint32_t uid, uint8_t index);
|
||||
DataRequest(uint32_t uid, const MACAddress_t& mac_address);
|
||||
DataRequest(uint32_t uid, uint8_t index, const MACAddress_t& mac_address);
|
||||
|
||||
private:
|
||||
RegisterFlag m_reg_flags;
|
||||
uint8_t m_index;
|
||||
MACAddress_t m_mac_address;
|
||||
};
|
||||
|
||||
// server messages
|
||||
class ServerMessage : public Message
|
||||
{
|
||||
public:
|
||||
ServerMessage() = delete;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool ValidateCRC32(size_t size) const;
|
||||
};
|
||||
|
||||
class VersionResponse : public ServerMessage
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] bool IsValid() const;
|
||||
[[nodiscard]] uint16_t GetVersion() const { return m_version; }
|
||||
|
||||
private:
|
||||
uint16_t m_version;
|
||||
uint8_t padding[2];
|
||||
};
|
||||
static_assert(sizeof(VersionResponse) == 0x18);
|
||||
|
||||
struct PortInfoData
|
||||
{
|
||||
uint8_t index;
|
||||
DsState state;
|
||||
DsModel model;
|
||||
DsConnection connection;
|
||||
MACAddress_t mac_address;
|
||||
DsBattery battery;
|
||||
uint8_t is_active;
|
||||
};
|
||||
|
||||
class PortInfo : public ServerMessage
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] bool IsValid() const;
|
||||
|
||||
[[nodiscard]] const PortInfoData& GetInfo() const { return m_info; }
|
||||
|
||||
[[nodiscard]] uint8_t GetIndex() const { return m_info.index; }
|
||||
[[nodiscard]] DsState GetState() const { return m_info.state; }
|
||||
[[nodiscard]] DsModel GetModel() const { return m_info.model; }
|
||||
[[nodiscard]] DsConnection GetConnection() const { return m_info.connection; }
|
||||
[[nodiscard]] MACAddress_t GetMacAddress() const { return m_info.mac_address; }
|
||||
[[nodiscard]] DsBattery GetBattery() const { return m_info.battery; }
|
||||
[[nodiscard]] bool IsActive() const { return m_info.is_active != 0; }
|
||||
|
||||
protected:
|
||||
PortInfoData m_info;
|
||||
};
|
||||
static_assert(sizeof(PortInfo) == 0x20);
|
||||
|
||||
struct TouchPoint
|
||||
{
|
||||
uint8_t active;
|
||||
uint8_t index;
|
||||
int16_t x, y;
|
||||
};
|
||||
static_assert(sizeof(TouchPoint) == 0x6);
|
||||
|
||||
struct DataResponseData
|
||||
{
|
||||
uint32_t m_packet_index;
|
||||
|
||||
uint8_t state1;
|
||||
uint8_t state2;
|
||||
uint8_t ps;
|
||||
uint8_t touch;
|
||||
|
||||
// y values are inverted by convention
|
||||
uint8_t lx, ly;
|
||||
uint8_t rx, ry;
|
||||
|
||||
uint8_t dpad_left;
|
||||
uint8_t dpad_down;
|
||||
uint8_t dpad_right;
|
||||
uint8_t dpad_up;
|
||||
|
||||
uint8_t square;
|
||||
uint8_t cross;
|
||||
uint8_t circle;
|
||||
uint8_t triangle;
|
||||
|
||||
uint8_t r1;
|
||||
uint8_t l1;
|
||||
uint8_t r2;
|
||||
uint8_t l2;
|
||||
|
||||
TouchPoint tpad1, tpad2;
|
||||
|
||||
uint64_t motion_timestamp;
|
||||
Vector3f accel;
|
||||
Vector3f gyro;
|
||||
};
|
||||
|
||||
class DataResponse : public PortInfo
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] bool IsValid() const;
|
||||
|
||||
[[nodiscard]] const DataResponseData& GetData() const { return m_data; }
|
||||
|
||||
uint32_t GetPacketIndex() const { return m_data.m_packet_index; }
|
||||
uint8_t GetState1() const { return m_data.state1; }
|
||||
uint8_t GetState2() const { return m_data.state2; }
|
||||
uint8_t GetPs() const { return m_data.ps; }
|
||||
uint8_t GetTouch() const { return m_data.touch; }
|
||||
uint8_t GetLx() const { return m_data.lx; }
|
||||
uint8_t GetLy() const { return m_data.ly; }
|
||||
uint8_t GetRx() const { return m_data.rx; }
|
||||
uint8_t GetRy() const { return m_data.ry; }
|
||||
uint8_t GetDpadLeft() const { return m_data.dpad_left; }
|
||||
uint8_t GetDpadDown() const { return m_data.dpad_down; }
|
||||
uint8_t GetDpadRight() const { return m_data.dpad_right; }
|
||||
uint8_t GetDpadUp() const { return m_data.dpad_up; }
|
||||
uint8_t GetSquare() const { return m_data.square; }
|
||||
uint8_t GetCross() const { return m_data.cross; }
|
||||
uint8_t GetCircle() const { return m_data.circle; }
|
||||
uint8_t GetTriangle() const { return m_data.triangle; }
|
||||
uint8_t GetR1() const { return m_data.r1; }
|
||||
uint8_t GetL1() const { return m_data.l1; }
|
||||
uint8_t GetR2() const { return m_data.r2; }
|
||||
uint8_t GetL2() const { return m_data.l2; }
|
||||
const TouchPoint& GetTpad1() const { return m_data.tpad1; }
|
||||
const TouchPoint& GetTpad2() const { return m_data.tpad2; }
|
||||
uint64_t GetMotionTimestamp() const { return m_data.motion_timestamp; }
|
||||
|
||||
const Vector3f& GetAcceleration() const { return m_data.accel; }
|
||||
const Vector3f& GetGyro() const { return m_data.gyro; }
|
||||
|
||||
private:
|
||||
DataResponseData m_data;
|
||||
};
|
||||
|
||||
//static_assert(sizeof(DataResponse) == 0x20);
|
||||
|
||||
#pragma pack(pop)
|
337
src/input/api/DirectInput/DirectInputController.cpp
Normal file
337
src/input/api/DirectInput/DirectInputController.cpp
Normal file
|
@ -0,0 +1,337 @@
|
|||
#include "input/api/DirectInput/DirectInputController.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
|
||||
DirectInputController::DirectInputController(const GUID& guid)
|
||||
: base_type(StringFromGUID(guid), fmt::format("[{}]", StringFromGUID(guid))),
|
||||
m_guid{ guid }
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DirectInputController::DirectInputController(const GUID& guid, std::string_view display_name)
|
||||
: base_type(StringFromGUID(guid), display_name), m_guid(guid)
|
||||
{
|
||||
}
|
||||
|
||||
DirectInputController::~DirectInputController()
|
||||
{
|
||||
if (m_effect)
|
||||
m_effect->Release();
|
||||
|
||||
if (m_device)
|
||||
{
|
||||
m_device->Unacquire();
|
||||
|
||||
// TODO: test if really needed
|
||||
// workaround for gamecube controllers crash on release?
|
||||
bool should_release_device = true;
|
||||
if (m_product_guid == GUID{}) {
|
||||
DIDEVICEINSTANCE info{};
|
||||
info.dwSize = sizeof(DIDEVICEINSTANCE);
|
||||
if (SUCCEEDED(m_device->GetDeviceInfo(&info)))
|
||||
{
|
||||
m_product_guid = info.guidProduct;
|
||||
}
|
||||
}
|
||||
|
||||
// info.guidProduct = {18440079-0000-0000-0000-504944564944}
|
||||
constexpr GUID kGameCubeController = { 0x18440079, 0, 0, {0,0,0x50,0x49,0x44,0x56,0x49,0x44} };
|
||||
if (kGameCubeController == m_product_guid)
|
||||
should_release_device = false;
|
||||
|
||||
if (should_release_device)
|
||||
m_device->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectInputController::save(pugi::xml_node& node)
|
||||
{
|
||||
base_type::save(node);
|
||||
|
||||
node.append_child("product_guid").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", StringFromGUID(m_product_guid)).c_str());
|
||||
}
|
||||
|
||||
void DirectInputController::load(const pugi::xml_node& node)
|
||||
{
|
||||
base_type::load(node);
|
||||
|
||||
if (const auto value = node.child("product_guid")) {
|
||||
if (GUIDFromString(value.child_value(), m_product_guid) && m_product_guid != GUID{} && !is_connected())
|
||||
{
|
||||
// test if another controller with the same product guid is connectable and replace
|
||||
for(const auto& c : m_provider->get_controllers())
|
||||
{
|
||||
if(const auto ptr = std::dynamic_pointer_cast<DirectInputController>(c))
|
||||
{
|
||||
if (ptr->is_connected() && ptr->get_product_guid() == m_product_guid)
|
||||
{
|
||||
const auto tmp_guid = m_guid;
|
||||
m_guid = ptr->get_guid();
|
||||
if (connect())
|
||||
break;
|
||||
|
||||
// couldn't connect
|
||||
m_guid = tmp_guid;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectInputController::connect()
|
||||
{
|
||||
if (is_connected())
|
||||
return true;
|
||||
|
||||
m_effect = nullptr;
|
||||
|
||||
std::scoped_lock lock(m_mutex);
|
||||
HRESULT hr = m_provider->get_dinput()->CreateDevice(m_guid, &m_device, nullptr);
|
||||
if (FAILED(hr) || m_device == nullptr)
|
||||
return false;
|
||||
|
||||
DIDEVICEINSTANCE idi{};
|
||||
idi.dwSize = sizeof(DIDEVICEINSTANCE);
|
||||
if (SUCCEEDED(m_device->GetDeviceInfo(&idi)))
|
||||
{
|
||||
// overwrite guid name with "real" display name
|
||||
m_display_name = boost::nowide::narrow(idi.tszProductName);
|
||||
}
|
||||
|
||||
// set data format
|
||||
if (FAILED(m_device->SetDataFormat(m_provider->get_data_format())))
|
||||
{
|
||||
SAFE_RELEASE(m_device);
|
||||
return false;
|
||||
}
|
||||
|
||||
HWND hwndMainWindow = gui_getWindowInfo().window_main.hwnd;
|
||||
|
||||
// set access
|
||||
if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_EXCLUSIVE)))
|
||||
{
|
||||
if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE)))
|
||||
{
|
||||
SAFE_RELEASE(m_device);
|
||||
return false;
|
||||
}
|
||||
// rumble can only be used with exclusive access
|
||||
}
|
||||
else
|
||||
{
|
||||
GUID guid_effect = GUID_NULL;
|
||||
// check if constant force is supported
|
||||
HRESULT result = m_device->EnumEffects([](LPCDIEFFECTINFOW eff, LPVOID guid) -> BOOL
|
||||
{
|
||||
*(GUID*)guid = eff->guid;
|
||||
return DIENUM_STOP;
|
||||
}, &guid_effect, DIEFT_CONSTANTFORCE);
|
||||
|
||||
if (SUCCEEDED(result) && guid_effect != GUID_NULL)
|
||||
{
|
||||
DWORD dwAxes[2] = { DIJOFS_X, DIJOFS_Y };
|
||||
LONG lDirection[2] = { 1, 0 };
|
||||
|
||||
DICONSTANTFORCE constant_force = { DI_FFNOMINALMAX }; // DI_FFNOMINALMAX -> should be max normally?!
|
||||
|
||||
DIEFFECT effect{};
|
||||
effect.dwSize = sizeof(DIEFFECT);
|
||||
effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
|
||||
effect.dwDuration = INFINITE; // DI_SECONDS;
|
||||
effect.dwGain = DI_FFNOMINALMAX; // No scaling
|
||||
effect.dwTriggerButton = DIEB_NOTRIGGER; // Not a button response DIEB_NOTRIGGER DIJOFS_BUTTON0
|
||||
effect.cAxes = 2;
|
||||
effect.rgdwAxes = dwAxes;
|
||||
effect.rglDirection = lDirection;
|
||||
effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
|
||||
effect.lpvTypeSpecificParams = &constant_force;
|
||||
m_device->CreateEffect(guid_effect, &effect, &m_effect, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
DIDEVICEINSTANCE info{};
|
||||
info.dwSize = sizeof(DIDEVICEINSTANCE);
|
||||
if (SUCCEEDED(m_device->GetDeviceInfo(&info)))
|
||||
{
|
||||
m_product_guid = info.guidProduct;
|
||||
}
|
||||
|
||||
std::fill(m_min_axis.begin(), m_min_axis.end(), 0);
|
||||
std::fill(m_max_axis.begin(), m_max_axis.end(), std::numeric_limits<uint16>::max());
|
||||
m_device->EnumObjects(
|
||||
[](LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) -> BOOL
|
||||
{
|
||||
auto* thisptr = (DirectInputController*)pvRef;
|
||||
|
||||
const auto instance = DIDFT_GETINSTANCE(lpddoi->dwType);
|
||||
// some tools may use state.rglSlider properties, so they have 8 instead of 6 axis
|
||||
if(instance >= thisptr->m_min_axis.size())
|
||||
{
|
||||
return DIENUM_CONTINUE;
|
||||
}
|
||||
|
||||
DIPROPRANGE range{};
|
||||
range.diph.dwSize = sizeof(range);
|
||||
range.diph.dwHeaderSize = sizeof(range.diph);
|
||||
range.diph.dwHow = DIPH_BYID;
|
||||
range.diph.dwObj = lpddoi->dwType;
|
||||
if (thisptr->m_device->GetProperty(DIPROP_RANGE, &range.diph) == DI_OK)
|
||||
{
|
||||
thisptr->m_min_axis[instance] = range.lMin;
|
||||
thisptr->m_max_axis[instance] = range.lMax;
|
||||
}
|
||||
|
||||
return DIENUM_CONTINUE;
|
||||
}, this, DIDFT_AXIS);
|
||||
|
||||
m_device->Acquire();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectInputController::is_connected()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
return m_device != nullptr;
|
||||
}
|
||||
|
||||
bool DirectInputController::has_rumble()
|
||||
{
|
||||
return m_effect != nullptr;
|
||||
}
|
||||
|
||||
void DirectInputController::start_rumble()
|
||||
{
|
||||
if (!has_rumble())
|
||||
return;
|
||||
}
|
||||
|
||||
void DirectInputController::stop_rumble()
|
||||
{
|
||||
if (!has_rumble())
|
||||
return;
|
||||
}
|
||||
|
||||
std::string DirectInputController::get_button_name(uint64 button) const
|
||||
{
|
||||
switch(button)
|
||||
{
|
||||
case kAxisXP: return "X+";
|
||||
case kAxisYP: return "Y+";
|
||||
|
||||
case kAxisXN: return "X-";
|
||||
case kAxisYN: return "Y-";
|
||||
|
||||
case kRotationXP: return "RX+";
|
||||
case kRotationYP: return "RY+";
|
||||
|
||||
case kRotationXN: return "RX-";
|
||||
case kRotationYN: return "RY-";
|
||||
|
||||
case kTriggerXP: return "Z+";
|
||||
case kTriggerYP: return "RZ+";
|
||||
|
||||
case kTriggerXN: return "Z-";
|
||||
case kTriggerYN: return "RZ-";
|
||||
}
|
||||
|
||||
return base_type::get_button_name(button);
|
||||
}
|
||||
|
||||
ControllerState DirectInputController::raw_state()
|
||||
{
|
||||
ControllerState result{};
|
||||
if (!is_connected())
|
||||
return result;
|
||||
|
||||
HRESULT hr = m_device->Poll();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
|
||||
{
|
||||
result.last_state = hr;
|
||||
m_device->Acquire();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DIJOYSTATE state{};
|
||||
hr = m_device->GetDeviceState(sizeof(state), &state);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
|
||||
{
|
||||
result.last_state = hr;
|
||||
m_device->Acquire();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result.last_state = hr;
|
||||
|
||||
// buttons
|
||||
for (size_t i = 0; i < std::size(state.rgbButtons); ++i)
|
||||
{
|
||||
if (HAS_BIT(state.rgbButtons[i], 7))
|
||||
{
|
||||
result.buttons.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
// axis
|
||||
constexpr float kThreshold = 0.001f;
|
||||
float v = (float(state.lX - m_min_axis[0]) / float(m_max_axis[0] - m_min_axis[0])) * 2.0f - 1.0f;
|
||||
if (std::abs(v) >= kThreshold)
|
||||
result.axis.x = v;
|
||||
|
||||
v = (float(state.lY - m_min_axis[1]) / float(m_max_axis[1] - m_min_axis[1])) * 2.0f - 1.0f;
|
||||
if (std::abs(v) >= kThreshold)
|
||||
result.axis.y = -v;
|
||||
|
||||
// Right Stick
|
||||
v = (float(state.lRx - m_min_axis[3]) / float(m_max_axis[3] - m_min_axis[3])) * 2.0f - 1.0f;
|
||||
if (std::abs(v) >= kThreshold)
|
||||
result.rotation.x = v;
|
||||
|
||||
v = (float(state.lRy - m_min_axis[4]) / float(m_max_axis[4] - m_min_axis[4])) * 2.0f - 1.0f;
|
||||
if (std::abs(v) >= kThreshold)
|
||||
result.rotation.y = -v;
|
||||
|
||||
// Trigger
|
||||
v = (float(state.lZ - m_min_axis[2]) / float(m_max_axis[2] - m_min_axis[2])) * 2.0f - 1.0f;
|
||||
if (std::abs(v) >= kThreshold)
|
||||
result.trigger.x = v;
|
||||
|
||||
v = (float(state.lRz - m_min_axis[5]) / float(m_max_axis[5] - m_min_axis[5])) * 2.0f - 1.0f;
|
||||
if (std::abs(v) >= kThreshold)
|
||||
result.trigger.y = -v;
|
||||
|
||||
// dpad
|
||||
const auto pov = state.rgdwPOV[0];
|
||||
if (pov != static_cast<DWORD>(-1))
|
||||
{
|
||||
switch (pov)
|
||||
{
|
||||
case 0: result.buttons.set(kButtonUp);
|
||||
break;
|
||||
case 4500: result.buttons.set(kButtonUp); // up + right
|
||||
case 9000: result.buttons.set(kButtonRight);
|
||||
break;
|
||||
case 13500: result.buttons.set(kButtonRight); // right + down
|
||||
case 18000: result.buttons.set(kButtonDown);
|
||||
break;
|
||||
case 22500: result.buttons.set(kButtonDown); // down + left
|
||||
case 27000: result.buttons.set(kButtonLeft);
|
||||
break;
|
||||
case 31500: result.buttons.set(kButtonLeft);; // left + up
|
||||
result.buttons.set(kButtonUp); // left + up
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
49
src/input/api/DirectInput/DirectInputController.h
Normal file
49
src/input/api/DirectInput/DirectInputController.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/DirectInput/DirectInputControllerProvider.h"
|
||||
#include "input/api/Controller.h"
|
||||
|
||||
class DirectInputController : public Controller<DirectInputControllerProvider>
|
||||
{
|
||||
public:
|
||||
DirectInputController(const GUID& guid);
|
||||
DirectInputController(const GUID& guid, std::string_view display_name);
|
||||
~DirectInputController() override;
|
||||
|
||||
std::string_view api_name() const override
|
||||
{
|
||||
static_assert(to_string(InputAPI::DirectInput) == "DirectInput");
|
||||
return to_string(InputAPI::DirectInput);
|
||||
}
|
||||
InputAPI::Type api() const override { return InputAPI::DirectInput; }
|
||||
|
||||
void save(pugi::xml_node& node) override;
|
||||
void load(const pugi::xml_node& node) override;
|
||||
|
||||
bool connect() override;
|
||||
bool is_connected() override;
|
||||
|
||||
bool has_rumble() override;
|
||||
|
||||
void start_rumble() override;
|
||||
void stop_rumble() override;
|
||||
|
||||
std::string get_button_name(uint64 button) const override;
|
||||
|
||||
const GUID& get_guid() const { return m_guid; }
|
||||
const GUID& get_product_guid() const { return m_product_guid; }
|
||||
|
||||
protected:
|
||||
ControllerState raw_state() override;
|
||||
|
||||
private:
|
||||
GUID m_guid;
|
||||
GUID m_product_guid{};
|
||||
|
||||
std::shared_mutex m_mutex;
|
||||
LPDIRECTINPUTDEVICE8 m_device = nullptr;
|
||||
LPDIRECTINPUTEFFECT m_effect = nullptr;
|
||||
|
||||
std::array<LONG, 6> m_min_axis{};
|
||||
std::array<LONG, 6> m_max_axis{};
|
||||
};
|
65
src/input/api/DirectInput/DirectInputControllerProvider.cpp
Normal file
65
src/input/api/DirectInput/DirectInputControllerProvider.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include "input/api/DirectInput/DirectInputControllerProvider.h"
|
||||
|
||||
#include "input/api/DirectInput/DirectInputController.h"
|
||||
|
||||
DirectInputControllerProvider::DirectInputControllerProvider()
|
||||
{
|
||||
/*m_module = LoadLibraryA("dinput8.dll");
|
||||
if (!m_module)
|
||||
throw std::runtime_error("can't load any xinput dll");
|
||||
|
||||
m_DirectInput8Create = (decltype(&DirectInput8Create))GetProcAddress(m_module, "DirectInput8Create");
|
||||
m_GetdfDIJoystick = (decltype(&GetdfDIJoystick))GetProcAddress(m_module, "GetdfDIJoystick");
|
||||
|
||||
if (!m_DirectInput8Create)
|
||||
{
|
||||
FreeLibrary(m_module);
|
||||
throw std::runtime_error("can't find the DirectInput8Create export");
|
||||
}*/
|
||||
|
||||
|
||||
const auto r = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_dinput8, nullptr);
|
||||
if (FAILED(r) || !m_dinput8)
|
||||
{
|
||||
const auto error = GetLastError();
|
||||
//FreeLibrary(m_module);
|
||||
throw std::runtime_error(fmt::format("can't create direct input object (error: {:#x})", error));
|
||||
}
|
||||
}
|
||||
|
||||
DirectInputControllerProvider::~DirectInputControllerProvider()
|
||||
{
|
||||
if (m_dinput8)
|
||||
m_dinput8->Release();
|
||||
|
||||
/*if (m_module)
|
||||
FreeLibrary(m_module);
|
||||
*/
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> DirectInputControllerProvider::get_controllers()
|
||||
{
|
||||
std::vector<std::shared_ptr<ControllerBase>> result;
|
||||
|
||||
m_dinput8->EnumDevices(DI8DEVCLASS_GAMECTRL,
|
||||
[](LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) -> BOOL
|
||||
{
|
||||
auto* controllers = (decltype(&result))pvRef;
|
||||
|
||||
std::string display_name = boost::nowide::narrow(lpddi->tszProductName);
|
||||
controllers->emplace_back(std::make_shared<DirectInputController>(lpddi->guidInstance, display_name));
|
||||
|
||||
return DIENUM_CONTINUE;
|
||||
}, &result, DIEDFL_ALLDEVICES);
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LPCDIDATAFORMAT DirectInputControllerProvider::get_data_format() const
|
||||
{
|
||||
/*if (m_GetdfDIJoystick)
|
||||
return m_GetdfDIJoystick();*/
|
||||
|
||||
return GetdfDIJoystick();
|
||||
}
|
37
src/input/api/DirectInput/DirectInputControllerProvider.h
Normal file
37
src/input/api/DirectInput/DirectInputControllerProvider.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
#if BOOST_OS_WINDOWS
|
||||
|
||||
#define DIRECTINPUT_VERSION 0x0800
|
||||
|
||||
#include <dinput.h>
|
||||
|
||||
#include "input/api/ControllerProvider.h"
|
||||
|
||||
#ifndef HAS_DIRECTINPUT
|
||||
#define HAS_DIRECTINPUT 1
|
||||
#endif
|
||||
|
||||
class DirectInputControllerProvider : public ControllerProviderBase
|
||||
{
|
||||
public:
|
||||
DirectInputControllerProvider();
|
||||
~DirectInputControllerProvider() override;
|
||||
|
||||
inline static InputAPI::Type kAPIType = InputAPI::DirectInput;
|
||||
InputAPI::Type api() const override { return kAPIType; }
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
|
||||
|
||||
IDirectInput8* get_dinput() const { return m_dinput8; }
|
||||
LPCDIDATAFORMAT get_data_format() const;
|
||||
|
||||
private:
|
||||
HMODULE m_module = nullptr;
|
||||
|
||||
decltype(&DirectInput8Create) m_DirectInput8Create;
|
||||
decltype(&GetdfDIJoystick) m_GetdfDIJoystick = nullptr;
|
||||
|
||||
IDirectInput8* m_dinput8 = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
103
src/input/api/GameCube/GameCubeController.cpp
Normal file
103
src/input/api/GameCube/GameCubeController.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
#include "input/api/GameCube/GameCubeController.h"
|
||||
|
||||
#ifdef HAS_GAMECUBE
|
||||
|
||||
GameCubeController::GameCubeController(uint32 adapter, uint32 index)
|
||||
: base_type(fmt::format("{}_{}", adapter, index), fmt::format("Controller {}", index + 1)), m_adapter(adapter),
|
||||
m_index(index)
|
||||
{
|
||||
// update names if multiple adapters are connected
|
||||
if (adapter > 0)
|
||||
m_display_name = fmt::format("Controller {} ({})", index + 1, adapter);
|
||||
|
||||
m_settings.axis.range = 1.20f;
|
||||
m_settings.rotation.range = 1.25f;
|
||||
m_settings.trigger.range = 1.07f;
|
||||
}
|
||||
|
||||
bool GameCubeController::is_connected()
|
||||
{
|
||||
return m_provider->is_connected(m_adapter);
|
||||
}
|
||||
|
||||
bool GameCubeController::has_rumble()
|
||||
{
|
||||
return m_provider->has_rumble_connected(m_adapter);
|
||||
}
|
||||
|
||||
void GameCubeController::start_rumble()
|
||||
{
|
||||
if (m_settings.rumble <= 0)
|
||||
return;
|
||||
|
||||
m_provider->set_rumble_state(m_adapter, m_index, true);
|
||||
}
|
||||
|
||||
void GameCubeController::stop_rumble()
|
||||
{
|
||||
m_provider->set_rumble_state(m_adapter, m_index, false);
|
||||
}
|
||||
|
||||
std::string GameCubeController::get_button_name(uint64 button) const
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case kButton0: return "A";
|
||||
case kButton1: return "B";
|
||||
case kButton2: return "X";
|
||||
case kButton3: return "Y";
|
||||
|
||||
case kButton4: return "Left";
|
||||
case kButton5: return "Right";
|
||||
case kButton6: return "Down";
|
||||
case kButton7: return "Up";
|
||||
|
||||
case kButton8: return "Start";
|
||||
case kButton9: return "Z";
|
||||
|
||||
case kButton10: return "Trigger R";
|
||||
case kButton11: return "Trigger L";
|
||||
}
|
||||
|
||||
return base_type::get_button_name(button);
|
||||
}
|
||||
|
||||
ControllerState GameCubeController::raw_state()
|
||||
{
|
||||
ControllerState result{};
|
||||
if (!is_connected())
|
||||
return result;
|
||||
|
||||
const auto state = m_provider->get_state(m_adapter, m_index);
|
||||
if (state.valid)
|
||||
{
|
||||
for (auto i = 0; i <= kButton11; ++i)
|
||||
{
|
||||
if (HAS_BIT(state.button, i))
|
||||
{
|
||||
result.buttons.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
// printf("(%d, %d) - (%d, %d) - (%d, %d)\n", state.lstick_x, state.lstick_y, state.rstick_x, state.rstick_y, state.lstick, state.rstick);
|
||||
result.axis.x = (float)state.lstick_x / std::numeric_limits<uint8>::max();
|
||||
result.axis.x = (result.axis.x * 2.0f) - 1.0f;
|
||||
|
||||
result.axis.y = (float)state.lstick_y / std::numeric_limits<uint8>::max();
|
||||
result.axis.y = (result.axis.y * 2.0f) - 1.0f;
|
||||
|
||||
result.rotation.x = (float)state.rstick_x / std::numeric_limits<uint8>::max();
|
||||
result.rotation.x = (result.rotation.x * 2.0f) - 1.0f;
|
||||
|
||||
result.rotation.y = (float)state.rstick_y / std::numeric_limits<uint8>::max();
|
||||
result.rotation.y = (result.rotation.y * 2.0f) - 1.0f;
|
||||
|
||||
|
||||
result.trigger.x = (float)state.lstick / std::numeric_limits<uint8>::max();
|
||||
result.trigger.y = (float)state.rstick / std::numeric_limits<uint8>::max();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
34
src/input/api/GameCube/GameCubeController.h
Normal file
34
src/input/api/GameCube/GameCubeController.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/GameCube/GameCubeControllerProvider.h"
|
||||
|
||||
#ifdef HAS_GAMECUBE
|
||||
|
||||
class GameCubeController : public Controller<GameCubeControllerProvider>
|
||||
{
|
||||
public:
|
||||
GameCubeController(uint32 adapter, uint32 index);
|
||||
|
||||
std::string_view api_name() const override
|
||||
{
|
||||
static_assert(to_string(InputAPI::GameCube) == "GameCube");
|
||||
return to_string(InputAPI::GameCube);
|
||||
}
|
||||
InputAPI::Type api() const override { return InputAPI::GameCube; }
|
||||
|
||||
bool is_connected() override;
|
||||
bool has_rumble() override;
|
||||
void start_rumble() override;
|
||||
void stop_rumble() override;
|
||||
|
||||
std::string get_button_name(uint64 button) const override;
|
||||
|
||||
protected:
|
||||
ControllerState raw_state() override;
|
||||
|
||||
uint32 m_adapter;
|
||||
uint32 m_index;
|
||||
};
|
||||
|
||||
#endif
|
388
src/input/api/GameCube/GameCubeControllerProvider.cpp
Normal file
388
src/input/api/GameCube/GameCubeControllerProvider.cpp
Normal file
|
@ -0,0 +1,388 @@
|
|||
#include "input/api/GameCube/GameCubeControllerProvider.h"
|
||||
#include "input/api/GameCube/GameCubeController.h"
|
||||
#include "util/libusbWrapper/libusbWrapper.h"
|
||||
|
||||
#if HAS_GAMECUBE
|
||||
|
||||
constexpr uint16_t kVendorId = 0x57e, kProductId = 0x337;
|
||||
|
||||
GameCubeControllerProvider::GameCubeControllerProvider()
|
||||
{
|
||||
m_libusb = libusbWrapper::getInstance();
|
||||
m_libusb->init();
|
||||
if(!m_libusb->isAvailable())
|
||||
throw std::runtime_error("libusbWrapper not available");
|
||||
m_libusb->p_libusb_init(&m_context);
|
||||
|
||||
for(auto i = 0; i < kMaxAdapters; ++i)
|
||||
{
|
||||
auto device = fetch_usb_device(i);
|
||||
if(std::get<0>(device))
|
||||
{
|
||||
m_adapters[i].m_device_handle = std::get<0>(device);
|
||||
m_adapters[i].m_endpoint_reader = std::get<1>(device);
|
||||
m_adapters[i].m_endpoint_writer = std::get<2>(device);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_libusb->p_libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
|
||||
{
|
||||
m_libusb->p_libusb_hotplug_register_callback(m_context, static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
|
||||
LIBUSB_HOTPLUG_NO_FLAGS, kVendorId, kProductId, LIBUSB_HOTPLUG_MATCH_ANY, &GameCubeControllerProvider::hotplug_event, this, &m_callback_handle);
|
||||
}
|
||||
|
||||
m_running = true;
|
||||
m_reader_thread = std::thread(&GameCubeControllerProvider::reader_thread, this);
|
||||
m_writer_thread = std::thread(&GameCubeControllerProvider::writer_thread, this);
|
||||
}
|
||||
|
||||
GameCubeControllerProvider::~GameCubeControllerProvider()
|
||||
{
|
||||
if (m_running)
|
||||
{
|
||||
m_running = false;
|
||||
m_writer_thread.join();
|
||||
m_reader_thread.join();
|
||||
}
|
||||
|
||||
if (m_callback_handle)
|
||||
{
|
||||
m_libusb->p_libusb_hotplug_deregister_callback(m_context, m_callback_handle);
|
||||
m_callback_handle = 0;
|
||||
}
|
||||
|
||||
for (auto& a : m_adapters)
|
||||
{
|
||||
m_libusb->p_libusb_close(a.m_device_handle);
|
||||
}
|
||||
|
||||
if (m_context)
|
||||
{
|
||||
m_libusb->p_libusb_exit(m_context);
|
||||
m_context = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ControllerPtr> GameCubeControllerProvider::get_controllers()
|
||||
{
|
||||
std::vector<ControllerPtr> result;
|
||||
|
||||
const auto adapter_count = get_adapter_count();
|
||||
for (uint32 adapter_index = 0; adapter_index < adapter_count && adapter_index < kMaxAdapters; ++adapter_index)
|
||||
{
|
||||
// adapter doesn't tell us which one is actually connected, so we return all of them
|
||||
for (int index = 0; index < 4; ++index)
|
||||
result.emplace_back(std::make_shared<GameCubeController>(adapter_index, index));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32 GameCubeControllerProvider::get_adapter_count() const
|
||||
{
|
||||
uint32 adapter_count = 0;
|
||||
|
||||
libusb_device** devices;
|
||||
const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices);
|
||||
if (count < 0)
|
||||
{
|
||||
forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_list: {}", static_cast<int>(count), m_libusb->p_libusb_error_name(static_cast<int>(count))).c_str());
|
||||
return adapter_count;
|
||||
}
|
||||
|
||||
for (ssize_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (!devices[i])
|
||||
continue;
|
||||
|
||||
libusb_device_descriptor desc;
|
||||
int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc);
|
||||
if (ret != 0)
|
||||
{
|
||||
forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desc.idVendor != kVendorId || desc.idProduct != kProductId)
|
||||
continue;
|
||||
|
||||
++adapter_count;
|
||||
}
|
||||
|
||||
m_libusb->p_libusb_free_device_list(devices, 1);
|
||||
return adapter_count;
|
||||
}
|
||||
|
||||
bool GameCubeControllerProvider::has_rumble_connected(uint32 adapter_index) const
|
||||
{
|
||||
if (adapter_index >= m_adapters.size())
|
||||
return false;
|
||||
|
||||
std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex);
|
||||
return m_adapters[adapter_index].m_rumble_connected;
|
||||
}
|
||||
|
||||
bool GameCubeControllerProvider::is_connected(uint32 adapter_index) const
|
||||
{
|
||||
if (adapter_index >= m_adapters.size())
|
||||
return false;
|
||||
|
||||
return m_adapters[adapter_index].m_device_handle != nullptr;
|
||||
}
|
||||
|
||||
void GameCubeControllerProvider::set_rumble_state(uint32 adapter_index, uint32 index, bool state)
|
||||
{
|
||||
if (adapter_index >= m_adapters.size())
|
||||
return;
|
||||
|
||||
if (index >= kMaxIndex)
|
||||
return;
|
||||
|
||||
std::scoped_lock lock(m_writer_mutex);
|
||||
m_adapters[adapter_index].rumble_states[index] = state;
|
||||
m_rumble_changed = true;
|
||||
m_writer_cond.notify_all();
|
||||
}
|
||||
|
||||
GameCubeControllerProvider::GCState GameCubeControllerProvider::get_state(uint32 adapter_index, uint32 index)
|
||||
{
|
||||
if (adapter_index >= m_adapters.size())
|
||||
return {};
|
||||
|
||||
if (index >= kMaxIndex)
|
||||
return {};
|
||||
|
||||
std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex);
|
||||
return m_adapters[adapter_index].m_states[index];
|
||||
}
|
||||
|
||||
#ifdef interface
|
||||
#undef interface
|
||||
#endif
|
||||
|
||||
std::tuple<libusb_device_handle*, uint8, uint8> GameCubeControllerProvider::fetch_usb_device(uint32 adapter) const
|
||||
{
|
||||
std::tuple<libusb_device_handle*, uint8, uint8> result{nullptr, 0xFF, 0xFF};
|
||||
|
||||
libusb_device** devices;
|
||||
const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices);
|
||||
if (count < 0)
|
||||
{
|
||||
forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_list: {}", static_cast<int>(count), m_libusb->p_libusb_error_name(static_cast<int>(count))).c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
int adapter_index = 0;
|
||||
for (ssize_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (!devices[i])
|
||||
continue;
|
||||
|
||||
libusb_device_descriptor desc;
|
||||
int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc);
|
||||
if (ret != 0)
|
||||
{
|
||||
forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desc.idVendor != kVendorId || desc.idProduct != kProductId)
|
||||
continue;
|
||||
|
||||
if (adapter != adapter_index++)
|
||||
continue;
|
||||
|
||||
libusb_device_handle* device_handle;
|
||||
ret = m_libusb->p_libusb_open(devices[i], &device_handle);
|
||||
if (ret != 0)
|
||||
{
|
||||
forceLog_printf((char*)fmt::format("libusb error {} at libusb_open: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_libusb->p_libusb_kernel_driver_active(device_handle, 0) == 1)
|
||||
{
|
||||
ret = m_libusb->p_libusb_detach_kernel_driver(device_handle, 0);
|
||||
if (ret != 0)
|
||||
{
|
||||
forceLog_printf((char*)fmt::format("libusb error {} at libusb_detach_kernel_driver: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
|
||||
m_libusb->p_libusb_close(device_handle);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ret = m_libusb->p_libusb_claim_interface(device_handle, 0);
|
||||
if (ret != 0)
|
||||
{
|
||||
forceLog_printf((char*)fmt::format("libusb error {} at libusb_claim_interface: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
|
||||
m_libusb->p_libusb_close(device_handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
libusb_config_descriptor* config = nullptr;
|
||||
m_libusb->p_libusb_get_config_descriptor(devices[i], 0, &config);
|
||||
for (auto ic = 0; ic < config->bNumInterfaces; ic++)
|
||||
{
|
||||
const auto& interface = config->interface[ic];
|
||||
for (auto j = 0; j < interface.num_altsetting; j++)
|
||||
{
|
||||
const auto& interface_desc = interface.altsetting[j];
|
||||
for (auto k = 0; k < interface_desc.bNumEndpoints; k++)
|
||||
{
|
||||
const auto& endpoint = interface_desc.endpoint[k];
|
||||
if (endpoint.bEndpointAddress & LIBUSB_ENDPOINT_IN)
|
||||
std::get<1>(result) = endpoint.bEndpointAddress;
|
||||
else
|
||||
std::get<2>(result) = endpoint.bEndpointAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_libusb->p_libusb_free_config_descriptor(config);
|
||||
|
||||
// start polling
|
||||
int size = 0;
|
||||
uint8_t payload = 0x13;
|
||||
m_libusb->p_libusb_interrupt_transfer(device_handle, std::get<2>(result), &payload, sizeof(payload), &size, 25);
|
||||
|
||||
std::get<0>(result) = device_handle;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
m_libusb->p_libusb_free_device_list(devices, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
void GameCubeControllerProvider::reader_thread()
|
||||
{
|
||||
SetThreadName("GCControllerAdapter::reader_thread");
|
||||
while (m_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
std::this_thread::yield();
|
||||
|
||||
for(auto& adapter : m_adapters)
|
||||
{
|
||||
if (!adapter.m_device_handle)
|
||||
continue;
|
||||
|
||||
std::array<uint8_t, 37> data{};
|
||||
int read;
|
||||
const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_reader, data.data(), static_cast<int>(data.size()), &read, 25);
|
||||
if (result == 0)
|
||||
{
|
||||
/*
|
||||
byte 1
|
||||
0x10 NORMAL STATE
|
||||
0x20 WAVEBIRD STATE
|
||||
0x04 RUMBLE POWER
|
||||
*/
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
GCState state;
|
||||
state.valid = true;
|
||||
state.button = *(uint16*)&data[1 + (i * 9) + 1];
|
||||
state.lstick_x = data[1 + (i * 9) + 3];
|
||||
state.lstick_y = data[1 + (i * 9) + 4];
|
||||
state.rstick_x = data[1 + (i * 9) + 5];
|
||||
state.rstick_y = data[1 + (i * 9) + 6];
|
||||
state.lstick = data[1 + (i * 9) + 7];
|
||||
state.rstick = data[1 + (i * 9) + 8];
|
||||
|
||||
std::scoped_lock lock(adapter.m_state_mutex);
|
||||
adapter.m_rumble_connected = HAS_FLAG(data[1], 4);
|
||||
adapter.m_states[i] = state;
|
||||
}
|
||||
}
|
||||
else if (result == LIBUSB_ERROR_NO_DEVICE || result == LIBUSB_ERROR_IO)
|
||||
{
|
||||
forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str());
|
||||
if (const auto handle = adapter.m_device_handle.exchange(nullptr))
|
||||
m_libusb->p_libusb_close(handle);
|
||||
}
|
||||
else { forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameCubeControllerProvider::writer_thread()
|
||||
{
|
||||
SetThreadName("GCControllerAdapter::writer_thread");
|
||||
|
||||
std::array<std::array<bool, 4>, kMaxAdapters> rumble_states{};
|
||||
|
||||
while (m_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
std::unique_lock lock(m_writer_mutex);
|
||||
if (!m_rumble_changed && m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout)
|
||||
{
|
||||
if (!m_running)
|
||||
return;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool cmd_sent = false;
|
||||
for (size_t i = 0; i < kMaxAdapters; ++i)
|
||||
{
|
||||
auto& adapter = m_adapters[i];
|
||||
if (!adapter.m_device_handle)
|
||||
continue;
|
||||
|
||||
if (adapter.rumble_states == rumble_states[i])
|
||||
continue;
|
||||
|
||||
rumble_states[i] = adapter.rumble_states;
|
||||
m_rumble_changed = false;
|
||||
lock.unlock();
|
||||
|
||||
std::array<uint8, 5> rumble{ 0x11, rumble_states[i][0],rumble_states[i][1],rumble_states[i][2], rumble_states[i][3] };
|
||||
|
||||
int written;
|
||||
const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_writer, rumble.data(), static_cast<int>(rumble.size()), &written, 25);
|
||||
if (result != 0) { forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str()); }
|
||||
cmd_sent = true;
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
if(cmd_sent)
|
||||
{
|
||||
lock.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GameCubeControllerProvider::hotplug_event(libusb_context* ctx, libusb_device* dev, libusb_hotplug_event event, void* user_data)
|
||||
{
|
||||
auto* thisptr = static_cast<GameCubeControllerProvider*>(user_data);
|
||||
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event)
|
||||
{
|
||||
for (auto i = 0; i < kMaxAdapters; ++i)
|
||||
{
|
||||
if (thisptr->m_adapters[i].m_device_handle)
|
||||
continue;
|
||||
|
||||
auto device = thisptr->fetch_usb_device(i);
|
||||
if (std::get<0>(device))
|
||||
{
|
||||
thisptr->m_adapters[i].m_endpoint_reader = std::get<1>(device);
|
||||
thisptr->m_adapters[i].m_endpoint_writer = std::get<2>(device);
|
||||
|
||||
thisptr->m_adapters[i].m_device_handle = std::get<0>(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*else
|
||||
{
|
||||
const auto device_handle = thisptr->m_device_handle.exchange(nullptr);
|
||||
if (device_handle)
|
||||
thisptr->m_libusb->p_libusb_close(device_handle);
|
||||
}*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
79
src/input/api/GameCube/GameCubeControllerProvider.h
Normal file
79
src/input/api/GameCube/GameCubeControllerProvider.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/libusbWrapper/libusbWrapper.h"
|
||||
#include "input/api/ControllerProvider.h"
|
||||
|
||||
#ifdef HAS_GAMECUBE
|
||||
|
||||
class GameCubeControllerProvider : public ControllerProviderBase
|
||||
{
|
||||
friend class DSUController;
|
||||
public:
|
||||
constexpr static size_t kMaxAdapters = 4;
|
||||
constexpr static size_t kMaxIndex = 4;
|
||||
|
||||
GameCubeControllerProvider();
|
||||
~GameCubeControllerProvider();
|
||||
|
||||
inline static InputAPI::Type kAPIType = InputAPI::GameCube;
|
||||
InputAPI::Type api() const override { return kAPIType; }
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
|
||||
|
||||
uint32 get_adapter_count() const;
|
||||
bool has_rumble_connected(uint32 adapter_index) const;
|
||||
bool is_connected(uint32 adapter_index) const;
|
||||
|
||||
void set_rumble_state(uint32 adapter_index, uint32 index, bool state);
|
||||
|
||||
struct GCState
|
||||
{
|
||||
bool valid = false;
|
||||
uint16 button = 0;
|
||||
|
||||
uint8 lstick_x = 0;
|
||||
uint8 lstick_y = 0;
|
||||
|
||||
uint8 rstick_x = 0;
|
||||
uint8 rstick_y = 0;
|
||||
|
||||
uint8 lstick = 0;
|
||||
uint8 rstick = 0;
|
||||
};
|
||||
GCState get_state(uint32 adapter_index, uint32 index);
|
||||
|
||||
private:
|
||||
std::shared_ptr<libusbWrapper> m_libusb;
|
||||
libusb_context* m_context = nullptr;
|
||||
|
||||
std::atomic_bool m_running = false;
|
||||
std::thread m_reader_thread, m_writer_thread;
|
||||
|
||||
void reader_thread();
|
||||
void writer_thread();
|
||||
|
||||
// handle, endpoint_reader, endpoint_writer
|
||||
std::tuple<libusb_device_handle*, uint8, uint8> fetch_usb_device(uint32 adapter) const;
|
||||
|
||||
std::mutex m_writer_mutex;
|
||||
std::condition_variable m_writer_cond;
|
||||
bool m_rumble_changed = false;
|
||||
struct Adapter
|
||||
{
|
||||
std::atomic<libusb_device_handle*> m_device_handle{};
|
||||
|
||||
uint8 m_endpoint_reader = 0xFF, m_endpoint_writer = 0xFF;
|
||||
|
||||
mutable std::mutex m_state_mutex;
|
||||
std::array<GCState, kMaxIndex> m_states{};
|
||||
bool m_rumble_connected = false;
|
||||
|
||||
std::array<bool, kMaxIndex> rumble_states{};
|
||||
};
|
||||
std::array<Adapter, kMaxAdapters> m_adapters;
|
||||
|
||||
libusb_hotplug_callback_handle m_callback_handle = 0;
|
||||
static int hotplug_event(struct libusb_context* ctx, struct libusb_device* dev, libusb_hotplug_event event, void* user_data);
|
||||
};
|
||||
|
||||
#endif
|
79
src/input/api/InputAPI.h
Normal file
79
src/input/api/InputAPI.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/helpers/helpers.h"
|
||||
|
||||
namespace InputAPI
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
Keyboard,
|
||||
SDLController,
|
||||
XInput,
|
||||
DirectInput,
|
||||
DSUClient,
|
||||
GameCube,
|
||||
Wiimote,
|
||||
|
||||
WGIGamepad,
|
||||
WGIRawController,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
constexpr std::string_view to_string(Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Keyboard:
|
||||
return "Keyboard";
|
||||
case DirectInput:
|
||||
return "DirectInput";
|
||||
case XInput:
|
||||
return "XInput";
|
||||
case Wiimote:
|
||||
return "Wiimote";
|
||||
case GameCube:
|
||||
return "GameCube";
|
||||
case DSUClient:
|
||||
return "DSUController";
|
||||
case WGIGamepad:
|
||||
return "WGIGamepad";
|
||||
case WGIRawController:
|
||||
return "WGIRawController";
|
||||
case SDLController:
|
||||
return "SDLController";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw std::runtime_error(fmt::format("unknown input api: {}", to_underlying(type)));
|
||||
}
|
||||
|
||||
constexpr Type from_string(std::string_view str)
|
||||
{
|
||||
if (str == to_string(Keyboard))
|
||||
return Keyboard;
|
||||
else if (str == to_string(DirectInput))
|
||||
return DirectInput;
|
||||
else if (str == to_string(XInput))
|
||||
return XInput;
|
||||
else if (str == to_string(Wiimote))
|
||||
return Wiimote;
|
||||
else if (str == to_string(GameCube))
|
||||
return GameCube;
|
||||
else if (str == to_string(DSUClient))
|
||||
return DSUClient;
|
||||
else if (str == to_string(SDLController))
|
||||
return SDLController;
|
||||
else if (str == "DSU") // legacy
|
||||
return DSUClient;
|
||||
|
||||
//else if (str == "WGIGamepad")
|
||||
// return WGIGamepad;
|
||||
//
|
||||
//else if (str == "WGIRawController")
|
||||
// return WGIRawController;
|
||||
|
||||
throw std::runtime_error(fmt::format("unknown input api: {}", str));
|
||||
}
|
||||
}
|
66
src/input/api/Keyboard/KeyboardController.cpp
Normal file
66
src/input/api/Keyboard/KeyboardController.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include "input/api/Keyboard/KeyboardController.h"
|
||||
|
||||
#include "gui/guiWrapper.h"
|
||||
|
||||
KeyboardController::KeyboardController()
|
||||
: base_type("keyboard", "Keyboard")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string KeyboardController::get_button_name(uint64 button) const
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
LONG scan_code = MapVirtualKeyA((UINT)button, MAPVK_VK_TO_VSC_EX);
|
||||
if(HIBYTE(scan_code))
|
||||
scan_code |= 0x100;
|
||||
|
||||
// because MapVirtualKey strips the extended bit for some keys
|
||||
switch (button)
|
||||
{
|
||||
case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN: // arrow keys
|
||||
case VK_PRIOR: case VK_NEXT: // page up and page down
|
||||
case VK_END: case VK_HOME:
|
||||
case VK_INSERT: case VK_DELETE:
|
||||
case VK_DIVIDE: // numpad slash
|
||||
case VK_NUMLOCK:
|
||||
{
|
||||
scan_code |= 0x100; // set extended bit
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scan_code <<= 16;
|
||||
|
||||
char key_name[128];
|
||||
if (GetKeyNameTextA(scan_code, key_name, std::size(key_name)) != 0)
|
||||
return key_name;
|
||||
#endif
|
||||
|
||||
return fmt::format("key_{}", button);
|
||||
}
|
||||
|
||||
extern WindowInfo g_window_info;
|
||||
|
||||
ControllerState KeyboardController::raw_state()
|
||||
{
|
||||
ControllerState result{};
|
||||
if (g_window_info.app_active)
|
||||
{
|
||||
static_assert(result.buttons.size() == std::size(g_window_info.keydown), "invalid size");
|
||||
for (uint32 i = wxKeyCode::WXK_BACK; i < result.buttons.size(); ++i)
|
||||
{
|
||||
if(const bool v = g_window_info.keydown[i])
|
||||
{
|
||||
result.buttons.set(i, v);
|
||||
}
|
||||
}
|
||||
// ignore generic key codes on Windows when there is also a left/right variant
|
||||
#if BOOST_OS_WINDOWS
|
||||
result.buttons.set(VK_SHIFT, false);
|
||||
result.buttons.set(VK_CONTROL, false);
|
||||
result.buttons.set(VK_MENU, false);
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
}
|
27
src/input/api/Keyboard/KeyboardController.h
Normal file
27
src/input/api/Keyboard/KeyboardController.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/Keyboard/KeyboardControllerProvider.h"
|
||||
#include "input/api/Controller.h"
|
||||
|
||||
class KeyboardController : public Controller<KeyboardControllerProvider>
|
||||
{
|
||||
public:
|
||||
KeyboardController();
|
||||
|
||||
std::string_view api_name() const override // TODO: use constexpr virtual function with c++20
|
||||
{
|
||||
static_assert(to_string(InputAPI::Keyboard) == "Keyboard");
|
||||
return to_string(InputAPI::Keyboard);
|
||||
}
|
||||
InputAPI::Type api() const override { return InputAPI::Keyboard; }
|
||||
|
||||
bool is_connected() override { return true; }
|
||||
|
||||
bool has_axis() const override { return false; }
|
||||
|
||||
std::string get_button_name(uint64 button) const override;
|
||||
|
||||
protected:
|
||||
ControllerState raw_state() override;
|
||||
|
||||
};
|
10
src/input/api/Keyboard/KeyboardControllerProvider.cpp
Normal file
10
src/input/api/Keyboard/KeyboardControllerProvider.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include "input/api/Keyboard/KeyboardControllerProvider.h"
|
||||
|
||||
#include "input/api/Keyboard/KeyboardController.h"
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> KeyboardControllerProvider::get_controllers()
|
||||
{
|
||||
std::vector<std::shared_ptr<ControllerBase>> result;
|
||||
result.emplace_back(std::make_shared<KeyboardController>());
|
||||
return result;
|
||||
}
|
17
src/input/api/Keyboard/KeyboardControllerProvider.h
Normal file
17
src/input/api/Keyboard/KeyboardControllerProvider.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/ControllerProvider.h"
|
||||
|
||||
#ifndef HAS_KEYBOARD
|
||||
#define HAS_KEYBOARD 1
|
||||
#endif
|
||||
|
||||
class KeyboardControllerProvider : public ControllerProviderBase
|
||||
{
|
||||
friend class KeyboardController;
|
||||
public:
|
||||
inline static InputAPI::Type kAPIType = InputAPI::Keyboard;
|
||||
InputAPI::Type api() const override { return kAPIType; }
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
|
||||
};
|
173
src/input/api/SDL/SDLController.cpp
Normal file
173
src/input/api/SDL/SDLController.cpp
Normal file
|
@ -0,0 +1,173 @@
|
|||
#include "input/api/SDL/SDLController.h"
|
||||
|
||||
#include "input/api/SDL/SDLControllerProvider.h"
|
||||
|
||||
SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index)
|
||||
: base_type(fmt::format("{}_", guid_index), fmt::format("Controller {}", guid_index + 1)), m_guid_index(guid_index),
|
||||
m_guid(guid)
|
||||
{
|
||||
char tmp[64];
|
||||
SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp));
|
||||
m_uuid += tmp;
|
||||
}
|
||||
|
||||
SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name)
|
||||
: base_type(fmt::format("{}_", guid_index), display_name), m_guid_index(guid_index), m_guid(guid)
|
||||
{
|
||||
char tmp[64];
|
||||
SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp));
|
||||
m_uuid += tmp;
|
||||
}
|
||||
|
||||
SDLController::~SDLController()
|
||||
{
|
||||
if (m_controller)
|
||||
SDL_GameControllerClose(m_controller);
|
||||
}
|
||||
|
||||
bool SDLController::is_connected()
|
||||
{
|
||||
std::scoped_lock lock(m_controller_mutex);
|
||||
if (!m_controller)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SDL_GameControllerGetAttached(m_controller))
|
||||
{
|
||||
m_controller = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDLController::connect()
|
||||
{
|
||||
if (is_connected())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
m_has_rumble = false;
|
||||
|
||||
const auto index = m_provider->get_index(m_guid_index, m_guid);
|
||||
|
||||
std::scoped_lock lock(m_controller_mutex);
|
||||
m_diid = SDL_JoystickGetDeviceInstanceID(index);
|
||||
if (m_diid == -1)
|
||||
return false;
|
||||
|
||||
m_controller = SDL_GameControllerFromInstanceID(m_diid);
|
||||
if (!m_controller)
|
||||
{
|
||||
m_controller = SDL_GameControllerOpen(index);
|
||||
if (!m_controller)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const char* name = SDL_GameControllerName(m_controller))
|
||||
m_display_name = name;
|
||||
|
||||
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i)
|
||||
{
|
||||
m_buttons[i] = SDL_GameControllerHasButton(m_controller, (SDL_GameControllerButton)i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i)
|
||||
{
|
||||
m_axis[i] = SDL_GameControllerHasAxis(m_controller, (SDL_GameControllerAxis)i);
|
||||
}
|
||||
|
||||
if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_ACCEL))
|
||||
{
|
||||
m_has_accel = true;
|
||||
SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||
}
|
||||
|
||||
if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_GYRO))
|
||||
{
|
||||
m_has_gyro = true;
|
||||
SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_GYRO, SDL_TRUE);
|
||||
}
|
||||
|
||||
m_has_rumble = SDL_GameControllerRumble(m_controller, 0, 0, 0) == 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDLController::start_rumble()
|
||||
{
|
||||
std::scoped_lock lock(m_controller_mutex);
|
||||
if (is_connected() && !m_has_rumble)
|
||||
return;
|
||||
|
||||
if (m_settings.rumble <= 0)
|
||||
return;
|
||||
|
||||
SDL_GameControllerRumble(m_controller, (Uint16)(m_settings.rumble * 0xFFFF), (Uint16)(m_settings.rumble * 0xFFFF),
|
||||
5 * 1000);
|
||||
}
|
||||
|
||||
void SDLController::stop_rumble()
|
||||
{
|
||||
std::scoped_lock lock(m_controller_mutex);
|
||||
if (is_connected() && !m_has_rumble)
|
||||
return;
|
||||
|
||||
SDL_GameControllerRumble(m_controller, 0, 0, 0);
|
||||
}
|
||||
|
||||
MotionSample SDLController::get_motion_sample()
|
||||
{
|
||||
if (is_connected() && has_motion())
|
||||
{
|
||||
return m_provider->motion_sample(m_diid);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string SDLController::get_button_name(uint64 button) const
|
||||
{
|
||||
if (const char* name = SDL_GameControllerGetStringForButton((SDL_GameControllerButton)button))
|
||||
return name;
|
||||
|
||||
return base_type::get_button_name(button);
|
||||
}
|
||||
|
||||
ControllerState SDLController::raw_state()
|
||||
{
|
||||
ControllerState result{};
|
||||
|
||||
std::scoped_lock lock(m_controller_mutex);
|
||||
if (!is_connected())
|
||||
return result;
|
||||
|
||||
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i)
|
||||
{
|
||||
if (m_buttons[i] && SDL_GameControllerGetButton(m_controller, (SDL_GameControllerButton)i))
|
||||
{
|
||||
result.buttons.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_axis[SDL_CONTROLLER_AXIS_LEFTX])
|
||||
result.axis.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTX) / 32767.0f;
|
||||
|
||||
if (m_axis[SDL_CONTROLLER_AXIS_LEFTY])
|
||||
result.axis.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTY) / 32767.0f;
|
||||
|
||||
if (m_axis[SDL_CONTROLLER_AXIS_RIGHTX])
|
||||
result.rotation.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTX) / 32767.0f;
|
||||
|
||||
if (m_axis[SDL_CONTROLLER_AXIS_RIGHTY])
|
||||
result.rotation.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTY) / 32767.0f;
|
||||
|
||||
if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERLEFT])
|
||||
result.trigger.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32767.0f;
|
||||
|
||||
if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERRIGHT])
|
||||
result.trigger.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32767.0f;
|
||||
|
||||
return result;
|
||||
}
|
60
src/input/api/SDL/SDLController.h
Normal file
60
src/input/api/SDL/SDLController.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/SDL/SDLControllerProvider.h"
|
||||
|
||||
#include <SDL2/SDL_gamecontroller.h>
|
||||
|
||||
class SDLController : public Controller<SDLControllerProvider>
|
||||
{
|
||||
public:
|
||||
SDLController(const SDL_JoystickGUID& guid, size_t guid_index);
|
||||
SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name);
|
||||
|
||||
~SDLController() override;
|
||||
|
||||
std::string_view api_name() const override
|
||||
{
|
||||
static_assert(to_string(InputAPI::SDLController) == "SDLController");
|
||||
return to_string(InputAPI::SDLController);
|
||||
}
|
||||
InputAPI::Type api() const override { return InputAPI::SDLController; }
|
||||
|
||||
bool is_connected() override;
|
||||
bool connect() override;
|
||||
|
||||
bool has_motion() override { return m_has_gyro && m_has_accel; }
|
||||
bool has_rumble() override { return m_has_rumble; }
|
||||
|
||||
void start_rumble() override;
|
||||
void stop_rumble() override;
|
||||
|
||||
MotionSample get_motion_sample() override;
|
||||
|
||||
std::string get_button_name(uint64 button) const override;
|
||||
const SDL_JoystickGUID& get_guid() const { return m_guid; }
|
||||
|
||||
constexpr static SDL_JoystickGUID kLeftJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00,0x68 ,0x00 };
|
||||
constexpr static SDL_JoystickGUID kRightJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 };
|
||||
constexpr static SDL_JoystickGUID kSwitchProController{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 };
|
||||
|
||||
protected:
|
||||
ControllerState raw_state() override;
|
||||
|
||||
private:
|
||||
inline static SDL_JoystickGUID kEmptyGUID{};
|
||||
|
||||
size_t m_guid_index;
|
||||
SDL_JoystickGUID m_guid;
|
||||
std::recursive_mutex m_controller_mutex;
|
||||
SDL_GameController* m_controller = nullptr;
|
||||
SDL_JoystickID m_diid = -1;
|
||||
|
||||
bool m_has_gyro = false;
|
||||
bool m_has_accel = false;
|
||||
bool m_has_rumble = false;
|
||||
|
||||
std::array<bool, SDL_CONTROLLER_BUTTON_MAX> m_buttons{};
|
||||
std::array<bool, SDL_CONTROLLER_AXIS_MAX> m_axis{};
|
||||
};
|
||||
|
244
src/input/api/SDL/SDLControllerProvider.cpp
Normal file
244
src/input/api/SDL/SDLControllerProvider.cpp
Normal file
|
@ -0,0 +1,244 @@
|
|||
#include "input/api/SDL/SDLControllerProvider.h"
|
||||
|
||||
#include "input/api/SDL/SDLController.h"
|
||||
#include "util/helpers/TempState.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <boost/functional/hash.hpp>
|
||||
|
||||
struct SDL_JoystickGUIDHash
|
||||
{
|
||||
std::size_t operator()(const SDL_JoystickGUID& guid) const
|
||||
{
|
||||
return boost::hash_value(guid.data);
|
||||
}
|
||||
};
|
||||
|
||||
SDLControllerProvider::SDLControllerProvider()
|
||||
{
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1");
|
||||
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1");
|
||||
|
||||
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) < 0)
|
||||
throw std::runtime_error(fmt::format("couldn't initialize SDL: %s", SDL_GetError()));
|
||||
|
||||
|
||||
if (SDL_GameControllerEventState(SDL_ENABLE) < 0) {
|
||||
forceLog_printf("Couldn't enable SDL gamecontroller event polling: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
m_running = true;
|
||||
m_thread = std::thread(&SDLControllerProvider::event_thread, this);
|
||||
}
|
||||
|
||||
SDLControllerProvider::~SDLControllerProvider()
|
||||
{
|
||||
if (m_running)
|
||||
{
|
||||
m_running = false;
|
||||
// wake the thread with a quit event if it's currently waiting for events
|
||||
SDL_Event evt;
|
||||
evt.type = SDL_QUIT;
|
||||
SDL_PushEvent(&evt);
|
||||
// wait until thread exited
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> SDLControllerProvider::get_controllers()
|
||||
{
|
||||
std::vector<std::shared_ptr<ControllerBase>> result;
|
||||
|
||||
std::unordered_map<SDL_JoystickGUID, size_t, SDL_JoystickGUIDHash> guid_counter;
|
||||
|
||||
TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks);
|
||||
for (int i = 0; i < SDL_NumJoysticks(); ++i)
|
||||
{
|
||||
if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER)
|
||||
{
|
||||
const auto guid = SDL_JoystickGetDeviceGUID(i);
|
||||
|
||||
const auto it = guid_counter.try_emplace(guid, 0);
|
||||
|
||||
if (auto* controller = SDL_GameControllerOpen(i))
|
||||
{
|
||||
const char* name = SDL_GameControllerName(controller);
|
||||
|
||||
result.emplace_back(std::make_shared<SDLController>(guid, it.first->second, name));
|
||||
SDL_GameControllerClose(controller);
|
||||
}
|
||||
else
|
||||
result.emplace_back(std::make_shared<SDLController>(guid, it.first->second));
|
||||
|
||||
++it.first->second;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int SDLControllerProvider::get_index(size_t guid_index, const SDL_JoystickGUID& guid) const
|
||||
{
|
||||
size_t index = 0;
|
||||
|
||||
TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks);
|
||||
for (int i = 0; i < SDL_NumJoysticks(); ++i)
|
||||
{
|
||||
if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER)
|
||||
{
|
||||
if(guid == SDL_JoystickGetDeviceGUID(i))
|
||||
{
|
||||
if (index == guid_index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
MotionSample SDLControllerProvider::motion_sample(int diid)
|
||||
{
|
||||
std::scoped_lock lock(m_motion_data_mtx[diid]);
|
||||
return m_motion_data[diid];
|
||||
}
|
||||
|
||||
void SDLControllerProvider::event_thread()
|
||||
{
|
||||
SetThreadName("SDLControllerProvider::event_thread");
|
||||
while (m_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
SDL_Event event{};
|
||||
SDL_WaitEvent(&event);
|
||||
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
m_running = false;
|
||||
return;
|
||||
|
||||
case SDL_CONTROLLERAXISMOTION: /**< Game controller axis motion */
|
||||
{
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERBUTTONDOWN: /**< Game controller button pressed */
|
||||
{
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERBUTTONUP: /**< Game controller button released */
|
||||
{
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERDEVICEADDED: /**< A new Game controller has been inserted into the system */
|
||||
{
|
||||
InputManager::instance().on_device_changed();
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERDEVICEREMOVED: /**< An opened Game controller has been removed */
|
||||
{
|
||||
InputManager::instance().on_device_changed();
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERDEVICEREMAPPED: /**< The controller mapping was updated */
|
||||
{
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERTOUCHPADDOWN: /**< Game controller touchpad was touched */
|
||||
{
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERTOUCHPADMOTION: /**< Game controller touchpad finger was moved */
|
||||
{
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERTOUCHPADUP: /**< Game controller touchpad finger was lifted */
|
||||
{
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERSENSORUPDATE: /**< Game controller sensor was updated */
|
||||
{
|
||||
const auto index = event.csensor.which;
|
||||
const auto ts = event.csensor.timestamp;
|
||||
auto& motionTracking = m_motion_tracking[index];
|
||||
|
||||
if (event.csensor.sensor == SDL_SENSOR_ACCEL)
|
||||
{
|
||||
const auto dif = ts - motionTracking.lastTimestampAccel;
|
||||
if (dif <= 0)
|
||||
break;
|
||||
|
||||
if (dif >= 10000)
|
||||
{
|
||||
motionTracking.hasAcc = false;
|
||||
motionTracking.hasGyro = false;
|
||||
motionTracking.lastTimestampAccel = ts;
|
||||
break;
|
||||
}
|
||||
|
||||
motionTracking.lastTimestampAccel = ts;
|
||||
motionTracking.acc[0] = -event.csensor.data[0] / 9.81f;
|
||||
motionTracking.acc[1] = -event.csensor.data[1] / 9.81f;
|
||||
motionTracking.acc[2] = -event.csensor.data[2] / 9.81f;
|
||||
motionTracking.hasAcc = true;
|
||||
}
|
||||
if (event.csensor.sensor == SDL_SENSOR_GYRO)
|
||||
{
|
||||
const auto dif = ts - motionTracking.lastTimestampGyro;
|
||||
if (dif <= 0)
|
||||
break;
|
||||
|
||||
if (dif >= 10000)
|
||||
{
|
||||
motionTracking.hasAcc = false;
|
||||
motionTracking.hasGyro = false;
|
||||
motionTracking.lastTimestampGyro = ts;
|
||||
break;
|
||||
}
|
||||
motionTracking.lastTimestampGyro = ts;
|
||||
motionTracking.gyro[0] = event.csensor.data[0];
|
||||
motionTracking.gyro[1] = -event.csensor.data[1];
|
||||
motionTracking.gyro[2] = -event.csensor.data[2];
|
||||
motionTracking.hasGyro = true;
|
||||
}
|
||||
if (motionTracking.hasAcc && motionTracking.hasGyro)
|
||||
{
|
||||
auto ts = std::max(motionTracking.lastTimestampGyro, motionTracking.lastTimestampAccel);
|
||||
if (ts > motionTracking.lastTimestampIntegrate)
|
||||
{
|
||||
const auto tsDif = ts - motionTracking.lastTimestampIntegrate;
|
||||
motionTracking.lastTimestampIntegrate = ts;
|
||||
float tsDifD = (float)tsDif / 1000.0f;
|
||||
if (tsDifD >= 1.0f)
|
||||
tsDifD = 1.0f;
|
||||
m_motion_handler[index].processMotionSample(tsDifD, motionTracking.gyro.x, motionTracking.gyro.y, motionTracking.gyro.z, motionTracking.acc.x, -motionTracking.acc.y, -motionTracking.acc.z);
|
||||
|
||||
std::scoped_lock lock(m_motion_data_mtx[index]);
|
||||
m_motion_data[index] = m_motion_handler[index].getMotionSample();
|
||||
}
|
||||
motionTracking.hasAcc = false;
|
||||
motionTracking.hasGyro = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
src/input/api/SDL/SDLControllerProvider.h
Normal file
54
src/input/api/SDL/SDLControllerProvider.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
#include <SDL2/SDL_joystick.h>
|
||||
#include "input/motion/MotionHandler.h"
|
||||
#include "input/api/ControllerProvider.h"
|
||||
|
||||
#ifndef HAS_SDL
|
||||
#define HAS_SDL 1
|
||||
#endif
|
||||
|
||||
static bool operator==(const SDL_JoystickGUID& g1, const SDL_JoystickGUID& g2)
|
||||
{
|
||||
return memcmp(&g1, &g2, sizeof(SDL_JoystickGUID)) == 0;
|
||||
}
|
||||
|
||||
class SDLControllerProvider : public ControllerProviderBase
|
||||
{
|
||||
friend class SDLController;
|
||||
public:
|
||||
SDLControllerProvider();
|
||||
~SDLControllerProvider();
|
||||
|
||||
inline static InputAPI::Type kAPIType = InputAPI::SDLController;
|
||||
InputAPI::Type api() const override { return kAPIType; }
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
|
||||
|
||||
int get_index(size_t guid_index, const SDL_JoystickGUID& guid) const;
|
||||
|
||||
MotionSample motion_sample(int diid);
|
||||
|
||||
private:
|
||||
void event_thread();
|
||||
|
||||
std::atomic_bool m_running = false;
|
||||
std::thread m_thread;
|
||||
|
||||
std::array<WiiUMotionHandler, 8> m_motion_handler{};
|
||||
std::array<MotionSample, 8> m_motion_data{};
|
||||
std::array<std::mutex, 8> m_motion_data_mtx{};
|
||||
|
||||
struct MotionInfoTracking
|
||||
{
|
||||
uint64 lastTimestampGyro{};
|
||||
uint64 lastTimestampAccel{};
|
||||
uint64 lastTimestampIntegrate{};
|
||||
bool hasGyro{};
|
||||
bool hasAcc{};
|
||||
glm::vec3 gyro{};
|
||||
glm::vec3 acc{};
|
||||
};
|
||||
|
||||
std::array<MotionInfoTracking, 8> m_motion_tracking{};
|
||||
|
||||
};
|
233
src/input/api/Wiimote/NativeWiimoteController.cpp
Normal file
233
src/input/api/Wiimote/NativeWiimoteController.cpp
Normal file
|
@ -0,0 +1,233 @@
|
|||
#include "input/api/Wiimote/NativeWiimoteController.h"
|
||||
|
||||
#include "input/api/Wiimote/WiimoteControllerProvider.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
NativeWiimoteController::NativeWiimoteController(size_t index)
|
||||
: base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)), m_index(index)
|
||||
{
|
||||
m_settings.motion = true;
|
||||
}
|
||||
|
||||
void NativeWiimoteController::save(pugi::xml_node& node)
|
||||
{
|
||||
base_type::save(node);
|
||||
|
||||
node.append_child("packet_delay").append_child(pugi::node_pcdata).set_value(
|
||||
fmt::format("{}", m_packet_delay).c_str());
|
||||
}
|
||||
|
||||
void NativeWiimoteController::load(const pugi::xml_node& node)
|
||||
{
|
||||
base_type::load(node);
|
||||
|
||||
if (const auto value = node.child("packet_delay"))
|
||||
m_packet_delay = ConvertString<uint32>(value.child_value());
|
||||
}
|
||||
|
||||
bool NativeWiimoteController::connect()
|
||||
{
|
||||
if (is_connected())
|
||||
return true;
|
||||
|
||||
if (!m_provider->is_registered_device(m_index))
|
||||
{
|
||||
m_provider->get_controllers();
|
||||
}
|
||||
|
||||
if (m_provider->is_connected(m_index))
|
||||
{
|
||||
m_provider->set_packet_delay(m_index, m_packet_delay);
|
||||
m_provider->set_led(m_index, m_player_index);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NativeWiimoteController::is_connected()
|
||||
{
|
||||
if (m_provider->is_connected(m_index))
|
||||
{
|
||||
m_provider->set_packet_delay(m_index, m_packet_delay);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void NativeWiimoteController::set_player_index(size_t player_index)
|
||||
{
|
||||
m_player_index = player_index;
|
||||
m_provider->set_led(m_index, m_player_index);
|
||||
}
|
||||
|
||||
NativeWiimoteController::Extension NativeWiimoteController::get_extension() const
|
||||
{
|
||||
Extension result = None;
|
||||
const auto ext = m_provider->get_state(m_index).m_extension;
|
||||
if (std::holds_alternative<NunchuckData>(ext))
|
||||
result = Nunchuck;
|
||||
else if (std::holds_alternative<ClassicData>(ext))
|
||||
result = Classic;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool NativeWiimoteController::is_mpls_attached() const
|
||||
{
|
||||
return m_provider->get_state(m_index).m_motion_plus.has_value();
|
||||
}
|
||||
|
||||
bool NativeWiimoteController::has_position()
|
||||
{
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
return std::any_of(state.ir_camera.dots.cbegin(), state.ir_camera.dots.cend(),
|
||||
[](const IRDot& v) { return v.visible; });
|
||||
}
|
||||
|
||||
glm::vec2 NativeWiimoteController::get_position()
|
||||
{
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
return state.ir_camera.position;
|
||||
}
|
||||
|
||||
glm::vec2 NativeWiimoteController::get_prev_position()
|
||||
{
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
return state.ir_camera.m_prev_position;
|
||||
}
|
||||
|
||||
bool NativeWiimoteController::has_low_battery()
|
||||
{
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
return HAS_FLAG(state.flags, kBatteryEmpty);
|
||||
}
|
||||
|
||||
void NativeWiimoteController::start_rumble()
|
||||
{
|
||||
if (m_settings.rumble < 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_provider->set_rumble(m_index, true);
|
||||
}
|
||||
|
||||
void NativeWiimoteController::stop_rumble()
|
||||
{
|
||||
m_provider->set_rumble(m_index, false);
|
||||
}
|
||||
|
||||
MotionSample NativeWiimoteController::get_motion_sample()
|
||||
{
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
return state.motion_sample;
|
||||
}
|
||||
|
||||
MotionSample NativeWiimoteController::get_nunchuck_motion_sample() const
|
||||
{
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
if (std::holds_alternative<NunchuckData>(state.m_extension))
|
||||
{
|
||||
return std::get<NunchuckData>(state.m_extension).motion_sample;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string NativeWiimoteController::get_button_name(uint64 button) const
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case kWiimoteButton_A: return "A";
|
||||
case kWiimoteButton_B: return "B";
|
||||
|
||||
case kWiimoteButton_One: return "1";
|
||||
case kWiimoteButton_Two: return "2";
|
||||
|
||||
case kWiimoteButton_Plus: return "+";
|
||||
case kWiimoteButton_Minus: return "-";
|
||||
|
||||
case kWiimoteButton_Home: return "HOME";
|
||||
|
||||
case kWiimoteButton_Up: return "UP";
|
||||
case kWiimoteButton_Down: return "DOWN";
|
||||
case kWiimoteButton_Left: return "LEFT";
|
||||
case kWiimoteButton_Right: return "RIGHT";
|
||||
|
||||
// nunchuck
|
||||
case kWiimoteButton_C: return "C";
|
||||
case kWiimoteButton_Z: return "Z";
|
||||
|
||||
// classic
|
||||
case kHighestWiimote + kClassicButton_A: return "A";
|
||||
case kHighestWiimote + kClassicButton_B: return "B";
|
||||
case kHighestWiimote + kClassicButton_Y: return "Y";
|
||||
case kHighestWiimote + kClassicButton_X: return "X";
|
||||
|
||||
case kHighestWiimote + kClassicButton_Plus: return "+";
|
||||
case kHighestWiimote + kClassicButton_Minus: return "-";
|
||||
|
||||
case kHighestWiimote + kClassicButton_Home: return "HOME";
|
||||
|
||||
case kHighestWiimote + kClassicButton_Up: return "UP";
|
||||
case kHighestWiimote + kClassicButton_Down: return "DOWN";
|
||||
case kHighestWiimote + kClassicButton_Left: return "LEFT";
|
||||
case kHighestWiimote + kClassicButton_Right: return "RIGHT";
|
||||
|
||||
case kHighestWiimote + kClassicButton_L: return "L";
|
||||
case kHighestWiimote + kClassicButton_R: return "R";
|
||||
|
||||
case kHighestWiimote + kClassicButton_ZL: return "ZL";
|
||||
case kHighestWiimote + kClassicButton_ZR: return "ZR";
|
||||
}
|
||||
|
||||
return base_type::get_button_name(button);
|
||||
}
|
||||
|
||||
uint32 NativeWiimoteController::get_packet_delay()
|
||||
{
|
||||
m_packet_delay = m_provider->get_packet_delay(m_index);
|
||||
return m_packet_delay;
|
||||
}
|
||||
|
||||
void NativeWiimoteController::set_packet_delay(uint32 delay)
|
||||
{
|
||||
m_packet_delay = delay;
|
||||
m_provider->set_packet_delay(m_index, delay);
|
||||
}
|
||||
|
||||
ControllerState NativeWiimoteController::raw_state()
|
||||
{
|
||||
ControllerState result{};
|
||||
if (!is_connected())
|
||||
return result;
|
||||
|
||||
const auto state = m_provider->get_state(m_index);
|
||||
result.buttons = state.buttons;
|
||||
|
||||
if (std::holds_alternative<NunchuckData>(state.m_extension))
|
||||
{
|
||||
const auto nunchuck = std::get<NunchuckData>(state.m_extension);
|
||||
if (nunchuck.c)
|
||||
result.buttons.set(kWiimoteButton_C);
|
||||
|
||||
if (nunchuck.z)
|
||||
result.buttons.set(kWiimoteButton_Z);
|
||||
|
||||
result.axis = nunchuck.axis;
|
||||
}
|
||||
else if (std::holds_alternative<ClassicData>(state.m_extension))
|
||||
{
|
||||
const auto classic = std::get<ClassicData>(state.m_extension);
|
||||
result.buttons |= (uint64)classic.buttons << kHighestWiimote;
|
||||
|
||||
result.axis = classic.left_axis;
|
||||
result.rotation = classic.right_axis;
|
||||
result.trigger = classic.trigger;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
66
src/input/api/Wiimote/NativeWiimoteController.h
Normal file
66
src/input/api/Wiimote/NativeWiimoteController.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/Wiimote/WiimoteControllerProvider.h"
|
||||
|
||||
// todo: find better name because of emulated nameclash
|
||||
class NativeWiimoteController : public Controller<WiimoteControllerProvider>
|
||||
{
|
||||
public:
|
||||
NativeWiimoteController(size_t index);
|
||||
|
||||
enum Extension
|
||||
{
|
||||
None,
|
||||
Nunchuck,
|
||||
Classic,
|
||||
MotionPlus,
|
||||
};
|
||||
|
||||
std::string_view api_name() const override
|
||||
{
|
||||
static_assert(to_string(InputAPI::Wiimote) == "Wiimote");
|
||||
return to_string(InputAPI::Wiimote);
|
||||
}
|
||||
InputAPI::Type api() const override { return InputAPI::Wiimote; }
|
||||
|
||||
void save(pugi::xml_node& node) override;
|
||||
void load(const pugi::xml_node& node) override;
|
||||
|
||||
bool connect() override;
|
||||
bool is_connected() override;
|
||||
|
||||
void set_player_index(size_t player_index);
|
||||
|
||||
Extension get_extension() const;
|
||||
bool is_mpls_attached() const;
|
||||
|
||||
ControllerState raw_state() override;
|
||||
|
||||
bool has_position() override;
|
||||
glm::vec2 get_position() override;
|
||||
glm::vec2 get_prev_position() override;
|
||||
|
||||
bool has_motion() override { return true; }
|
||||
bool has_rumble() override { return true; }
|
||||
|
||||
bool has_battery() override { return true; }
|
||||
bool has_low_battery() override;
|
||||
|
||||
void start_rumble() override;
|
||||
void stop_rumble() override;
|
||||
|
||||
MotionSample get_motion_sample() override;
|
||||
MotionSample get_nunchuck_motion_sample() const;
|
||||
|
||||
std::string get_button_name(uint64 button) const override;
|
||||
|
||||
uint32 get_packet_delay();
|
||||
void set_packet_delay(uint32 delay);
|
||||
|
||||
private:
|
||||
size_t m_index;
|
||||
size_t m_player_index = 0;
|
||||
uint32 m_packet_delay = WiimoteControllerProvider::kDefaultPacketDelay;
|
||||
};
|
||||
|
1056
src/input/api/Wiimote/WiimoteControllerProvider.cpp
Normal file
1056
src/input/api/Wiimote/WiimoteControllerProvider.cpp
Normal file
File diff suppressed because it is too large
Load diff
126
src/input/api/Wiimote/WiimoteControllerProvider.h
Normal file
126
src/input/api/Wiimote/WiimoteControllerProvider.h
Normal file
|
@ -0,0 +1,126 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/motion/MotionHandler.h"
|
||||
#include "input/api/Wiimote/WiimoteDevice.h"
|
||||
#include "input/api/Wiimote/WiimoteMessages.h"
|
||||
|
||||
#include "input/api/ControllerProvider.h"
|
||||
|
||||
#include <list>
|
||||
#include <variant>
|
||||
#include <boost/ptr_container/ptr_vector.hpp>
|
||||
|
||||
#ifndef HAS_WIIMOTE
|
||||
#define HAS_WIIMOTE 1
|
||||
#endif
|
||||
|
||||
#define WIIMOTE_DEBUG 1
|
||||
|
||||
class WiimoteControllerProvider : public ControllerProviderBase
|
||||
{
|
||||
friend class WiimoteController;
|
||||
public:
|
||||
constexpr static uint32 kDefaultPacketDelay = 25;
|
||||
|
||||
WiimoteControllerProvider();
|
||||
~WiimoteControllerProvider();
|
||||
|
||||
inline static InputAPI::Type kAPIType = InputAPI::Wiimote;
|
||||
InputAPI::Type api() const override { return kAPIType; }
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
|
||||
|
||||
bool is_connected(size_t index);
|
||||
bool is_registered_device(size_t index);
|
||||
void set_rumble(size_t index, bool state);
|
||||
void request_status(size_t index);
|
||||
void set_led(size_t index, size_t player_index);
|
||||
|
||||
uint32 get_packet_delay(size_t index);
|
||||
void set_packet_delay(size_t index, uint32 delay);
|
||||
|
||||
struct WiimoteState
|
||||
{
|
||||
uint16 buttons = 0;
|
||||
uint8 flags = 0;
|
||||
uint8 battery_level = 0;
|
||||
|
||||
glm::vec3 m_acceleration{}, m_prev_acceleration{};
|
||||
float m_roll = 0;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point m_last_motion_timestamp{};
|
||||
MotionSample motion_sample{};
|
||||
WiiUMotionHandler motion_handler{};
|
||||
|
||||
bool m_calibrated = false;
|
||||
Calibration m_calib_acceleration{};
|
||||
|
||||
struct IRCamera
|
||||
{
|
||||
IRMode mode = kIRDisabled;
|
||||
std::array<IRDot, 4> dots{}, prev_dots{};
|
||||
|
||||
glm::vec2 position{}, m_prev_position{};
|
||||
glm::vec2 middle {};
|
||||
float distance = 0;
|
||||
std::pair<sint32, sint32> indices{ 0,1 };
|
||||
}ir_camera{};
|
||||
|
||||
std::optional<MotionPlusData> m_motion_plus;
|
||||
std::variant<std::monostate, NunchuckData, ClassicData> m_extension{};
|
||||
};
|
||||
WiimoteState get_state(size_t index);
|
||||
|
||||
|
||||
private:
|
||||
std::atomic_bool m_running = false;
|
||||
std::thread m_reader_thread, m_writer_thread;
|
||||
|
||||
std::shared_mutex m_device_mutex;
|
||||
|
||||
struct Wiimote
|
||||
{
|
||||
Wiimote(WiimoteDevicePtr device)
|
||||
: device(std::move(device)) {}
|
||||
|
||||
WiimoteDevicePtr device;
|
||||
std::atomic_bool connected = true;
|
||||
std::atomic_bool rumble = false;
|
||||
|
||||
std::shared_mutex mutex;
|
||||
WiimoteState state{};
|
||||
|
||||
std::atomic_uint32_t data_delay = kDefaultPacketDelay;
|
||||
std::chrono::high_resolution_clock::time_point data_ts{};
|
||||
};
|
||||
boost::ptr_vector<Wiimote> m_wiimotes;
|
||||
|
||||
std::list<std::pair<size_t,std::vector<uint8>>> m_write_queue;
|
||||
std::mutex m_writer_mutex;
|
||||
std::condition_variable m_writer_cond;
|
||||
|
||||
void reader_thread();
|
||||
void writer_thread();
|
||||
|
||||
void calibrate(size_t index);
|
||||
IRMode set_ir_camera(size_t index, bool state);
|
||||
|
||||
void send_packet(size_t index, std::vector<uint8> data);
|
||||
void send_read_packet(size_t index, MemoryType type, RegisterAddress address, uint16 size);
|
||||
void send_write_packet(size_t index, MemoryType type, RegisterAddress address, const std::vector<uint8>& data);
|
||||
|
||||
void parse_acceleration(WiimoteState& wiimote_state, const uint8*& data);
|
||||
|
||||
void rotate_ir(WiimoteState& wiimote_state);
|
||||
void calculate_ir_position(WiimoteState& wiimote_state);
|
||||
int parse_ir(WiimoteState& wiimote_state, const uint8* data);
|
||||
|
||||
void request_extension(size_t index);
|
||||
void detect_motion_plus(size_t index);
|
||||
void set_motion_plus(size_t index, bool state);
|
||||
|
||||
void update_report_type(size_t index);
|
||||
};
|
||||
|
||||
|
||||
|
16
src/input/api/Wiimote/WiimoteDevice.h
Normal file
16
src/input/api/Wiimote/WiimoteDevice.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
class WiimoteDevice
|
||||
{
|
||||
friend class WiimoteInfo;
|
||||
public:
|
||||
virtual ~WiimoteDevice() = default;
|
||||
|
||||
virtual bool write_data(const std::vector<uint8>& data) = 0;
|
||||
virtual std::optional<std::vector<uint8_t>> read_data() = 0;
|
||||
|
||||
virtual bool operator==(WiimoteDevice& o) const = 0;
|
||||
bool operator!=(WiimoteDevice& o) const { return *this == o; }
|
||||
};
|
||||
|
||||
using WiimoteDevicePtr = std::shared_ptr<WiimoteDevice>;
|
244
src/input/api/Wiimote/WiimoteMessages.h
Normal file
244
src/input/api/Wiimote/WiimoteMessages.h
Normal file
|
@ -0,0 +1,244 @@
|
|||
#pragma once
|
||||
|
||||
// https://wiibrew.org/wiki/Wiimote
|
||||
|
||||
enum InputReportId : uint8
|
||||
{
|
||||
kNone = 0,
|
||||
|
||||
kStatus = 0x20,
|
||||
kRead = 0x21,
|
||||
kWrite = 0x22,
|
||||
|
||||
kDataCore = 0x30,
|
||||
kDataCoreAcc = 0x31,
|
||||
kDataCoreExt8 = 0x32,
|
||||
kDataCoreAccIR = 0x33,
|
||||
kDataCoreExt19 = 0x34,
|
||||
kDataCoreAccExt = 0x35,
|
||||
kDataCoreIRExt = 0x36,
|
||||
kDataCoreAccIRExt = 0x37,
|
||||
kDataExt = 0x3d,
|
||||
};
|
||||
|
||||
enum RegisterAddress : uint32
|
||||
{
|
||||
kRegisterCalibration = 0x16,
|
||||
kRegisterCalibration2 = 0x20, // backup calibration data
|
||||
|
||||
kRegisterIR = 0x4b00030,
|
||||
kRegisterIRSensitivity1 = 0x4b00000,
|
||||
kRegisterIRSensitivity2 = 0x4b0001a,
|
||||
kRegisterIRMode = 0x4b00033,
|
||||
|
||||
kRegisterExtensionEncrypted = 0x4a40040,
|
||||
|
||||
kRegisterExtension1 = 0x4a400f0,
|
||||
kRegisterExtension2 = 0x4a400fb,
|
||||
kRegisterExtensionType = 0x4a400fa,
|
||||
kRegisterExtensionCalibration = 0x4a40020,
|
||||
|
||||
kRegisterMotionPlusDetect = 0x4a600fa,
|
||||
kRegisterMotionPlusInit = 0x4a600f0,
|
||||
kRegisterMotionPlusEnable = 0x4a600fe,
|
||||
};
|
||||
|
||||
enum ExtensionType : uint64
|
||||
{
|
||||
kExtensionNunchuck = 0x0000A4200000,
|
||||
kExtensionClassic = 0x0000A4200101,
|
||||
kExtensionClassicPro = 0x0100A4200101,
|
||||
kExtensionDrawsome = 0xFF00A4200013,
|
||||
kExtensionGuitar = 0x0000A4200103,
|
||||
kExtensionDrums = 0x0100A4200103,
|
||||
kExtensionBalanceBoard = 0x2A2C,
|
||||
|
||||
kExtensionMotionPlus = 0xa6200005,
|
||||
|
||||
kExtensionPartialyInserted = 0xffffffffffff,
|
||||
};
|
||||
|
||||
enum MemoryType : uint8
|
||||
{
|
||||
kEEPROMMemory = 0,
|
||||
kRegisterMemory = 0x4,
|
||||
};
|
||||
|
||||
enum StatusBitmask : uint8
|
||||
{
|
||||
kBatteryEmpty = 0x1,
|
||||
kExtensionConnected = 0x2,
|
||||
kSpeakerEnabled = 0x4,
|
||||
kIREnabled = 0x8,
|
||||
kLed1 = 0x10,
|
||||
kLed2 = 0x20,
|
||||
kLed3 = 0x40,
|
||||
kLed4 = 0x80
|
||||
};
|
||||
|
||||
enum OutputReportId : uint8
|
||||
{
|
||||
kLED = 0x11,
|
||||
kType = 0x12,
|
||||
kIR = 0x13,
|
||||
kSpeakerState = 0x14,
|
||||
kStatusRequest = 0x15,
|
||||
kWriteMemory = 0x16,
|
||||
kReadMemory = 0x17,
|
||||
kSpeakerData = 0x18,
|
||||
kSpeakerMute = 0x19,
|
||||
kIR2 = 0x1A,
|
||||
};
|
||||
|
||||
enum IRMode : uint8
|
||||
{
|
||||
kIRDisabled,
|
||||
kBasicIR = 1,
|
||||
kExtendedIR = 3,
|
||||
kFullIR = 5,
|
||||
};
|
||||
|
||||
enum WiimoteButtons
|
||||
{
|
||||
kWiimoteButton_Left = 0,
|
||||
kWiimoteButton_Right = 1,
|
||||
kWiimoteButton_Down = 2,
|
||||
kWiimoteButton_Up = 3,
|
||||
kWiimoteButton_Plus = 4,
|
||||
|
||||
kWiimoteButton_Two = 8,
|
||||
kWiimoteButton_One = 9,
|
||||
kWiimoteButton_B = 10,
|
||||
kWiimoteButton_A = 11,
|
||||
kWiimoteButton_Minus = 12,
|
||||
|
||||
kWiimoteButton_Home = 15,
|
||||
|
||||
// self defined
|
||||
kWiimoteButton_C = 16,
|
||||
kWiimoteButton_Z = 17,
|
||||
|
||||
kHighestWiimote = 20,
|
||||
};
|
||||
|
||||
enum ClassicButtons
|
||||
{
|
||||
kClassicButton_R = 1,
|
||||
kClassicButton_Plus = 2,
|
||||
kClassicButton_Home = 3,
|
||||
kClassicButton_Minus = 4,
|
||||
kClassicButton_L = 5,
|
||||
kClassicButton_Down = 6,
|
||||
kClassicButton_Right = 7,
|
||||
kClassicButton_Up = 8,
|
||||
kClassicButton_Left = 9,
|
||||
kClassicButton_ZR = 10,
|
||||
kClassicButton_X = 11,
|
||||
kClassicButton_A = 12,
|
||||
kClassicButton_Y = 13,
|
||||
kClassicButton_B = 14,
|
||||
kClassicButton_ZL = 15,
|
||||
};
|
||||
|
||||
struct Calibration
|
||||
{
|
||||
glm::vec<3, uint16> zero{ 0x200, 0x200, 0x200 };
|
||||
glm::vec<3, uint16> gravity{ 0x240, 0x240, 0x240 };
|
||||
};
|
||||
|
||||
struct BasicIR
|
||||
{
|
||||
uint8 x1;
|
||||
uint8 y1;
|
||||
|
||||
struct
|
||||
{
|
||||
uint8 x2 : 2;
|
||||
uint8 y2 : 2;
|
||||
uint8 x1 : 2;
|
||||
uint8 y1 : 2;
|
||||
} bits;
|
||||
static_assert(sizeof(bits) == 1);
|
||||
|
||||
uint8 x2;
|
||||
uint8 y2;
|
||||
};
|
||||
static_assert(sizeof(BasicIR) == 5);
|
||||
|
||||
struct ExtendedIR
|
||||
{
|
||||
uint8 x;
|
||||
uint8 y;
|
||||
struct
|
||||
{
|
||||
uint8 size : 4;
|
||||
uint8 x : 2;
|
||||
uint8 y : 2;
|
||||
} bits;
|
||||
static_assert(sizeof(bits) == 1);
|
||||
};
|
||||
static_assert(sizeof(ExtendedIR) == 3);
|
||||
|
||||
struct IRDot
|
||||
{
|
||||
bool visible = false;
|
||||
glm::vec2 pos;
|
||||
glm::vec<2, uint16> raw;
|
||||
uint32 size;
|
||||
};
|
||||
|
||||
struct IRCamera
|
||||
{
|
||||
IRMode mode;
|
||||
std::array<IRDot, 4> dots{}, prev_dots{};
|
||||
|
||||
glm::vec2 position, m_prev_position;
|
||||
glm::vec2 middle;
|
||||
float distance;
|
||||
std::pair<sint32, sint32> indices{ 0,1 };
|
||||
};
|
||||
|
||||
struct NunchuchCalibration : Calibration
|
||||
{
|
||||
glm::vec<2, uint8> min{};
|
||||
glm::vec<2, uint8> center{ 0x7f, 0x7f };
|
||||
glm::vec<2, uint8> max{ 0xff, 0xff };
|
||||
};
|
||||
|
||||
struct MotionPlusData
|
||||
{
|
||||
Calibration calibration{};
|
||||
|
||||
glm::vec3 orientation; // yaw, roll, pitch
|
||||
bool slow_roll = false;
|
||||
bool slow_pitch = false;
|
||||
bool slow_yaw = false;
|
||||
bool extension_connected = false;
|
||||
};
|
||||
|
||||
struct NunchuckData
|
||||
{
|
||||
glm::vec3 acceleration{}, prev_acceleration{};
|
||||
NunchuchCalibration calibration{};
|
||||
|
||||
bool c = false;
|
||||
bool z = false;
|
||||
|
||||
glm::vec2 axis{};
|
||||
glm::vec<2, uint8> raw_axis{};
|
||||
|
||||
MotionSample motion_sample{};
|
||||
};
|
||||
|
||||
struct ClassicData
|
||||
{
|
||||
glm::vec2 left_axis{};
|
||||
glm::vec<2, uint8> left_raw_axis{};
|
||||
|
||||
glm::vec2 right_axis{};
|
||||
glm::vec<2, uint8> right_raw_axis{};
|
||||
|
||||
glm::vec2 trigger{};
|
||||
glm::vec<2, uint8> raw_trigger{};
|
||||
uint16 buttons = 0;
|
||||
};
|
127
src/input/api/Wiimote/windows/WinWiimoteDevice.cpp
Normal file
127
src/input/api/Wiimote/windows/WinWiimoteDevice.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
#include "input/api/Wiimote/windows/WinWiimoteDevice.h"
|
||||
|
||||
#include <hidsdi.h>
|
||||
#include <SetupAPI.h>
|
||||
|
||||
WinWiimoteDevice::WinWiimoteDevice(HANDLE handle, std::vector<uint8_t> identifier)
|
||||
: m_handle(handle), m_identifier(std::move(identifier))
|
||||
{
|
||||
m_overlapped.hEvent = CreateEvent(nullptr, TRUE, TRUE, nullptr);
|
||||
}
|
||||
|
||||
WinWiimoteDevice::~WinWiimoteDevice()
|
||||
{
|
||||
CancelIo(m_handle);
|
||||
ResetEvent(m_overlapped.hEvent);
|
||||
CloseHandle(m_handle);
|
||||
}
|
||||
|
||||
bool WinWiimoteDevice::write_data(const std::vector<uint8>& data)
|
||||
{
|
||||
return HidD_SetOutputReport(m_handle, (void*)data.data(), (ULONG)data.size());
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> WinWiimoteDevice::read_data()
|
||||
{
|
||||
DWORD read = 0;
|
||||
std::array<uint8_t, 32> buffer{};
|
||||
|
||||
if (!ReadFile(m_handle, buffer.data(), (DWORD)buffer.size(), &read, &m_overlapped))
|
||||
{
|
||||
const auto error = GetLastError();
|
||||
if (error == ERROR_DEVICE_NOT_CONNECTED)
|
||||
return {};
|
||||
else if (error == ERROR_IO_PENDING)
|
||||
{
|
||||
const auto wait_result = WaitForSingleObject(m_overlapped.hEvent, 100);
|
||||
if (wait_result == WAIT_TIMEOUT)
|
||||
{
|
||||
CancelIo(m_handle);
|
||||
ResetEvent(m_overlapped.hEvent);
|
||||
return {};
|
||||
}
|
||||
else if (wait_result == WAIT_FAILED)
|
||||
return {};
|
||||
|
||||
if (GetOverlappedResult(m_handle, &m_overlapped, &read, FALSE) == FALSE)
|
||||
return {};
|
||||
}
|
||||
else if (error == ERROR_INVALID_HANDLE)
|
||||
{
|
||||
ResetEvent(m_overlapped.hEvent);
|
||||
return {};
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
}
|
||||
|
||||
ResetEvent(m_overlapped.hEvent);
|
||||
if (read == 0)
|
||||
return {};
|
||||
|
||||
return {{buffer.cbegin(), buffer.cbegin() + read}};
|
||||
}
|
||||
|
||||
std::vector<WiimoteDevicePtr> WinWiimoteDevice::get_devices()
|
||||
{
|
||||
std::vector<WiimoteDevicePtr> result;
|
||||
|
||||
GUID hid_guid;
|
||||
HidD_GetHidGuid(&hid_guid);
|
||||
|
||||
const auto device_info = SetupDiGetClassDevs(&hid_guid, nullptr, nullptr, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
|
||||
|
||||
for (DWORD index = 0; ; ++index)
|
||||
{
|
||||
SP_DEVICE_INTERFACE_DATA device_data{};
|
||||
device_data.cbSize = sizeof(device_data);
|
||||
if (SetupDiEnumDeviceInterfaces(device_info, nullptr, &hid_guid, index, &device_data) == FALSE)
|
||||
break;
|
||||
|
||||
DWORD device_data_len;
|
||||
if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, nullptr, 0, &device_data_len, nullptr) == FALSE
|
||||
&& GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
continue;
|
||||
|
||||
std::vector<uint8_t> detail_data_buffer;
|
||||
detail_data_buffer.resize(device_data_len);
|
||||
|
||||
const auto detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA)detail_data_buffer.data();
|
||||
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||
|
||||
if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, device_data_len, nullptr, nullptr)
|
||||
== FALSE)
|
||||
continue;
|
||||
|
||||
HANDLE device_handle = CreateFile(detail_data->DevicePath, (GENERIC_READ | GENERIC_WRITE),
|
||||
(FILE_SHARE_READ | FILE_SHARE_WRITE), nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED, nullptr);
|
||||
if (device_handle == INVALID_HANDLE_VALUE)
|
||||
continue;
|
||||
|
||||
HIDD_ATTRIBUTES attributes{};
|
||||
attributes.Size = sizeof(attributes);
|
||||
if (HidD_GetAttributes(device_handle, &attributes) == FALSE)
|
||||
{
|
||||
CloseHandle(device_handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attributes.VendorID != 0x057e || (attributes.ProductID != 0x0306 && attributes.ProductID != 0x0330))
|
||||
{
|
||||
CloseHandle(device_handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
result.emplace_back(std::make_shared<WinWiimoteDevice>(device_handle, detail_data_buffer));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WinWiimoteDevice::operator==(WiimoteDevice& o) const
|
||||
{
|
||||
return m_identifier == static_cast<WinWiimoteDevice&>(o).m_identifier;
|
||||
}
|
24
src/input/api/Wiimote/windows/WinWiimoteDevice.h
Normal file
24
src/input/api/Wiimote/windows/WinWiimoteDevice.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/Wiimote/WiimoteDevice.h"
|
||||
|
||||
class WinWiimoteDevice : public WiimoteDevice
|
||||
{
|
||||
public:
|
||||
WinWiimoteDevice(HANDLE handle, std::vector<uint8_t> identifier);
|
||||
~WinWiimoteDevice();
|
||||
|
||||
bool write_data(const std::vector<uint8>& data) override;
|
||||
std::optional<std::vector<uint8_t>> read_data() override;
|
||||
|
||||
static std::vector<WiimoteDevicePtr> get_devices();
|
||||
|
||||
bool operator==(WiimoteDevice& o) const override;
|
||||
|
||||
private:
|
||||
HANDLE m_handle;
|
||||
OVERLAPPED m_overlapped{};
|
||||
std::vector<uint8_t> m_identifier;
|
||||
};
|
||||
|
||||
using WiimoteDevice_t = WinWiimoteDevice;
|
151
src/input/api/XInput/XInputController.cpp
Normal file
151
src/input/api/XInput/XInputController.cpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
#include "input/api/XInput/XInputController.h"
|
||||
|
||||
XInputController::XInputController(uint32 index)
|
||||
: base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1))
|
||||
{
|
||||
if (index >= XUSER_MAX_COUNT)
|
||||
throw std::runtime_error(fmt::format("invalid xinput index {} (must be smaller than {})", index,
|
||||
XUSER_MAX_COUNT));
|
||||
|
||||
m_index = index;
|
||||
|
||||
m_settings.axis.deadzone = m_settings.rotation.deadzone = m_settings.trigger.deadzone = 0.15f;
|
||||
}
|
||||
|
||||
bool XInputController::connect()
|
||||
{
|
||||
if (m_connected)
|
||||
return true;
|
||||
|
||||
m_has_battery = false;
|
||||
|
||||
XINPUT_CAPABILITIES caps{};
|
||||
m_connected = m_provider->m_XInputGetCapabilities(m_index, XINPUT_FLAG_GAMEPAD, &caps) !=
|
||||
ERROR_DEVICE_NOT_CONNECTED;
|
||||
if (!m_connected) return false;
|
||||
|
||||
m_has_rumble = (caps.Vibration.wLeftMotorSpeed > 0) || (caps.Vibration.wRightMotorSpeed > 0);
|
||||
|
||||
if (m_provider->m_XInputGetBatteryInformation)
|
||||
{
|
||||
XINPUT_BATTERY_INFORMATION battery{};
|
||||
if (m_provider->m_XInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery) == ERROR_SUCCESS)
|
||||
{
|
||||
m_has_battery = (battery.BatteryType == BATTERY_TYPE_ALKALINE || battery.BatteryType ==
|
||||
BATTERY_TYPE_NIMH);
|
||||
}
|
||||
}
|
||||
|
||||
return m_connected;
|
||||
}
|
||||
|
||||
bool XInputController::is_connected()
|
||||
{
|
||||
return m_connected;
|
||||
}
|
||||
|
||||
|
||||
void XInputController::start_rumble()
|
||||
{
|
||||
if (!has_rumble() || m_settings.rumble <= 0)
|
||||
return;
|
||||
|
||||
XINPUT_VIBRATION vibration;
|
||||
vibration.wLeftMotorSpeed = static_cast<WORD>(m_settings.rumble * std::numeric_limits<uint16>::max());
|
||||
vibration.wRightMotorSpeed = static_cast<WORD>(m_settings.rumble * std::numeric_limits<uint16>::max());
|
||||
m_provider->m_XInputSetState(m_index, &vibration);
|
||||
}
|
||||
|
||||
void XInputController::stop_rumble()
|
||||
{
|
||||
if (!has_rumble())
|
||||
return;
|
||||
|
||||
XINPUT_VIBRATION vibration{};
|
||||
m_provider->m_XInputSetState(m_index, &vibration);
|
||||
}
|
||||
|
||||
bool XInputController::has_low_battery()
|
||||
{
|
||||
if (!has_battery())
|
||||
return false;
|
||||
|
||||
XINPUT_BATTERY_INFORMATION battery{};
|
||||
if (m_provider->m_XInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery) == ERROR_SUCCESS)
|
||||
{
|
||||
return (battery.BatteryType == BATTERY_TYPE_ALKALINE || battery.BatteryType == BATTERY_TYPE_NIMH) && battery
|
||||
.BatteryLevel <= BATTERY_LEVEL_LOW;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string XInputController::get_button_name(uint64 button) const
|
||||
{
|
||||
switch (1ULL << button)
|
||||
{
|
||||
case XINPUT_GAMEPAD_A: return "A";
|
||||
case XINPUT_GAMEPAD_B: return "B";
|
||||
case XINPUT_GAMEPAD_X: return "X";
|
||||
case XINPUT_GAMEPAD_Y: return "Y";
|
||||
|
||||
case XINPUT_GAMEPAD_LEFT_SHOULDER: return "L";
|
||||
case XINPUT_GAMEPAD_RIGHT_SHOULDER: return "R";
|
||||
|
||||
case XINPUT_GAMEPAD_START: return "Start";
|
||||
case XINPUT_GAMEPAD_BACK: return "Select";
|
||||
|
||||
case XINPUT_GAMEPAD_LEFT_THUMB: return "L-Stick";
|
||||
case XINPUT_GAMEPAD_RIGHT_THUMB: return "R-Stick";
|
||||
case XINPUT_GAMEPAD_DPAD_UP: return "DPAD-Up";
|
||||
case XINPUT_GAMEPAD_DPAD_DOWN: return "DPAD-Down";
|
||||
case XINPUT_GAMEPAD_DPAD_LEFT: return "DPAD-Left";
|
||||
case XINPUT_GAMEPAD_DPAD_RIGHT: return "DPAD-Right";
|
||||
}
|
||||
|
||||
return Controller::get_button_name(button);
|
||||
}
|
||||
|
||||
ControllerState XInputController::raw_state()
|
||||
{
|
||||
ControllerState result{};
|
||||
if (!m_connected)
|
||||
return result;
|
||||
|
||||
XINPUT_STATE state;
|
||||
if (m_provider->m_XInputGetState(m_index, &state) != ERROR_SUCCESS)
|
||||
{
|
||||
m_connected = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
result.buttons = state.Gamepad.wButtons;
|
||||
|
||||
if (state.Gamepad.sThumbLX > 0)
|
||||
result.axis.x = (float)state.Gamepad.sThumbLX / std::numeric_limits<sint16>::max();
|
||||
else if (state.Gamepad.sThumbLX < 0)
|
||||
result.axis.x = (float)-state.Gamepad.sThumbLX / std::numeric_limits<sint16>::min();
|
||||
|
||||
if (state.Gamepad.sThumbLY > 0)
|
||||
result.axis.y = (float)state.Gamepad.sThumbLY / std::numeric_limits<sint16>::max();
|
||||
else if (state.Gamepad.sThumbLY < 0)
|
||||
result.axis.y = (float)-state.Gamepad.sThumbLY / std::numeric_limits<sint16>::min();
|
||||
|
||||
// Right Stick
|
||||
if (state.Gamepad.sThumbRX > 0)
|
||||
result.rotation.x = (float)state.Gamepad.sThumbRX / std::numeric_limits<sint16>::max();
|
||||
else if (state.Gamepad.sThumbRX < 0)
|
||||
result.rotation.x = (float)-state.Gamepad.sThumbRX / std::numeric_limits<sint16>::min();
|
||||
|
||||
if (state.Gamepad.sThumbRY > 0)
|
||||
result.rotation.y = (float)state.Gamepad.sThumbRY / std::numeric_limits<sint16>::max();
|
||||
else if (state.Gamepad.sThumbRY < 0)
|
||||
result.rotation.y = (float)-state.Gamepad.sThumbRY / std::numeric_limits<sint16>::min();
|
||||
|
||||
// Trigger
|
||||
result.trigger.x = (float)state.Gamepad.bLeftTrigger / std::numeric_limits<uint8>::max();
|
||||
result.trigger.y = (float)state.Gamepad.bRightTrigger / std::numeric_limits<uint8>::max();
|
||||
|
||||
return result;
|
||||
}
|
38
src/input/api/XInput/XInputController.h
Normal file
38
src/input/api/XInput/XInputController.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/XInput/XInputControllerProvider.h"
|
||||
#include "input/api/Controller.h"
|
||||
|
||||
class XInputController : public Controller<XInputControllerProvider>
|
||||
{
|
||||
public:
|
||||
XInputController(uint32 index);
|
||||
|
||||
std::string_view api_name() const override
|
||||
{
|
||||
static_assert(to_string(InputAPI::XInput) == "XInput");
|
||||
return to_string(InputAPI::XInput);
|
||||
}
|
||||
InputAPI::Type api() const override { return InputAPI::XInput; }
|
||||
|
||||
bool connect() override;
|
||||
bool is_connected() override;
|
||||
|
||||
bool has_rumble() override { return m_has_rumble; }
|
||||
bool has_battery() override { return m_has_battery; }
|
||||
bool has_low_battery() override;
|
||||
|
||||
void start_rumble() override;
|
||||
void stop_rumble() override;
|
||||
|
||||
std::string get_button_name(uint64 button) const override;
|
||||
|
||||
protected:
|
||||
ControllerState raw_state() override;
|
||||
|
||||
private:
|
||||
uint32 m_index;
|
||||
bool m_connected = false;
|
||||
bool m_has_battery = false;
|
||||
bool m_has_rumble = false;
|
||||
};
|
57
src/input/api/XInput/XInputControllerProvider.cpp
Normal file
57
src/input/api/XInput/XInputControllerProvider.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#include <Windows.h>
|
||||
|
||||
#include "input/api/XInput/XInputControllerProvider.h"
|
||||
#include "input/api/XInput/XInputController.h"
|
||||
|
||||
XInputControllerProvider::XInputControllerProvider()
|
||||
{
|
||||
// try to load newest to oldest
|
||||
m_module = LoadLibraryA("XInput1_4.DLL");
|
||||
if (!m_module)
|
||||
{
|
||||
m_module = LoadLibraryA("XInput1_3.DLL");
|
||||
if (!m_module)
|
||||
{
|
||||
m_module = LoadLibraryA("XInput9_1_0.dll");
|
||||
if (!m_module)
|
||||
throw std::runtime_error("can't load any xinput dll");
|
||||
}
|
||||
}
|
||||
|
||||
#define GET_XINPUT_PROC(__FUNC__) m_##__FUNC__ = (decltype(m_##__FUNC__))GetProcAddress(m_module, #__FUNC__)
|
||||
GET_XINPUT_PROC(XInputGetCapabilities);
|
||||
GET_XINPUT_PROC(XInputGetState);
|
||||
GET_XINPUT_PROC(XInputSetState);
|
||||
|
||||
if (!m_XInputGetCapabilities || !m_XInputGetState || !m_XInputSetState)
|
||||
{
|
||||
FreeLibrary(m_module);
|
||||
throw std::runtime_error("can't find necessary xinput functions");
|
||||
}
|
||||
|
||||
// only available in XInput1_4 and XInput1_3
|
||||
GET_XINPUT_PROC(XInputGetBatteryInformation);
|
||||
#undef GET_XINPUT_PROC
|
||||
|
||||
}
|
||||
|
||||
XInputControllerProvider::~XInputControllerProvider()
|
||||
{
|
||||
if (m_module)
|
||||
FreeLibrary(m_module);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> XInputControllerProvider::get_controllers()
|
||||
{
|
||||
std::vector<std::shared_ptr<ControllerBase>> result;
|
||||
for(DWORD i = 0; i < XUSER_MAX_COUNT; ++i)
|
||||
{
|
||||
XINPUT_CAPABILITIES caps;
|
||||
if (m_XInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS)
|
||||
{
|
||||
result.emplace_back(std::make_shared<XInputController>(i));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
32
src/input/api/XInput/XInputControllerProvider.h
Normal file
32
src/input/api/XInput/XInputControllerProvider.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
#if BOOST_OS_WINDOWS
|
||||
#include "input/api/ControllerProvider.h"
|
||||
|
||||
#include <Xinput.h>
|
||||
|
||||
#ifndef HAS_XINPUT
|
||||
#define HAS_XINPUT 1
|
||||
#endif
|
||||
|
||||
|
||||
class XInputControllerProvider : public ControllerProviderBase
|
||||
{
|
||||
friend class XInputController;
|
||||
public:
|
||||
XInputControllerProvider();
|
||||
~XInputControllerProvider() override;
|
||||
|
||||
inline static InputAPI::Type kAPIType = InputAPI::XInput;
|
||||
InputAPI::Type api() const override { return kAPIType; }
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
|
||||
|
||||
private:
|
||||
HMODULE m_module = nullptr;
|
||||
decltype(&XInputGetBatteryInformation) m_XInputGetBatteryInformation;
|
||||
decltype(&XInputGetCapabilities) m_XInputGetCapabilities;
|
||||
decltype(&XInputSetState) m_XInputSetState;
|
||||
decltype(&XInputGetState) m_XInputGetState;
|
||||
};
|
||||
|
||||
#endif
|
258
src/input/emulated/ClassicController.cpp
Normal file
258
src/input/emulated/ClassicController.cpp
Normal file
|
@ -0,0 +1,258 @@
|
|||
#include "input/emulated/ClassicController.h"
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/SDL/SDLController.h"
|
||||
|
||||
|
||||
ClassicController::ClassicController(size_t player_index)
|
||||
: WPADController(player_index, kDataFormat_CLASSIC)
|
||||
{
|
||||
}
|
||||
|
||||
uint32 ClassicController::get_emulated_button_flag(uint32 id) const
|
||||
{
|
||||
return s_get_emulated_button_flag(id);
|
||||
}
|
||||
|
||||
uint32 ClassicController::s_get_emulated_button_flag(uint32 id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case kButtonId_A:
|
||||
return kCLButton_A;
|
||||
case kButtonId_B:
|
||||
return kCLButton_B;
|
||||
case kButtonId_X:
|
||||
return kCLButton_X;
|
||||
case kButtonId_Y:
|
||||
return kCLButton_Y;
|
||||
|
||||
case kButtonId_Plus:
|
||||
return kCLButton_Plus;
|
||||
case kButtonId_Minus:
|
||||
return kCLButton_Minus;
|
||||
|
||||
case kButtonId_Up:
|
||||
return kCLButton_Up;
|
||||
case kButtonId_Down:
|
||||
return kCLButton_Down;
|
||||
case kButtonId_Left:
|
||||
return kCLButton_Left;
|
||||
case kButtonId_Right:
|
||||
return kCLButton_Right;
|
||||
|
||||
case kButtonId_L:
|
||||
return kCLButton_L;
|
||||
case kButtonId_ZL:
|
||||
return kCLButton_ZL;
|
||||
case kButtonId_R:
|
||||
return kCLButton_R;
|
||||
case kButtonId_ZR:
|
||||
return kCLButton_ZR;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string_view ClassicController::get_button_name(ButtonId id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case kButtonId_A: return "A";
|
||||
case kButtonId_B: return "B";
|
||||
case kButtonId_X: return "X";
|
||||
case kButtonId_Y: return "Y";
|
||||
case kButtonId_L: return "L";
|
||||
case kButtonId_R: return "R";
|
||||
case kButtonId_ZL: return "ZL";
|
||||
case kButtonId_ZR: return "ZR";
|
||||
|
||||
case kButtonId_Plus: return "+";
|
||||
case kButtonId_Minus: return "-";
|
||||
case kButtonId_Home: return "home";
|
||||
|
||||
case kButtonId_Up: return "up";
|
||||
case kButtonId_Down: return "down";
|
||||
case kButtonId_Left: return "left";
|
||||
case kButtonId_Right: return "right";
|
||||
|
||||
case kButtonId_StickL_Up: return "up";
|
||||
case kButtonId_StickL_Down: return "down";
|
||||
case kButtonId_StickL_Left: return "left";
|
||||
case kButtonId_StickL_Right: return "right";
|
||||
|
||||
case kButtonId_StickR_Up: return "up";
|
||||
case kButtonId_StickR_Down: return "down";
|
||||
case kButtonId_StickR_Left: return "left";
|
||||
case kButtonId_StickR_Right: return "right";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec2 ClassicController::get_axis() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_StickL_Left);
|
||||
const auto right = get_axis_value(kButtonId_StickL_Right);
|
||||
|
||||
const auto up = get_axis_value(kButtonId_StickL_Up);
|
||||
const auto down = get_axis_value(kButtonId_StickL_Down);
|
||||
|
||||
glm::vec2 result;
|
||||
result.x = (left > right) ? -left : right;
|
||||
result.y = (up > down) ? up : -down;
|
||||
return length(result) > 1.0f ? normalize(result) : result;
|
||||
}
|
||||
|
||||
glm::vec2 ClassicController::get_rotation() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_StickR_Left);
|
||||
const auto right = get_axis_value(kButtonId_StickR_Right);
|
||||
|
||||
const auto up = get_axis_value(kButtonId_StickR_Up);
|
||||
const auto down = get_axis_value(kButtonId_StickR_Down);
|
||||
|
||||
glm::vec2 result;
|
||||
result.x = (left > right) ? -left : right;
|
||||
result.y = (up > down) ? up : -down;
|
||||
return length(result) > 1.0f ? normalize(result) : result;
|
||||
}
|
||||
|
||||
glm::vec2 ClassicController::get_trigger() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_ZL);
|
||||
const auto right = get_axis_value(kButtonId_ZR);
|
||||
return { left, right };
|
||||
}
|
||||
|
||||
bool ClassicController::set_default_mapping(const std::shared_ptr<ControllerBase>& controller)
|
||||
{
|
||||
std::vector<std::pair<uint64, uint64>> mapping;
|
||||
switch (controller->api())
|
||||
{
|
||||
case InputAPI::SDLController: {
|
||||
const auto sdl_controller = std::static_pointer_cast<SDLController>(controller);
|
||||
if (sdl_controller->get_guid() == SDLController::kLeftJoyCon)
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_L, kButton9},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
|
||||
{kButtonId_Minus, kButton4},
|
||||
|
||||
{kButtonId_Up, kButton11},
|
||||
{kButtonId_Down, kButton12},
|
||||
{kButtonId_Left, kButton13},
|
||||
{kButtonId_Right, kButton14},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYN},
|
||||
{kButtonId_StickL_Down, kAxisYP},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
};
|
||||
}
|
||||
else if (sdl_controller->get_guid() == SDLController::kRightJoyCon)
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton0},
|
||||
{kButtonId_B, kButton1},
|
||||
{kButtonId_X, kButton2},
|
||||
{kButtonId_Y, kButton3},
|
||||
|
||||
{kButtonId_R, kButton10},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton6},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYN},
|
||||
{kButtonId_StickR_Down, kRotationYP},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton1},
|
||||
{kButtonId_B, kButton0},
|
||||
{kButtonId_X, kButton3},
|
||||
{kButtonId_Y, kButton2},
|
||||
|
||||
{kButtonId_L, kButton9},
|
||||
{kButtonId_R, kButton10},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton6},
|
||||
{kButtonId_Minus, kButton4},
|
||||
|
||||
{kButtonId_Up, kButton11},
|
||||
{kButtonId_Down, kButton12},
|
||||
{kButtonId_Left, kButton13},
|
||||
{kButtonId_Right, kButton14},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYN},
|
||||
{kButtonId_StickL_Down, kAxisYP},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYN},
|
||||
{kButtonId_StickR_Down, kRotationYP},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
}
|
||||
}
|
||||
case InputAPI::XInput:
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton13},
|
||||
{kButtonId_B, kButton12},
|
||||
{kButtonId_X, kButton15},
|
||||
{kButtonId_Y, kButton14},
|
||||
|
||||
{kButtonId_L, kButton8},
|
||||
{kButtonId_R, kButton9},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton4},
|
||||
{kButtonId_Minus, kButton5},
|
||||
|
||||
{kButtonId_Up, kButton0},
|
||||
{kButtonId_Down, kButton1},
|
||||
{kButtonId_Left, kButton2},
|
||||
{kButtonId_Right, kButton3},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYP},
|
||||
{kButtonId_StickL_Down, kAxisYN},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYP},
|
||||
{kButtonId_StickR_Down, kRotationYN},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool mapping_updated = false;
|
||||
std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m)
|
||||
{
|
||||
if (m_mappings.find(m.first) == m_mappings.cend())
|
||||
{
|
||||
set_mapping(m.first, controller, m.second);
|
||||
mapping_updated = true;
|
||||
}
|
||||
});
|
||||
|
||||
return mapping_updated;
|
||||
}
|
72
src/input/emulated/ClassicController.h
Normal file
72
src/input/emulated/ClassicController.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/emulated/WPADController.h"
|
||||
|
||||
class ClassicController : public WPADController
|
||||
{
|
||||
public:
|
||||
enum ButtonId
|
||||
{
|
||||
kButtonId_None,
|
||||
|
||||
kButtonId_A,
|
||||
kButtonId_B,
|
||||
kButtonId_X,
|
||||
kButtonId_Y,
|
||||
|
||||
kButtonId_L,
|
||||
kButtonId_R,
|
||||
kButtonId_ZL,
|
||||
kButtonId_ZR,
|
||||
|
||||
kButtonId_Plus,
|
||||
kButtonId_Minus,
|
||||
kButtonId_Home,
|
||||
|
||||
kButtonId_Up,
|
||||
kButtonId_Down,
|
||||
kButtonId_Left,
|
||||
kButtonId_Right,
|
||||
|
||||
kButtonId_StickL_Up,
|
||||
kButtonId_StickL_Down,
|
||||
kButtonId_StickL_Left,
|
||||
kButtonId_StickL_Right,
|
||||
|
||||
kButtonId_StickR_Up,
|
||||
kButtonId_StickR_Down,
|
||||
kButtonId_StickR_Left,
|
||||
kButtonId_StickR_Right,
|
||||
|
||||
kButtonId_Max,
|
||||
};
|
||||
|
||||
ClassicController(size_t player_index);
|
||||
|
||||
Type type() const override { return Type::Classic; }
|
||||
WPADDeviceType get_device_type() const override { return kWAPDevClassic; }
|
||||
|
||||
uint32 get_emulated_button_flag(uint32 id) const override;
|
||||
size_t get_highest_mapping_id() const override { return kButtonId_Max; }
|
||||
bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; }
|
||||
|
||||
glm::vec2 get_axis() const override;
|
||||
glm::vec2 get_rotation() const override;
|
||||
glm::vec2 get_trigger() const override;
|
||||
|
||||
|
||||
static uint32 s_get_emulated_button_flag(uint32 id);
|
||||
|
||||
static std::string_view get_button_name(ButtonId id);
|
||||
|
||||
bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); }
|
||||
bool is_left_down() const override { return is_mapping_down(kButtonId_Left); }
|
||||
bool is_right_down() const override { return is_mapping_down(kButtonId_Right); }
|
||||
bool is_up_down() const override { return is_mapping_down(kButtonId_Up); }
|
||||
bool is_down_down() const override { return is_mapping_down(kButtonId_Down); }
|
||||
bool is_a_down() const override { return is_mapping_down(kButtonId_A); }
|
||||
bool is_b_down() const override { return is_mapping_down(kButtonId_B); }
|
||||
bool is_home_down() const override { return is_mapping_down(kButtonId_Home); }
|
||||
|
||||
bool set_default_mapping(const std::shared_ptr<ControllerBase>& controller) override;
|
||||
};
|
341
src/input/emulated/EmulatedController.cpp
Normal file
341
src/input/emulated/EmulatedController.cpp
Normal file
|
@ -0,0 +1,341 @@
|
|||
#include "input/emulated/EmulatedController.h"
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
#include "input/api/Wiimote/NativeWiimoteController.h"
|
||||
#endif
|
||||
|
||||
std::string_view EmulatedController::type_to_string(Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case VPAD: return "Wii U GamePad";
|
||||
case Pro: return "Wii U Pro Controller";
|
||||
case Classic: return "Wii U Classic Controller";
|
||||
case Wiimote: return "Wiimote";
|
||||
}
|
||||
|
||||
throw std::runtime_error(fmt::format("unknown emulated controller: {}", to_underlying(type)));
|
||||
}
|
||||
|
||||
EmulatedController::Type EmulatedController::type_from_string(std::string_view str)
|
||||
{
|
||||
if (str == "Wii U GamePad")
|
||||
return VPAD;
|
||||
else if (str == "Wii U Pro Controller")
|
||||
return Pro;
|
||||
else if (str == "Wii U Classic Controller")
|
||||
return Classic;
|
||||
else if (str == "Wiimote")
|
||||
return Wiimote;
|
||||
|
||||
throw std::runtime_error(fmt::format("unknown emulated controller: {}", str));
|
||||
}
|
||||
|
||||
EmulatedController::EmulatedController(size_t player_index)
|
||||
: m_player_index(player_index)
|
||||
{
|
||||
}
|
||||
|
||||
void EmulatedController::calibrate()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
controller->calibrate();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::connect()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
controller->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::update()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for(const auto& controller : m_controllers)
|
||||
{
|
||||
controller->update();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::controllers_update_states()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
controller->update_state();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::start_rumble()
|
||||
{
|
||||
m_rumble = true;
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
controller->start_rumble();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::stop_rumble()
|
||||
{
|
||||
if (!m_rumble)
|
||||
return;
|
||||
|
||||
m_rumble = false;
|
||||
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
controller->stop_rumble();
|
||||
}
|
||||
}
|
||||
|
||||
bool EmulatedController::is_battery_low() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->has_low_battery(); });
|
||||
}
|
||||
|
||||
bool EmulatedController::has_motion() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->use_motion(); });
|
||||
}
|
||||
|
||||
MotionSample EmulatedController::get_motion_data() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
if (controller->use_motion())
|
||||
return controller->get_motion_sample();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool EmulatedController::has_second_motion() const
|
||||
{
|
||||
int motion = 0;
|
||||
std::shared_lock lock(m_mutex);
|
||||
for(const auto& controller : m_controllers)
|
||||
{
|
||||
if(controller->use_motion())
|
||||
{
|
||||
// if wiimote has nunchuck connected, we use its acceleration
|
||||
#if BOOST_OS_WINDOWS
|
||||
if(controller->api() == InputAPI::Wiimote)
|
||||
{
|
||||
if(((NativeWiimoteController*)controller.get())->get_extension() == NativeWiimoteController::Nunchuck)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
motion++;
|
||||
}
|
||||
}
|
||||
|
||||
return motion >= 2;
|
||||
}
|
||||
|
||||
MotionSample EmulatedController::get_second_motion_data() const
|
||||
{
|
||||
int motion = 0;
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
if (controller->use_motion())
|
||||
{
|
||||
// if wiimote has nunchuck connected, we use its acceleration
|
||||
#if BOOST_OS_WINDOWS
|
||||
if (controller->api() == InputAPI::Wiimote)
|
||||
{
|
||||
if (((NativeWiimoteController*)controller.get())->get_extension() == NativeWiimoteController::Nunchuck)
|
||||
{
|
||||
return ((NativeWiimoteController*)controller.get())->get_nunchuck_motion_sample();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
motion++;
|
||||
if(motion == 2)
|
||||
{
|
||||
return controller->get_motion_sample();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool EmulatedController::has_position() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->has_position(); });
|
||||
}
|
||||
|
||||
glm::vec2 EmulatedController::get_position() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
if (controller->has_position())
|
||||
return controller->get_position();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
glm::vec2 EmulatedController::get_prev_position() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& controller : m_controllers)
|
||||
{
|
||||
if (controller->has_position())
|
||||
return controller->get_prev_position();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<ControllerBase> EmulatedController::find_controller(std::string_view uuid, InputAPI::Type type) const
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
const auto it = std::find_if(m_controllers.cbegin(), m_controllers.cend(), [uuid, type](const auto& c) { return c->api() == type && c->uuid() == uuid; });
|
||||
if (it != m_controllers.cend())
|
||||
return *it;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void EmulatedController::add_controller(std::shared_ptr<ControllerBase> controller)
|
||||
{
|
||||
controller->connect();
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
if (const auto wiimote = std::dynamic_pointer_cast<NativeWiimoteController>(controller)) {
|
||||
wiimote->set_player_index(m_player_index);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_controllers.emplace_back(std::move(controller));
|
||||
}
|
||||
|
||||
void EmulatedController::remove_controller(const std::shared_ptr<ControllerBase>& controller)
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
const auto it = std::find(m_controllers.cbegin(), m_controllers.cend(), controller);
|
||||
if (it != m_controllers.cend())
|
||||
{
|
||||
m_controllers.erase(it);
|
||||
|
||||
for(auto m = m_mappings.begin(); m != m_mappings.end();)
|
||||
{
|
||||
if(auto mc = m->second.controller.lock())
|
||||
{
|
||||
if(*mc == *controller)
|
||||
{
|
||||
m = m_mappings.erase(m);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
++m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::clear_controllers()
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_controllers.clear();
|
||||
}
|
||||
|
||||
float EmulatedController::get_axis_value(uint64 mapping) const
|
||||
{
|
||||
const auto it = m_mappings.find(mapping);
|
||||
if (it != m_mappings.cend())
|
||||
{
|
||||
if (const auto controller = it->second.controller.lock()) {
|
||||
return controller->get_axis_value(it->second.button);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool EmulatedController::is_mapping_down(uint64 mapping) const
|
||||
{
|
||||
const auto it = m_mappings.find(mapping);
|
||||
if (it != m_mappings.cend())
|
||||
{
|
||||
if (const auto controller = it->second.controller.lock()) {
|
||||
return controller->get_state().buttons.test(it->second.button);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string EmulatedController::get_mapping_name(uint64 mapping) const
|
||||
{
|
||||
const auto it = m_mappings.find(mapping);
|
||||
if (it != m_mappings.cend())
|
||||
{
|
||||
if (const auto controller = it->second.controller.lock()) {
|
||||
return controller->get_button_name(it->second.button);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<ControllerBase> EmulatedController::get_mapping_controller(uint64 mapping) const
|
||||
{
|
||||
const auto it = m_mappings.find(mapping);
|
||||
if (it != m_mappings.cend())
|
||||
{
|
||||
if (const auto controller = it->second.controller.lock()) {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void EmulatedController::delete_mapping(uint64 mapping)
|
||||
{
|
||||
m_mappings.erase(mapping);
|
||||
}
|
||||
|
||||
void EmulatedController::clear_mappings()
|
||||
{
|
||||
m_mappings.clear();
|
||||
}
|
||||
|
||||
void EmulatedController::set_mapping(uint64 mapping, const std::shared_ptr<ControllerBase>& controller,
|
||||
uint64 button)
|
||||
{
|
||||
m_mappings[mapping] = { controller, button };
|
||||
}
|
||||
|
||||
bool EmulatedController::operator==(const EmulatedController& o) const
|
||||
{
|
||||
return type() == o.type() && m_player_index == o.m_player_index;
|
||||
}
|
||||
|
||||
bool EmulatedController::operator!=(const EmulatedController& o) const
|
||||
{
|
||||
return !(*this == o);
|
||||
}
|
140
src/input/emulated/EmulatedController.h
Normal file
140
src/input/emulated/EmulatedController.h
Normal file
|
@ -0,0 +1,140 @@
|
|||
#pragma once
|
||||
#include <pugixml.hpp>
|
||||
|
||||
class ControllerBase;
|
||||
|
||||
#include "input/motion/MotionSample.h"
|
||||
|
||||
#include "input/api/ControllerState.h"
|
||||
#include "input/api/InputAPI.h"
|
||||
|
||||
#include "util/helpers/helpers.h"
|
||||
|
||||
// mapping = wii u controller button id
|
||||
// button = api button id
|
||||
|
||||
class EmulatedController
|
||||
{
|
||||
friend class InputManager;
|
||||
public:
|
||||
EmulatedController(size_t player_index);
|
||||
virtual ~EmulatedController() = default;
|
||||
|
||||
virtual void load(const pugi::xml_node& node){};
|
||||
virtual void save(pugi::xml_node& node){};
|
||||
|
||||
enum Type
|
||||
{
|
||||
VPAD,
|
||||
Pro,
|
||||
Classic,
|
||||
Wiimote,
|
||||
|
||||
MAX
|
||||
};
|
||||
virtual Type type() const = 0;
|
||||
std::string_view type_string() const { return type_to_string(type()); }
|
||||
|
||||
static std::string_view type_to_string(Type type);
|
||||
static Type type_from_string(std::string_view str);
|
||||
|
||||
size_t player_index() const { return m_player_index; }
|
||||
const std::string& get_profile_name() const { return m_profile_name; }
|
||||
bool has_profile_name() const { return !m_profile_name.empty() && m_profile_name != "default"; }
|
||||
|
||||
void calibrate();
|
||||
|
||||
void connect();
|
||||
virtual void update();
|
||||
void controllers_update_states();
|
||||
|
||||
virtual glm::vec2 get_axis() const = 0;
|
||||
virtual glm::vec2 get_rotation() const = 0;
|
||||
virtual glm::vec2 get_trigger() const = 0;
|
||||
|
||||
void start_rumble();
|
||||
void stop_rumble();
|
||||
|
||||
bool is_battery_low() const;
|
||||
|
||||
bool has_motion() const;
|
||||
MotionSample get_motion_data() const;
|
||||
|
||||
// some controllers (nunchuck) provide extra motion data
|
||||
bool has_second_motion() const;
|
||||
MotionSample get_second_motion_data() const;
|
||||
|
||||
bool has_position() const;
|
||||
glm::vec2 get_position() const;
|
||||
glm::vec2 get_prev_position() const;
|
||||
|
||||
std::shared_ptr<ControllerBase> find_controller(std::string_view uuid, InputAPI::Type type) const;
|
||||
void add_controller(std::shared_ptr<ControllerBase> controller);
|
||||
void remove_controller(const std::shared_ptr<ControllerBase>& controller);
|
||||
void clear_controllers();
|
||||
const std::vector<std::shared_ptr<ControllerBase>>& get_controllers() const { return m_controllers; }
|
||||
|
||||
bool is_mapping_down(uint64 mapping) const;
|
||||
std::string get_mapping_name(uint64 mapping) const;
|
||||
std::shared_ptr<ControllerBase> get_mapping_controller(uint64 mapping) const;
|
||||
void delete_mapping(uint64 mapping);
|
||||
void clear_mappings();
|
||||
void set_mapping(uint64 mapping, const std::shared_ptr<ControllerBase>& controller_base, uint64 button);
|
||||
|
||||
virtual uint32 get_emulated_button_flag(uint32 mapping) const = 0;
|
||||
|
||||
bool operator==(const EmulatedController& o) const;
|
||||
bool operator!=(const EmulatedController& o) const;
|
||||
|
||||
virtual size_t get_highest_mapping_id() const = 0;
|
||||
virtual bool is_axis_mapping(uint64 mapping) const = 0;
|
||||
|
||||
virtual bool is_start_down() const = 0;
|
||||
virtual bool is_left_down() const = 0;
|
||||
virtual bool is_right_down() const = 0;
|
||||
virtual bool is_up_down() const = 0;
|
||||
virtual bool is_down_down() const = 0;
|
||||
virtual bool is_a_down() const = 0;
|
||||
virtual bool is_b_down() const = 0;
|
||||
virtual bool is_home_down() const = 0;
|
||||
|
||||
bool was_home_button_down() { return std::exchange(m_homebutton_down, false); }
|
||||
|
||||
virtual bool set_default_mapping(const std::shared_ptr<ControllerBase>& controller) { return false; }
|
||||
|
||||
protected:
|
||||
size_t m_player_index;
|
||||
std::string m_profile_name = "default";
|
||||
|
||||
mutable std::shared_mutex m_mutex;
|
||||
std::vector<std::shared_ptr<ControllerBase>> m_controllers;
|
||||
|
||||
float get_axis_value(uint64 mapping) const;
|
||||
bool m_rumble = false;
|
||||
|
||||
struct Mapping
|
||||
{
|
||||
std::weak_ptr<ControllerBase> controller;
|
||||
uint64 button;
|
||||
};
|
||||
std::unordered_map<uint64, Mapping> m_mappings;
|
||||
|
||||
bool m_homebutton_down = false;
|
||||
};
|
||||
|
||||
using EmulatedControllerPtr = std::shared_ptr<EmulatedController>;
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<EmulatedController::Type> : formatter<string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(EmulatedController::Type v, FormatContext& ctx) {
|
||||
switch (v)
|
||||
{
|
||||
case EmulatedController::Type::VPAD: return formatter<string_view>::format("Wii U Gamepad", ctx);
|
||||
case EmulatedController::Type::Pro: return formatter<string_view>::format("Wii U Pro Controller", ctx);
|
||||
case EmulatedController::Type::Classic: return formatter<string_view>::format("Wii U Classic Controller Pro", ctx);
|
||||
case EmulatedController::Type::Wiimote: return formatter<string_view>::format("Wiimote", ctx);
|
||||
}
|
||||
throw std::invalid_argument(fmt::format("invalid emulated controller type with value {}", to_underlying(v)));
|
||||
}
|
||||
};
|
272
src/input/emulated/ProController.cpp
Normal file
272
src/input/emulated/ProController.cpp
Normal file
|
@ -0,0 +1,272 @@
|
|||
#include "input/emulated/ProController.h"
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/SDL/SDLController.h"
|
||||
|
||||
ProController::ProController(size_t player_index)
|
||||
: WPADController(player_index, kDataFormat_URCC)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
uint32 ProController::get_emulated_button_flag(uint32 id) const
|
||||
{
|
||||
return s_get_emulated_button_flag(id);
|
||||
}
|
||||
|
||||
uint32 ProController::s_get_emulated_button_flag(uint32 id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case kButtonId_A:
|
||||
return kProButton_A;
|
||||
case kButtonId_B:
|
||||
return kProButton_B;
|
||||
case kButtonId_X:
|
||||
return kProButton_X;
|
||||
case kButtonId_Y:
|
||||
return kProButton_Y;
|
||||
|
||||
case kButtonId_Plus:
|
||||
return kProButton_Plus;
|
||||
case kButtonId_Minus:
|
||||
return kProButton_Minus;
|
||||
case kButtonId_Up:
|
||||
return kProButton_Up;
|
||||
case kButtonId_Down:
|
||||
return kProButton_Down;
|
||||
case kButtonId_Left:
|
||||
return kProButton_Left;
|
||||
case kButtonId_Right:
|
||||
return kProButton_Right;
|
||||
|
||||
case kButtonId_StickL:
|
||||
return kProButton_StickL;
|
||||
case kButtonId_StickR:
|
||||
return kProButton_StickR;
|
||||
|
||||
case kButtonId_L:
|
||||
return kProButton_L;
|
||||
case kButtonId_ZL:
|
||||
return kProButton_ZL;
|
||||
case kButtonId_R:
|
||||
return kProButton_R;
|
||||
case kButtonId_ZR:
|
||||
return kProButton_ZR;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view ProController::get_button_name(ButtonId id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case kButtonId_A: return "A";
|
||||
case kButtonId_B: return "B";
|
||||
case kButtonId_X: return "X";
|
||||
case kButtonId_Y: return "Y";
|
||||
case kButtonId_L: return "L";
|
||||
case kButtonId_R: return "R";
|
||||
case kButtonId_ZL: return "ZL";
|
||||
case kButtonId_ZR: return "ZR";
|
||||
case kButtonId_Plus: return "+";
|
||||
case kButtonId_Minus: return "-";
|
||||
case kButtonId_Up: return "up";
|
||||
case kButtonId_Down: return "down";
|
||||
case kButtonId_Left: return "left";
|
||||
case kButtonId_Right: return "right";
|
||||
case kButtonId_StickL: return "click";
|
||||
case kButtonId_StickR: return "click";
|
||||
case kButtonId_StickL_Up: return "up";
|
||||
case kButtonId_StickL_Down: return "down";
|
||||
case kButtonId_StickL_Left: return "left";
|
||||
case kButtonId_StickL_Right: return "right";
|
||||
case kButtonId_StickR_Up: return "up";
|
||||
case kButtonId_StickR_Down: return "down";
|
||||
case kButtonId_StickR_Left: return "left";
|
||||
case kButtonId_StickR_Right: return "right";
|
||||
case kButtonId_Home: return "home";
|
||||
default:
|
||||
cemu_assert_debug(false);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
glm::vec2 ProController::get_axis() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_StickL_Left);
|
||||
const auto right = get_axis_value(kButtonId_StickL_Right);
|
||||
|
||||
const auto up = get_axis_value(kButtonId_StickL_Up);
|
||||
const auto down = get_axis_value(kButtonId_StickL_Down);
|
||||
|
||||
glm::vec2 result;
|
||||
result.x = (left > right) ? -left : right;
|
||||
result.y = (up > down) ? up : -down;
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 ProController::get_rotation() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_StickR_Left);
|
||||
const auto right = get_axis_value(kButtonId_StickR_Right);
|
||||
|
||||
const auto up = get_axis_value(kButtonId_StickR_Up);
|
||||
const auto down = get_axis_value(kButtonId_StickR_Down);
|
||||
|
||||
glm::vec2 result;
|
||||
result.x = (left > right) ? -left : right;
|
||||
result.y = (up > down) ? up : -down;
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 ProController::get_trigger() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_ZL);
|
||||
const auto right = get_axis_value(kButtonId_ZR);
|
||||
return { left, right };
|
||||
}
|
||||
|
||||
bool ProController::set_default_mapping(const std::shared_ptr<ControllerBase>& controller)
|
||||
{
|
||||
std::vector<std::pair<uint64, uint64>> mapping;
|
||||
switch (controller->api())
|
||||
{
|
||||
case InputAPI::SDLController: {
|
||||
const auto sdl_controller = std::static_pointer_cast<SDLController>(controller);
|
||||
if (sdl_controller->get_guid() == SDLController::kLeftJoyCon)
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_L, kButton9},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
|
||||
{kButtonId_Minus, kButton4},
|
||||
|
||||
{kButtonId_Up, kButton11},
|
||||
{kButtonId_Down, kButton12},
|
||||
{kButtonId_Left, kButton13},
|
||||
{kButtonId_Right, kButton14},
|
||||
|
||||
{kButtonId_StickL, kButton7},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYN},
|
||||
{kButtonId_StickL_Down, kAxisYP},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
};
|
||||
}
|
||||
else if (sdl_controller->get_guid() == SDLController::kRightJoyCon)
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton0},
|
||||
{kButtonId_B, kButton1},
|
||||
{kButtonId_X, kButton2},
|
||||
{kButtonId_Y, kButton3},
|
||||
|
||||
{kButtonId_R, kButton10},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton6},
|
||||
|
||||
{kButtonId_StickR, kButton8},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYN},
|
||||
{kButtonId_StickR_Down, kRotationYP},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton1},
|
||||
{kButtonId_B, kButton0},
|
||||
{kButtonId_X, kButton3},
|
||||
{kButtonId_Y, kButton2},
|
||||
|
||||
{kButtonId_L, kButton9},
|
||||
{kButtonId_R, kButton10},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton6},
|
||||
{kButtonId_Minus, kButton4},
|
||||
|
||||
{kButtonId_Up, kButton11},
|
||||
{kButtonId_Down, kButton12},
|
||||
{kButtonId_Left, kButton13},
|
||||
{kButtonId_Right, kButton14},
|
||||
|
||||
{kButtonId_StickL, kButton7},
|
||||
{kButtonId_StickR, kButton8},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYN},
|
||||
{kButtonId_StickL_Down, kAxisYP},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYN},
|
||||
{kButtonId_StickR_Down, kRotationYP},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InputAPI::XInput:
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton13},
|
||||
{kButtonId_B, kButton12},
|
||||
{kButtonId_X, kButton15},
|
||||
{kButtonId_Y, kButton14},
|
||||
|
||||
{kButtonId_L, kButton8},
|
||||
{kButtonId_R, kButton9},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton4},
|
||||
{kButtonId_Minus, kButton5},
|
||||
|
||||
{kButtonId_Up, kButton0},
|
||||
{kButtonId_Down, kButton1},
|
||||
{kButtonId_Left, kButton2},
|
||||
{kButtonId_Right, kButton3},
|
||||
|
||||
{kButtonId_StickL, kButton6},
|
||||
{kButtonId_StickR, kButton7},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYP},
|
||||
{kButtonId_StickL_Down, kAxisYN},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYP},
|
||||
{kButtonId_StickR_Down, kRotationYN},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool mapping_updated = false;
|
||||
std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m)
|
||||
{
|
||||
if (m_mappings.find(m.first) == m_mappings.cend())
|
||||
{
|
||||
set_mapping(m.first, controller, m.second);
|
||||
mapping_updated = true;
|
||||
}
|
||||
});
|
||||
|
||||
return mapping_updated;
|
||||
}
|
74
src/input/emulated/ProController.h
Normal file
74
src/input/emulated/ProController.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/emulated/WPADController.h"
|
||||
|
||||
class ProController : public WPADController
|
||||
{
|
||||
public:
|
||||
enum ButtonId
|
||||
{
|
||||
kButtonId_None,
|
||||
|
||||
kButtonId_A,
|
||||
kButtonId_B,
|
||||
kButtonId_X,
|
||||
kButtonId_Y,
|
||||
|
||||
kButtonId_L,
|
||||
kButtonId_R,
|
||||
kButtonId_ZL,
|
||||
kButtonId_ZR,
|
||||
|
||||
kButtonId_Plus,
|
||||
kButtonId_Minus,
|
||||
kButtonId_Home,
|
||||
|
||||
kButtonId_Up,
|
||||
kButtonId_Down,
|
||||
kButtonId_Left,
|
||||
kButtonId_Right,
|
||||
|
||||
kButtonId_StickL,
|
||||
kButtonId_StickR,
|
||||
|
||||
kButtonId_StickL_Up,
|
||||
kButtonId_StickL_Down,
|
||||
kButtonId_StickL_Left,
|
||||
kButtonId_StickL_Right,
|
||||
|
||||
kButtonId_StickR_Up,
|
||||
kButtonId_StickR_Down,
|
||||
kButtonId_StickR_Left,
|
||||
kButtonId_StickR_Right,
|
||||
|
||||
kButtonId_Max,
|
||||
};
|
||||
|
||||
ProController(size_t player_index);
|
||||
|
||||
Type type() const override { return Type::Pro; }
|
||||
WPADDeviceType get_device_type() const override { return kWAPDevURCC; }
|
||||
|
||||
uint32 get_emulated_button_flag(uint32 id) const override;
|
||||
size_t get_highest_mapping_id() const override { return kButtonId_Max; }
|
||||
bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; }
|
||||
|
||||
glm::vec2 get_axis() const override;
|
||||
glm::vec2 get_rotation() const override;
|
||||
glm::vec2 get_trigger() const override;
|
||||
|
||||
static uint32 s_get_emulated_button_flag(uint32 id);
|
||||
|
||||
static std::string_view get_button_name(ButtonId id);
|
||||
|
||||
bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); }
|
||||
bool is_left_down() const override { return is_mapping_down(kButtonId_Left); }
|
||||
bool is_right_down() const override { return is_mapping_down(kButtonId_Right); }
|
||||
bool is_up_down() const override { return is_mapping_down(kButtonId_Up); }
|
||||
bool is_down_down() const override { return is_mapping_down(kButtonId_Down); }
|
||||
bool is_a_down() const override { return is_mapping_down(kButtonId_A); }
|
||||
bool is_b_down() const override { return is_mapping_down(kButtonId_B); }
|
||||
bool is_home_down() const override { return is_mapping_down(kButtonId_Home); }
|
||||
|
||||
bool set_default_mapping(const std::shared_ptr<ControllerBase>& controller) override;
|
||||
};
|
687
src/input/emulated/VPADController.cpp
Normal file
687
src/input/emulated/VPADController.cpp
Normal file
|
@ -0,0 +1,687 @@
|
|||
#include "input/emulated/VPADController.h"
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/SDL/SDLController.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "input/InputManager.h"
|
||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
||||
#include "Cafe/CafeSystem.h"
|
||||
|
||||
enum ControllerVPADMapping2 : uint32
|
||||
{
|
||||
VPAD_A = 0x8000,
|
||||
VPAD_B = 0x4000,
|
||||
VPAD_X = 0x2000,
|
||||
VPAD_Y = 0x1000,
|
||||
|
||||
VPAD_L = 0x0020,
|
||||
VPAD_R = 0x0010,
|
||||
VPAD_ZL = 0x0080,
|
||||
VPAD_ZR = 0x0040,
|
||||
|
||||
VPAD_PLUS = 0x0008,
|
||||
VPAD_MINUS = 0x0004,
|
||||
VPAD_HOME = 0x0002,
|
||||
|
||||
VPAD_UP = 0x0200,
|
||||
VPAD_DOWN = 0x0100,
|
||||
VPAD_LEFT = 0x0800,
|
||||
VPAD_RIGHT = 0x0400,
|
||||
|
||||
VPAD_STICK_R = 0x00020000,
|
||||
VPAD_STICK_L = 0x00040000,
|
||||
|
||||
VPAD_STICK_L_UP = 0x10000000,
|
||||
VPAD_STICK_L_DOWN = 0x08000000,
|
||||
VPAD_STICK_L_LEFT = 0x40000000,
|
||||
VPAD_STICK_L_RIGHT = 0x20000000,
|
||||
|
||||
VPAD_STICK_R_UP = 0x01000000,
|
||||
VPAD_STICK_R_DOWN = 0x00800000,
|
||||
VPAD_STICK_R_LEFT = 0x04000000,
|
||||
VPAD_STICK_R_RIGHT = 0x02000000,
|
||||
|
||||
// special flag
|
||||
VPAD_REPEAT = 0x80000000,
|
||||
};
|
||||
|
||||
void VPADController::VPADRead(VPADStatus_t& status, const BtnRepeat& repeat)
|
||||
{
|
||||
controllers_update_states();
|
||||
m_mic_active = false;
|
||||
m_screen_active = false;
|
||||
for (uint32 i = kButtonId_A; i < kButtonId_Max; ++i)
|
||||
{
|
||||
// axis will be aplied later
|
||||
if (is_axis_mapping(i))
|
||||
continue;
|
||||
|
||||
if (is_mapping_down(i))
|
||||
{
|
||||
const uint32 value = get_emulated_button_flag(i);
|
||||
if (value == 0)
|
||||
{
|
||||
// special buttons
|
||||
if (i == kButtonId_Mic)
|
||||
m_mic_active = true;
|
||||
else if (i == kButtonId_Screen)
|
||||
m_screen_active = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
status.hold |= value;
|
||||
}
|
||||
}
|
||||
|
||||
m_homebutton_down |= is_home_down();
|
||||
|
||||
const auto axis = get_axis();
|
||||
status.leftStick.x = axis.x;
|
||||
status.leftStick.y = axis.y;
|
||||
|
||||
constexpr float kAxisThreshold = 0.5f;
|
||||
constexpr float kHoldAxisThreshold = 0.1f;
|
||||
const uint32 last_hold = m_last_holdvalue;
|
||||
|
||||
if (axis.x <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_LEFT) && axis.x <= -kHoldAxisThreshold))
|
||||
status.hold |= VPAD_STICK_L_LEFT;
|
||||
else if (axis.x >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_RIGHT) && axis.x >= kHoldAxisThreshold))
|
||||
status.hold |= VPAD_STICK_L_RIGHT;
|
||||
|
||||
if (axis.y <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_DOWN) && axis.y <= -kHoldAxisThreshold))
|
||||
status.hold |= VPAD_STICK_L_DOWN;
|
||||
else if (axis.y >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_UP) && axis.y >= kHoldAxisThreshold))
|
||||
status.hold |= VPAD_STICK_L_UP;
|
||||
|
||||
const auto rotation = get_rotation();
|
||||
status.rightStick.x = rotation.x;
|
||||
status.rightStick.y = rotation.y;
|
||||
|
||||
if (rotation.x <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_LEFT) && rotation.x <= -kHoldAxisThreshold))
|
||||
status.hold |= VPAD_STICK_R_LEFT;
|
||||
else if (rotation.x >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_RIGHT) && rotation.x >=
|
||||
kHoldAxisThreshold))
|
||||
status.hold |= VPAD_STICK_R_RIGHT;
|
||||
|
||||
if (rotation.y <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_DOWN) && rotation.y <= -kHoldAxisThreshold))
|
||||
status.hold |= VPAD_STICK_R_DOWN;
|
||||
else if (rotation.y >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_UP) && rotation.y >= kHoldAxisThreshold))
|
||||
status.hold |= VPAD_STICK_R_UP;
|
||||
|
||||
// button repeat
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
if (status.hold != m_last_holdvalue)
|
||||
{
|
||||
m_last_hold_change = m_last_pulse = now;
|
||||
}
|
||||
|
||||
if (repeat.pulse > 0)
|
||||
{
|
||||
if (m_last_hold_change + std::chrono::milliseconds(repeat.delay) >= now)
|
||||
{
|
||||
if ((m_last_pulse + std::chrono::milliseconds(repeat.pulse)) < now)
|
||||
{
|
||||
m_last_pulse = now;
|
||||
status.hold |= VPAD_REPEAT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// general
|
||||
status.release = m_last_holdvalue & ~status.hold;
|
||||
status.trig = ~m_last_holdvalue & status.hold;
|
||||
m_last_holdvalue = status.hold;
|
||||
|
||||
// touch
|
||||
update_touch(status);
|
||||
|
||||
// motion
|
||||
status.dir.x = {1, 0, 0};
|
||||
status.dir.y = {0, 1, 0};
|
||||
status.dir.z = {0, 0, 1};
|
||||
status.accXY = {1.0f, 0.0f};
|
||||
update_motion(status);
|
||||
}
|
||||
|
||||
void VPADController::update()
|
||||
{
|
||||
EmulatedController::update();
|
||||
|
||||
if (!CafeSystem::IsTitleRunning())
|
||||
return;
|
||||
|
||||
std::unique_lock lock(m_rumble_mutex);
|
||||
if (m_rumble_queue.empty())
|
||||
{
|
||||
m_parser = 0;
|
||||
lock.unlock();
|
||||
|
||||
stop_rumble();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto tick = now_cached();
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(tick - m_last_rumble_check).count() < 1000 / 60)
|
||||
return;
|
||||
|
||||
m_last_rumble_check = tick;
|
||||
|
||||
const auto& it = m_rumble_queue.front();
|
||||
if (it[m_parser])
|
||||
start_rumble();
|
||||
else
|
||||
stop_rumble();
|
||||
|
||||
++m_parser;
|
||||
if (m_parser >= it.size())
|
||||
{
|
||||
m_rumble_queue.pop();
|
||||
m_parser = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void VPADController::update_touch(VPADStatus_t& status)
|
||||
{
|
||||
status.tpData.touch = kTpTouchOff;
|
||||
status.tpData.validity = kTpInvalid;
|
||||
// keep x,y from previous update
|
||||
// NGDK (Neko Game Development Kit 2) games (e.g. Mysterios Cities of Gold) rely on x/y remaining intact after touch is released
|
||||
status.tpData.x = (uint16)m_last_touch_position.x;
|
||||
status.tpData.y = (uint16)m_last_touch_position.y;
|
||||
|
||||
auto& instance = InputManager::instance();
|
||||
bool pad_view;
|
||||
if (has_position())
|
||||
{
|
||||
const auto mouse = get_position();
|
||||
|
||||
status.tpData.touch = kTpTouchOn;
|
||||
status.tpData.validity = kTpValid;
|
||||
status.tpData.x = (uint16)(mouse.x * 3883.0f + 92.0f);
|
||||
status.tpData.y = (uint16)(4095.0f - mouse.y * 3694.0f - 254.0f);
|
||||
|
||||
m_last_touch_position = glm::ivec2{status.tpData.x, status.tpData.y};
|
||||
}
|
||||
else if (const auto left_mouse = instance.get_left_down_mouse_info(&pad_view))
|
||||
{
|
||||
glm::ivec2 image_pos, image_size;
|
||||
LatteRenderTarget_getScreenImageArea(&image_pos.x, &image_pos.y, &image_size.x, &image_size.y, nullptr, nullptr, pad_view);
|
||||
|
||||
glm::vec2 relative_mouse_pos = left_mouse.value() - image_pos;
|
||||
relative_mouse_pos = { std::min(relative_mouse_pos.x, (float)image_size.x), std::min(relative_mouse_pos.y, (float)image_size.y) };
|
||||
relative_mouse_pos = { std::max(relative_mouse_pos.x, 0.0f), std::max(relative_mouse_pos.y, 0.0f) };
|
||||
relative_mouse_pos /= image_size;
|
||||
|
||||
status.tpData.touch = kTpTouchOn;
|
||||
status.tpData.validity = kTpValid;
|
||||
status.tpData.x = (uint16)((relative_mouse_pos.x * 3883.0f) + 92.0f);
|
||||
status.tpData.y = (uint16)(4095.0f - (relative_mouse_pos.y * 3694.0f) - 254.0f);
|
||||
|
||||
m_last_touch_position = glm::ivec2{ status.tpData.x, status.tpData.y };
|
||||
|
||||
/*cemuLog_force("TDATA: {},{} -> {},{} -> {},{} -> {},{} -> {},{} -> {},{}",
|
||||
left_mouse->x, left_mouse->y,
|
||||
(left_mouse.value() - image_pos).x, (left_mouse.value() - image_pos).y,
|
||||
relative_mouse_pos.x, relative_mouse_pos.y,
|
||||
(uint16)(relative_mouse_pos.x * 3883.0 + 92.0), (uint16)(4095.0 - relative_mouse_pos.y * 3694.0 - 254.0),
|
||||
status.tpData.x.value(), status.tpData.y.value(), status.tpData.x.bevalue(), status.tpData.y.bevalue()
|
||||
);*/
|
||||
}
|
||||
|
||||
status.tpProcessed1 = status.tpData;
|
||||
status.tpProcessed2 = status.tpData;
|
||||
}
|
||||
|
||||
void VPADController::update_motion(VPADStatus_t& status)
|
||||
{
|
||||
if (has_motion())
|
||||
{
|
||||
auto motionSample = get_motion_data();
|
||||
|
||||
glm::vec3 acc;
|
||||
motionSample.getVPADAccelerometer(&acc[0]);
|
||||
//const auto& acc = motionSample.getVPADAccelerometer();
|
||||
status.acc.x = acc.x;
|
||||
status.acc.y = acc.y;
|
||||
status.acc.z = acc.z;
|
||||
status.accMagnitude = motionSample.getVPADAccMagnitude();
|
||||
status.accAcceleration = motionSample.getVPADAccAcceleration();
|
||||
|
||||
glm::vec3 gyroChange;
|
||||
motionSample.getVPADGyroChange(&gyroChange[0]);
|
||||
//const auto& gyroChange = motionSample.getVPADGyroChange();
|
||||
status.gyroChange.x = gyroChange.x;
|
||||
status.gyroChange.y = gyroChange.y;
|
||||
status.gyroChange.z = gyroChange.z;
|
||||
|
||||
//debug_printf("GyroChange %7.2lf %7.2lf %7.2lf\n", (float)status.gyroChange.x, (float)status.gyroChange.y, (float)status.gyroChange.z);
|
||||
|
||||
glm::vec3 gyroOrientation;
|
||||
motionSample.getVPADOrientation(&gyroOrientation[0]);
|
||||
//const auto& gyroOrientation = motionSample.getVPADOrientation();
|
||||
status.gyroOrientation.x = gyroOrientation.x;
|
||||
status.gyroOrientation.y = gyroOrientation.y;
|
||||
status.gyroOrientation.z = gyroOrientation.z;
|
||||
|
||||
float attitude[9];
|
||||
motionSample.getVPADAttitudeMatrix(attitude);
|
||||
status.dir.x.x = attitude[0];
|
||||
status.dir.x.y = attitude[1];
|
||||
status.dir.x.z = attitude[2];
|
||||
status.dir.y.x = attitude[3];
|
||||
status.dir.y.y = attitude[4];
|
||||
status.dir.y.z = attitude[5];
|
||||
status.dir.z.x = attitude[6];
|
||||
status.dir.z.y = attitude[7];
|
||||
status.dir.z.z = attitude[8];
|
||||
return;
|
||||
}
|
||||
|
||||
bool pad_view;
|
||||
auto& input_manager = InputManager::instance();
|
||||
if (const auto right_mouse = input_manager.get_right_down_mouse_info(&pad_view))
|
||||
{
|
||||
const Vector2<float> mousePos(right_mouse->x, right_mouse->y);
|
||||
|
||||
int w, h;
|
||||
if (pad_view)
|
||||
gui_getPadWindowSize(&w, &h);
|
||||
else
|
||||
gui_getWindowSize(&w, &h);
|
||||
|
||||
float wx = mousePos.x / w;
|
||||
float wy = mousePos.y / h;
|
||||
|
||||
static glm::vec3 m_lastGyroRotation{}, m_startGyroRotation{};
|
||||
static bool m_startGyroRotationSet{};
|
||||
|
||||
float rotX = (wy * 2 - 1.0f) * 135.0f; // up/down best
|
||||
float rotY = (wx * 2 - 1.0f) * -180.0f; // left/right
|
||||
float rotZ = input_manager.m_mouse_wheel * 14.0f + m_lastGyroRotation.z;
|
||||
input_manager.m_mouse_wheel = 0.0f;
|
||||
|
||||
if (!m_startGyroRotationSet)
|
||||
{
|
||||
m_startGyroRotation = {rotX, rotY, rotZ};
|
||||
m_startGyroRotationSet = true;
|
||||
}
|
||||
|
||||
/* debug_printf("\n\ngyro:\n<%.02f, %.02f, %.02f>\n\n",
|
||||
rotX, rotY, rotZ);*/
|
||||
|
||||
Quaternion<float> q(rotX, rotY, rotZ);
|
||||
auto rot = q.GetTransposedRotationMatrix();
|
||||
|
||||
/*m_forward = std::get<0>(rot);
|
||||
m_right = std::get<1>(rot);
|
||||
m_up = std::get<2>(rot);*/
|
||||
|
||||
status.dir.x = std::get<0>(rot);
|
||||
status.dir.y = std::get<1>(rot);
|
||||
status.dir.z = std::get<2>(rot);
|
||||
|
||||
/*debug_printf("rot:\n<%.02f, %.02f, %.02f>\n<%.02f, %.02f, %.02f>\n<%.02f, %.02f, %.02f>\n\n",
|
||||
(float)status.dir.x.x, (float)status.dir.x.y, (float)status.dir.x.z,
|
||||
(float)status.dir.y.x, (float)status.dir.y.y, (float)status.dir.y.z,
|
||||
(float)status.dir.z.x, (float)status.dir.z.y, (float)status.dir.z.z);*/
|
||||
|
||||
glm::vec3 rotation(rotX - m_lastGyroRotation.x, (rotY - m_lastGyroRotation.y) * 15.0f,
|
||||
rotZ - m_lastGyroRotation.z);
|
||||
|
||||
rotation.x = std::min(1.0f, std::max(-1.0f, rotation.x / 360.0f));
|
||||
rotation.y = std::min(1.0f, std::max(-1.0f, rotation.y / 360.0f));
|
||||
rotation.z = std::min(1.0f, std::max(-1.0f, rotation.z / 360.0f));
|
||||
|
||||
/*debug_printf("\n\ngyro:\n<%.02f, %.02f, %.02f>\n\n",
|
||||
rotation.x, rotation.y, rotation.z);*/
|
||||
|
||||
constexpr float pi2 = (float)(M_PI * 2);
|
||||
status.gyroChange = {rotation.x, rotation.y, rotation.z};
|
||||
status.gyroOrientation = {rotation.x, rotation.y, rotation.z};
|
||||
//status.angle = { rotation.x / pi2, rotation.y / pi2, rotation.z / pi2 };
|
||||
|
||||
status.acc = {rotation.x, rotation.y, rotation.z};
|
||||
status.accAcceleration = 1.0f;
|
||||
status.accMagnitude = 1.0f;
|
||||
|
||||
status.accXY = {1.0f, 0.0f};
|
||||
|
||||
m_lastGyroRotation = {rotX, rotY, rotZ};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string_view VPADController::get_button_name(ButtonId id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case kButtonId_A: return "A";
|
||||
case kButtonId_B: return "B";
|
||||
case kButtonId_X: return "X";
|
||||
case kButtonId_Y: return "Y";
|
||||
case kButtonId_L: return "L";
|
||||
case kButtonId_R: return "R";
|
||||
case kButtonId_ZL: return "ZL";
|
||||
case kButtonId_ZR: return "ZR";
|
||||
case kButtonId_Plus: return "+";
|
||||
case kButtonId_Minus: return "-";
|
||||
case kButtonId_Up: return "up";
|
||||
case kButtonId_Down: return "down";
|
||||
case kButtonId_Left: return "left";
|
||||
case kButtonId_Right: return "right";
|
||||
case kButtonId_StickL: return "click";
|
||||
case kButtonId_StickR: return "click";
|
||||
case kButtonId_StickL_Up: return "up";
|
||||
case kButtonId_StickL_Down: return "down";
|
||||
case kButtonId_StickL_Left: return "left";
|
||||
case kButtonId_StickL_Right: return "right";
|
||||
case kButtonId_StickR_Up: return "up";
|
||||
case kButtonId_StickR_Down: return "down";
|
||||
case kButtonId_StickR_Left: return "left";
|
||||
case kButtonId_StickR_Right: return "right";
|
||||
case kButtonId_Home: return "home";
|
||||
default:
|
||||
cemu_assert_debug(false);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void VPADController::clear_rumble()
|
||||
{
|
||||
std::scoped_lock lock(m_rumble_mutex);
|
||||
while (!m_rumble_queue.empty())
|
||||
m_rumble_queue.pop();
|
||||
|
||||
m_parser = 0;
|
||||
}
|
||||
|
||||
bool VPADController::push_rumble(uint8* pattern, uint8 length)
|
||||
{
|
||||
if (pattern == nullptr || length == 0)
|
||||
{
|
||||
clear_rumble();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(m_rumble_mutex);
|
||||
if (m_rumble_queue.size() >= 5)
|
||||
{
|
||||
forceLogDebug_printf("too many cmds");
|
||||
return false;
|
||||
}
|
||||
|
||||
// len = max 15 bytes of data = 120 bits = 1 seconds
|
||||
// we will use 60 hz for 1 second
|
||||
std::vector<bool> bitset;
|
||||
int byte = 0;
|
||||
int len = (int)length;
|
||||
while (len > 0)
|
||||
{
|
||||
const uint8 p = pattern[byte];
|
||||
for (int j = 0; j < 8 && j < len; j += 2)
|
||||
{
|
||||
const bool set = (p & (3 << j)) != 0;
|
||||
bitset.push_back(set);
|
||||
}
|
||||
|
||||
++byte;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
|
||||
m_rumble_queue.emplace(std::move(bitset));
|
||||
m_last_rumble_check = {};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 VPADController::get_emulated_button_flag(uint32 id) const
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case kButtonId_A: return VPAD_A;
|
||||
case kButtonId_B: return VPAD_B;
|
||||
case kButtonId_X: return VPAD_X;
|
||||
case kButtonId_Y: return VPAD_Y;
|
||||
case kButtonId_L: return VPAD_L;
|
||||
case kButtonId_R: return VPAD_R;
|
||||
case kButtonId_ZL: return VPAD_ZL;
|
||||
case kButtonId_ZR: return VPAD_ZR;
|
||||
case kButtonId_Plus: return VPAD_PLUS;
|
||||
case kButtonId_Minus: return VPAD_MINUS;
|
||||
case kButtonId_Up: return VPAD_UP;
|
||||
case kButtonId_Down: return VPAD_DOWN;
|
||||
case kButtonId_Left: return VPAD_LEFT;
|
||||
case kButtonId_Right: return VPAD_RIGHT;
|
||||
case kButtonId_StickL: return VPAD_STICK_L;
|
||||
case kButtonId_StickR: return VPAD_STICK_R;
|
||||
|
||||
case kButtonId_StickL_Up: return VPAD_STICK_L_UP;
|
||||
case kButtonId_StickL_Down: return VPAD_STICK_L_DOWN;
|
||||
case kButtonId_StickL_Left: return VPAD_STICK_L_LEFT;
|
||||
case kButtonId_StickL_Right: return VPAD_STICK_L_RIGHT;
|
||||
|
||||
case kButtonId_StickR_Up: return VPAD_STICK_R_UP;
|
||||
case kButtonId_StickR_Down: return VPAD_STICK_R_DOWN;
|
||||
case kButtonId_StickR_Left: return VPAD_STICK_R_LEFT;
|
||||
case kButtonId_StickR_Right: return VPAD_STICK_R_RIGHT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
glm::vec2 VPADController::get_axis() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_StickL_Left);
|
||||
const auto right = get_axis_value(kButtonId_StickL_Right);
|
||||
|
||||
const auto up = get_axis_value(kButtonId_StickL_Up);
|
||||
const auto down = get_axis_value(kButtonId_StickL_Down);
|
||||
|
||||
glm::vec2 result;
|
||||
result.x = (left > right) ? -left : right;
|
||||
result.y = (up > down) ? up : -down;
|
||||
return length(result) > 1.0f ? normalize(result) : result;
|
||||
}
|
||||
|
||||
glm::vec2 VPADController::get_rotation() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_StickR_Left);
|
||||
const auto right = get_axis_value(kButtonId_StickR_Right);
|
||||
|
||||
const auto up = get_axis_value(kButtonId_StickR_Up);
|
||||
const auto down = get_axis_value(kButtonId_StickR_Down);
|
||||
|
||||
glm::vec2 result;
|
||||
result.x = (left > right) ? -left : right;
|
||||
result.y = (up > down) ? up : -down;
|
||||
return length(result) > 1.0f ? normalize(result) : result;
|
||||
}
|
||||
|
||||
glm::vec2 VPADController::get_trigger() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_ZL);
|
||||
const auto right = get_axis_value(kButtonId_ZR);
|
||||
return {left, right};
|
||||
}
|
||||
|
||||
bool VPADController::set_default_mapping(const std::shared_ptr<ControllerBase>& controller)
|
||||
{
|
||||
std::vector<std::pair<uint64, uint64>> mapping;
|
||||
switch (controller->api())
|
||||
{
|
||||
case InputAPI::SDLController: {
|
||||
const auto sdl_controller = std::static_pointer_cast<SDLController>(controller);
|
||||
if (sdl_controller->get_guid() == SDLController::kLeftJoyCon)
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_L, kButton9},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
|
||||
{kButtonId_Minus, kButton4},
|
||||
|
||||
{kButtonId_Up, kButton11},
|
||||
{kButtonId_Down, kButton12},
|
||||
{kButtonId_Left, kButton13},
|
||||
{kButtonId_Right, kButton14},
|
||||
|
||||
{kButtonId_StickL, kButton7},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYN},
|
||||
{kButtonId_StickL_Down, kAxisYP},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
|
||||
{kButtonId_Mic, kButton15},
|
||||
};
|
||||
}
|
||||
else if (sdl_controller->get_guid() == SDLController::kRightJoyCon)
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton0},
|
||||
{kButtonId_B, kButton1},
|
||||
{kButtonId_X, kButton2},
|
||||
{kButtonId_Y, kButton3},
|
||||
|
||||
{kButtonId_R, kButton10},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton6},
|
||||
|
||||
{kButtonId_StickR, kButton8},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYN},
|
||||
{kButtonId_StickR_Down, kRotationYP},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
}
|
||||
else if (sdl_controller->get_guid() == SDLController::kSwitchProController)
|
||||
{
|
||||
// Switch Pro Controller is similar to default mapping, but with a/b and x/y swapped
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton0},
|
||||
{kButtonId_B, kButton1},
|
||||
{kButtonId_X, kButton2},
|
||||
{kButtonId_Y, kButton3},
|
||||
|
||||
{kButtonId_L, kButton9},
|
||||
{kButtonId_R, kButton10},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton6},
|
||||
{kButtonId_Minus, kButton4},
|
||||
|
||||
{kButtonId_Up, kButton11},
|
||||
{kButtonId_Down, kButton12},
|
||||
{kButtonId_Left, kButton13},
|
||||
{kButtonId_Right, kButton14},
|
||||
|
||||
{kButtonId_StickL, kButton7},
|
||||
{kButtonId_StickR, kButton8},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYN},
|
||||
{kButtonId_StickL_Down, kAxisYP},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYN},
|
||||
{kButtonId_StickR_Down, kRotationYP},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton1},
|
||||
{kButtonId_B, kButton0},
|
||||
{kButtonId_X, kButton3},
|
||||
{kButtonId_Y, kButton2},
|
||||
|
||||
{kButtonId_L, kButton9},
|
||||
{kButtonId_R, kButton10},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton6},
|
||||
{kButtonId_Minus, kButton4},
|
||||
|
||||
{kButtonId_Up, kButton11},
|
||||
{kButtonId_Down, kButton12},
|
||||
{kButtonId_Left, kButton13},
|
||||
{kButtonId_Right, kButton14},
|
||||
|
||||
{kButtonId_StickL, kButton7},
|
||||
{kButtonId_StickR, kButton8},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYN},
|
||||
{kButtonId_StickL_Down, kAxisYP},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYN},
|
||||
{kButtonId_StickR_Down, kRotationYP},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InputAPI::XInput:
|
||||
{
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kButton13},
|
||||
{kButtonId_B, kButton12},
|
||||
{kButtonId_X, kButton15},
|
||||
{kButtonId_Y, kButton14},
|
||||
|
||||
{kButtonId_L, kButton8},
|
||||
{kButtonId_R, kButton9},
|
||||
{kButtonId_ZL, kTriggerXP},
|
||||
{kButtonId_ZR, kTriggerYP},
|
||||
|
||||
{kButtonId_Plus, kButton4},
|
||||
{kButtonId_Minus, kButton5},
|
||||
|
||||
{kButtonId_Up, kButton0},
|
||||
{kButtonId_Down, kButton1},
|
||||
{kButtonId_Left, kButton2},
|
||||
{kButtonId_Right, kButton3},
|
||||
|
||||
{kButtonId_StickL, kButton6},
|
||||
{kButtonId_StickR, kButton7},
|
||||
|
||||
{kButtonId_StickL_Up, kAxisYP},
|
||||
{kButtonId_StickL_Down, kAxisYN},
|
||||
{kButtonId_StickL_Left, kAxisXN},
|
||||
{kButtonId_StickL_Right, kAxisXP},
|
||||
|
||||
{kButtonId_StickR_Up, kRotationYP},
|
||||
{kButtonId_StickR_Down, kRotationYN},
|
||||
{kButtonId_StickR_Left, kRotationXN},
|
||||
{kButtonId_StickR_Right, kRotationXP},
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool mapping_updated = false;
|
||||
std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m)
|
||||
{
|
||||
if (m_mappings.find(m.first) == m_mappings.cend())
|
||||
{
|
||||
set_mapping(m.first, controller, m.second);
|
||||
mapping_updated = true;
|
||||
}
|
||||
});
|
||||
|
||||
return mapping_updated;
|
||||
}
|
104
src/input/emulated/VPADController.h
Normal file
104
src/input/emulated/VPADController.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/emulated/EmulatedController.h"
|
||||
#include "Cafe/OS/libs/vpad/vpad.h"
|
||||
|
||||
|
||||
class VPADController : public EmulatedController
|
||||
{
|
||||
public:
|
||||
enum ButtonId
|
||||
{
|
||||
kButtonId_None,
|
||||
|
||||
kButtonId_A,
|
||||
kButtonId_B,
|
||||
kButtonId_X,
|
||||
kButtonId_Y,
|
||||
|
||||
kButtonId_L,
|
||||
kButtonId_R,
|
||||
kButtonId_ZL,
|
||||
kButtonId_ZR,
|
||||
|
||||
kButtonId_Plus,
|
||||
kButtonId_Minus,
|
||||
|
||||
kButtonId_Up,
|
||||
kButtonId_Down,
|
||||
kButtonId_Left,
|
||||
kButtonId_Right,
|
||||
|
||||
kButtonId_StickL,
|
||||
kButtonId_StickR,
|
||||
|
||||
kButtonId_StickL_Up,
|
||||
kButtonId_StickL_Down,
|
||||
kButtonId_StickL_Left,
|
||||
kButtonId_StickL_Right,
|
||||
|
||||
kButtonId_StickR_Up,
|
||||
kButtonId_StickR_Down,
|
||||
kButtonId_StickR_Left,
|
||||
kButtonId_StickR_Right,
|
||||
|
||||
kButtonId_Mic,
|
||||
kButtonId_Screen,
|
||||
|
||||
kButtonId_Home,
|
||||
|
||||
kButtonId_Max,
|
||||
};
|
||||
|
||||
using EmulatedController::EmulatedController;
|
||||
|
||||
Type type() const override { return VPAD; }
|
||||
|
||||
void VPADRead(VPADStatus_t& status, const BtnRepeat& repeat);
|
||||
|
||||
void update() override;
|
||||
|
||||
uint32 get_emulated_button_flag(uint32 id) const override;
|
||||
|
||||
glm::vec2 get_axis() const override;
|
||||
glm::vec2 get_rotation() const override;
|
||||
glm::vec2 get_trigger() const override;
|
||||
|
||||
bool is_mic_active() { return m_mic_active; }
|
||||
bool is_screen_active() { return m_screen_active; }
|
||||
|
||||
static std::string_view get_button_name(ButtonId id);
|
||||
|
||||
void clear_rumble();
|
||||
bool push_rumble(uint8* pattern, uint8 length);
|
||||
|
||||
size_t get_highest_mapping_id() const override { return kButtonId_Max; }
|
||||
bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; }
|
||||
|
||||
bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); }
|
||||
bool is_left_down() const override { return is_mapping_down(kButtonId_Left); }
|
||||
bool is_right_down() const override { return is_mapping_down(kButtonId_Right); }
|
||||
bool is_up_down() const override { return is_mapping_down(kButtonId_Up); }
|
||||
bool is_down_down() const override { return is_mapping_down(kButtonId_Down); }
|
||||
bool is_a_down() const override { return is_mapping_down(kButtonId_A); }
|
||||
bool is_b_down() const override { return is_mapping_down(kButtonId_B); }
|
||||
bool is_home_down() const override { return is_mapping_down(kButtonId_Home); }
|
||||
|
||||
bool set_default_mapping(const std::shared_ptr<ControllerBase>& controller) override;
|
||||
|
||||
private:
|
||||
bool m_mic_active = false;
|
||||
bool m_screen_active = false;
|
||||
uint32be m_last_holdvalue = 0;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point m_last_hold_change{}, m_last_pulse{};
|
||||
|
||||
std::mutex m_rumble_mutex;
|
||||
std::chrono::high_resolution_clock::time_point m_last_rumble_check{};
|
||||
std::queue<std::vector<bool>> m_rumble_queue;
|
||||
uint8 m_parser = 0;
|
||||
|
||||
void update_touch(VPADStatus_t& status);
|
||||
void update_motion(VPADStatus_t& status);
|
||||
glm::ivec2 m_last_touch_position{};
|
||||
};
|
378
src/input/emulated/WPADController.cpp
Normal file
378
src/input/emulated/WPADController.cpp
Normal file
|
@ -0,0 +1,378 @@
|
|||
#include "input/emulated/WPADController.h"
|
||||
|
||||
#include "input/emulated/ClassicController.h"
|
||||
#include "input/emulated/ProController.h"
|
||||
#include "input/emulated/WiimoteController.h"
|
||||
|
||||
WPADController::WPADController(size_t player_index, WPADDataFormat data_format)
|
||||
: EmulatedController(player_index), m_data_format(data_format)
|
||||
{
|
||||
}
|
||||
|
||||
WPADDataFormat WPADController::get_default_data_format() const
|
||||
{
|
||||
switch (get_device_type())
|
||||
{
|
||||
case kWAPDevCore:
|
||||
return kDataFormat_CORE_ACC_DPD;
|
||||
case kWAPDevFreestyle:
|
||||
return kDataFormat_FREESTYLE_ACC;
|
||||
case kWAPDevClassic:
|
||||
return kDataFormat_CLASSIC;
|
||||
case kWAPDevMPLS:
|
||||
return kDataFormat_MPLS;
|
||||
case kWAPDevMPLSFreeStyle:
|
||||
return kDataFormat_FREESTYLE_ACC_DPD;
|
||||
case kWAPDevMPLSClassic:
|
||||
return kDataFormat_CLASSIC_ACC_DPD;
|
||||
case kWAPDevURCC:
|
||||
return kDataFormat_URCC;
|
||||
default:
|
||||
return kDataFormat_CORE;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 WPADController::get_emulated_button_flag(WPADDataFormat format, uint32 id) const
|
||||
{
|
||||
switch(format)
|
||||
{
|
||||
case kDataFormat_CORE:
|
||||
case kDataFormat_CORE_ACC:
|
||||
case kDataFormat_CORE_ACC_DPD:
|
||||
case kDataFormat_CORE_ACC_DPD_FULL:
|
||||
case kDataFormat_FREESTYLE:
|
||||
case kDataFormat_FREESTYLE_ACC:
|
||||
case kDataFormat_FREESTYLE_ACC_DPD:
|
||||
case kDataFormat_MPLS:
|
||||
return WiimoteController::s_get_emulated_button_flag(id);
|
||||
case kDataFormat_CLASSIC:
|
||||
case kDataFormat_CLASSIC_ACC:
|
||||
case kDataFormat_CLASSIC_ACC_DPD:
|
||||
return ClassicController::s_get_emulated_button_flag(id);
|
||||
|
||||
case kDataFormat_TRAIN: break;
|
||||
case kDataFormat_GUITAR: break;
|
||||
case kDataFormat_BALANCE_CHECKER: break;
|
||||
case kDataFormat_DRUM: break;
|
||||
|
||||
case kDataFormat_TAIKO: break;
|
||||
case kDataFormat_URCC:
|
||||
return ProController::s_get_emulated_button_flag(id);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WPADController::WPADRead(WPADStatus_t* status)
|
||||
{
|
||||
controllers_update_states();
|
||||
uint32 button = 0;
|
||||
for (uint32 i = 1; i < get_highest_mapping_id(); ++i)
|
||||
{
|
||||
if (is_mapping_down(i))
|
||||
{
|
||||
const uint32 value = get_emulated_button_flag(m_data_format, i);
|
||||
button |= value;
|
||||
}
|
||||
}
|
||||
|
||||
m_homebutton_down |= is_home_down();
|
||||
|
||||
// todo fill position api from wiimote
|
||||
|
||||
switch (m_data_format)
|
||||
{
|
||||
case kDataFormat_CORE:
|
||||
case kDataFormat_CORE_ACC:
|
||||
case kDataFormat_CORE_ACC_DPD:
|
||||
case kDataFormat_CORE_ACC_DPD_FULL:
|
||||
{
|
||||
memset(status, 0x00, sizeof(*status));
|
||||
status->button = button;
|
||||
break;
|
||||
}
|
||||
|
||||
case kDataFormat_FREESTYLE:
|
||||
case kDataFormat_FREESTYLE_ACC:
|
||||
case kDataFormat_FREESTYLE_ACC_DPD:
|
||||
{
|
||||
WPADFSStatus_t* ex_status = (WPADFSStatus_t*)status;
|
||||
memset(ex_status, 0x00, sizeof(*ex_status));
|
||||
ex_status->button = button;
|
||||
|
||||
auto axis = get_axis();
|
||||
axis *= 127.0f;
|
||||
ex_status->fsStickX = (sint8)axis.x;
|
||||
ex_status->fsStickY = (sint8)axis.y;
|
||||
break;
|
||||
}
|
||||
|
||||
case kDataFormat_CLASSIC:
|
||||
case kDataFormat_CLASSIC_ACC:
|
||||
case kDataFormat_CLASSIC_ACC_DPD:
|
||||
case kDataFormat_GUITAR:
|
||||
case kDataFormat_DRUM:
|
||||
case kDataFormat_TAIKO:
|
||||
{
|
||||
WPADCLStatus_t* ex_status = (WPADCLStatus_t*)status;
|
||||
memset(ex_status, 0x00, sizeof(*ex_status));
|
||||
ex_status->clButton = button;
|
||||
|
||||
auto axis = get_axis();
|
||||
axis *= 2048.0f;
|
||||
ex_status->clLStickX = (uint16)axis.x;
|
||||
ex_status->clLStickY = (uint16)axis.y;
|
||||
|
||||
auto rotation = get_rotation();
|
||||
rotation *= 2048.0f;
|
||||
ex_status->clRStickX = (uint16)rotation.x;
|
||||
ex_status->clRStickY = (uint16)rotation.y;
|
||||
break;
|
||||
}
|
||||
case kDataFormat_TRAIN:
|
||||
{
|
||||
WPADTRStatus_t* ex_status = (WPADTRStatus_t*)status;
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
case kDataFormat_BALANCE_CHECKER:
|
||||
{
|
||||
WPADBLStatus_t* ex_status = (WPADBLStatus_t*)status;
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
case kDataFormat_MPLS:
|
||||
{
|
||||
WPADMPStatus_t* ex_status = (WPADMPStatus_t*)status;
|
||||
ex_status->stat = 1; // attached
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
case kDataFormat_URCC:
|
||||
{
|
||||
WPADUCStatus_t* ex_status = (WPADUCStatus_t*)status;
|
||||
memset(ex_status, 0x00, sizeof(*ex_status));
|
||||
ex_status->ucButton = button;
|
||||
|
||||
ex_status->cable = TRUE;
|
||||
ex_status->charge = TRUE;
|
||||
|
||||
auto axis = get_axis();
|
||||
axis *= 2048.0f;
|
||||
ex_status->ucLStickX = (uint16)axis.x;
|
||||
ex_status->ucLStickY = (uint16)axis.y;
|
||||
|
||||
auto rotation = get_rotation();
|
||||
rotation *= 2048.0f;
|
||||
ex_status->ucRStickX = (uint16)rotation.x;
|
||||
ex_status->ucRStickY = (uint16)rotation.y;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
cemu_assert(false);
|
||||
}
|
||||
|
||||
status->dev = get_device_type();
|
||||
status->err = WPAD_ERR_NONE;
|
||||
}
|
||||
|
||||
void WPADController::KPADRead(KPADStatus_t& status, const BtnRepeat& repeat)
|
||||
{
|
||||
uint32be* hold, *release, *trigger;
|
||||
switch (type())
|
||||
{
|
||||
case Pro:
|
||||
hold = &status.ex_status.uc.hold;
|
||||
release = &status.ex_status.uc.release;
|
||||
trigger = &status.ex_status.uc.trig;
|
||||
break;
|
||||
case Classic:
|
||||
hold = &status.ex_status.cl.hold;
|
||||
release = &status.ex_status.cl.release;
|
||||
trigger = &status.ex_status.cl.trig;
|
||||
break;
|
||||
default:
|
||||
hold = &status.hold;
|
||||
release = &status.release;
|
||||
trigger = &status.trig;
|
||||
}
|
||||
|
||||
controllers_update_states();
|
||||
for (uint32 i = 1; i < get_highest_mapping_id(); ++i)
|
||||
{
|
||||
if (is_mapping_down(i))
|
||||
{
|
||||
const uint32 value = get_emulated_button_flag(m_data_format, i);
|
||||
*hold |= value;
|
||||
}
|
||||
}
|
||||
|
||||
m_homebutton_down |= is_home_down();
|
||||
|
||||
// button repeat
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (*hold != m_last_holdvalue)
|
||||
{
|
||||
m_last_hold_change = m_last_pulse = now;
|
||||
}
|
||||
|
||||
if (repeat.pulse > 0)
|
||||
{
|
||||
if (m_last_hold_change + std::chrono::milliseconds(repeat.delay) >= now)
|
||||
{
|
||||
if ((m_last_pulse + std::chrono::milliseconds(repeat.pulse)) < now)
|
||||
{
|
||||
m_last_pulse = now;
|
||||
*hold |= kWPADButtonRepeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// axis
|
||||
const auto axis = get_axis();
|
||||
const auto rotation = get_rotation();
|
||||
|
||||
*release = m_last_holdvalue & ~*hold;
|
||||
//status.release = m_last_holdvalue & ~*hold;
|
||||
*trigger = ~m_last_holdvalue & *hold;
|
||||
//status.trig = ~m_last_holdvalue & *hold;
|
||||
m_last_holdvalue = *hold;
|
||||
|
||||
if (is_mpls_attached())
|
||||
{
|
||||
status.mpls.dir.X.x = 1;
|
||||
status.mpls.dir.X.y = 0;
|
||||
status.mpls.dir.X.z = 0;
|
||||
|
||||
status.mpls.dir.Y.x = 0;
|
||||
status.mpls.dir.Y.y = 1;
|
||||
status.mpls.dir.Y.z = 0;
|
||||
|
||||
status.mpls.dir.Z.x = 0;
|
||||
status.mpls.dir.Z.y = 0;
|
||||
status.mpls.dir.Z.z = 1;
|
||||
}
|
||||
|
||||
if (has_motion())
|
||||
{
|
||||
auto motion_sample = get_motion_data();
|
||||
|
||||
glm::vec3 acc;
|
||||
motion_sample.getAccelerometer(&acc[0]);
|
||||
status.acc.x = acc.x;
|
||||
status.acc.y = acc.y;
|
||||
status.acc.z = acc.z;
|
||||
|
||||
status.acc_value = motion_sample.getVPADAccMagnitude();
|
||||
status.acc_speed = motion_sample.getVPADAccAcceleration();
|
||||
|
||||
//glm::vec2 acc_vert;
|
||||
//motion_sample.getVPADAccXY(&acc_vert[0]);
|
||||
//status.acc_vertical.x = acc_vert.x;
|
||||
//status.acc_vertical.y = acc_vert.y;
|
||||
|
||||
status.accVertical.x = std::min(1.0f, std::abs(acc.x + acc.y));
|
||||
status.accVertical.y = std::min(std::max(-1.0f, -acc.z), 1.0f);
|
||||
|
||||
if (is_mpls_attached())
|
||||
{
|
||||
// todo
|
||||
glm::vec3 gyroChange;
|
||||
motion_sample.getVPADGyroChange(&gyroChange[0]);
|
||||
//const auto& gyroChange = motionSample.getVPADGyroChange();
|
||||
status.mpls.mpls.x = gyroChange.x;
|
||||
status.mpls.mpls.y = gyroChange.y;
|
||||
status.mpls.mpls.z = gyroChange.z;
|
||||
|
||||
//debug_printf("GyroChange %7.2lf %7.2lf %7.2lf\n", (float)status.gyroChange.x, (float)status.gyroChange.y, (float)status.gyroChange.z);
|
||||
|
||||
glm::vec3 gyroOrientation;
|
||||
motion_sample.getVPADOrientation(&gyroOrientation[0]);
|
||||
//const auto& gyroOrientation = motionSample.getVPADOrientation();
|
||||
status.mpls.angle.x = gyroOrientation.x;
|
||||
status.mpls.angle.y = gyroOrientation.y;
|
||||
status.mpls.angle.z = gyroOrientation.z;
|
||||
|
||||
float attitude[9];
|
||||
motion_sample.getVPADAttitudeMatrix(attitude);
|
||||
status.mpls.dir.X.x = attitude[0];
|
||||
status.mpls.dir.X.y = attitude[1];
|
||||
status.mpls.dir.X.z = attitude[2];
|
||||
status.mpls.dir.Y.x = attitude[3];
|
||||
status.mpls.dir.Y.y = attitude[4];
|
||||
status.mpls.dir.Y.z = attitude[5];
|
||||
status.mpls.dir.Z.x = attitude[6];
|
||||
status.mpls.dir.Z.y = attitude[7];
|
||||
status.mpls.dir.Z.z = attitude[8];
|
||||
}
|
||||
}
|
||||
|
||||
if (has_position())
|
||||
{
|
||||
status.dpd_valid_fg = 1;
|
||||
|
||||
const auto position = get_position();
|
||||
|
||||
const auto pos = (position * 2.0f) - 1.0f;
|
||||
status.pos.x = pos.x;
|
||||
status.pos.y = pos.y;
|
||||
|
||||
const auto delta = position - get_prev_position();
|
||||
status.vec.x = delta.x;
|
||||
status.vec.y = delta.y;
|
||||
status.speed = glm::length(delta);
|
||||
}
|
||||
|
||||
switch (type())
|
||||
{
|
||||
case Wiimote:
|
||||
status.ex_status.fs.stick.x = axis.x;
|
||||
status.ex_status.fs.stick.y = axis.y;
|
||||
|
||||
if(has_second_motion())
|
||||
{
|
||||
auto motion_sample = get_second_motion_data();
|
||||
|
||||
glm::vec3 acc;
|
||||
motion_sample.getAccelerometer(&acc[0]);
|
||||
status.ex_status.fs.acc.x = acc.x;
|
||||
status.ex_status.fs.acc.y = acc.y;
|
||||
status.ex_status.fs.acc.z = acc.z;
|
||||
|
||||
status.ex_status.fs.accValue = motion_sample.getVPADAccMagnitude();
|
||||
status.ex_status.fs.accSpeed = motion_sample.getVPADAccAcceleration();
|
||||
}
|
||||
|
||||
break;
|
||||
case Pro:
|
||||
status.ex_status.uc.lstick.x = axis.x;
|
||||
status.ex_status.uc.lstick.y = axis.y;
|
||||
|
||||
status.ex_status.uc.rstick.x = rotation.x;
|
||||
status.ex_status.uc.rstick.y = rotation.y;
|
||||
|
||||
status.ex_status.uc.charge = FALSE;
|
||||
status.ex_status.uc.cable = TRUE;
|
||||
|
||||
break;
|
||||
case Classic:
|
||||
status.ex_status.cl.lstick.x = axis.x;
|
||||
status.ex_status.cl.lstick.y = axis.y;
|
||||
|
||||
status.ex_status.cl.rstick.x = rotation.x;
|
||||
status.ex_status.cl.rstick.y = rotation.y;
|
||||
|
||||
if (HAS_FLAG((uint32)status.ex_status.cl.hold, kCLButton_ZL))
|
||||
status.ex_status.cl.ltrigger = 1.0f;
|
||||
|
||||
if (HAS_FLAG((uint32)status.ex_status.cl.hold, kCLButton_ZR))
|
||||
status.ex_status.cl.rtrigger = 1.0f;
|
||||
break;
|
||||
default:
|
||||
cemu_assert(false);
|
||||
}
|
||||
|
||||
|
||||
}
|
147
src/input/emulated/WPADController.h
Normal file
147
src/input/emulated/WPADController.h
Normal file
|
@ -0,0 +1,147 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/cmdargs.h>
|
||||
|
||||
#include "input/emulated/EmulatedController.h"
|
||||
#include "Cafe/OS/libs/padscore/padscore.h"
|
||||
#include "Cafe/OS/libs/vpad/vpad.h"
|
||||
|
||||
constexpr uint32 kWPADButtonRepeat = 0x80000000;
|
||||
|
||||
enum WPADDeviceType
|
||||
{
|
||||
kWAPDevCore = 0,
|
||||
kWAPDevFreestyle = 1,
|
||||
kWAPDevClassic = 2,
|
||||
kWAPDevMPLS = 5,
|
||||
kWAPDevMPLSFreeStyle = 6,
|
||||
kWAPDevMPLSClassic = 7,
|
||||
kWAPDevURCC = 31,
|
||||
kWAPDevNotFound = 253,
|
||||
kWAPDevUnknown = 255,
|
||||
};
|
||||
|
||||
// core, balanceboard
|
||||
enum WPADCoreButtons
|
||||
{
|
||||
kWPADButton_Left = 0x1,
|
||||
kWPADButton_Right = 0x2,
|
||||
kWPADButton_Down = 0x4,
|
||||
kWPADButton_Up = 0x8,
|
||||
kWPADButton_Plus = 0x10,
|
||||
kWPADButton_2 = 0x100,
|
||||
kWPADButton_1 = 0x200,
|
||||
kWPADButton_B = 0x400,
|
||||
kWPADButton_A = 0x800,
|
||||
kWPADButton_Minus = 0x1000,
|
||||
kWPADButton_Home = 0x8000,
|
||||
};
|
||||
|
||||
// Nunchuck aka Freestyle
|
||||
enum WPADNunchuckButtons
|
||||
{
|
||||
kWPADButton_Z = 0x2000,
|
||||
kWPADButton_C = 0x4000,
|
||||
};
|
||||
|
||||
// Classic Controller
|
||||
enum WPADClassicButtons
|
||||
{
|
||||
kCLButton_Up = 0x1,
|
||||
kCLButton_Left = 0x2,
|
||||
kCLButton_ZR = 0x4,
|
||||
kCLButton_X = 0x8,
|
||||
kCLButton_A = 0x10,
|
||||
kCLButton_Y = 0x20,
|
||||
kCLButton_B = 0x40,
|
||||
kCLButton_ZL = 0x80,
|
||||
kCLButton_R = 0x200,
|
||||
kCLButton_Plus = 0x400,
|
||||
kCLButton_Home = 0x800,
|
||||
kCLButton_Minus = 0x1000,
|
||||
kCLButton_L = 0x2000,
|
||||
kCLButton_Down = 0x4000,
|
||||
kCLButton_Right = 0x8000
|
||||
};
|
||||
|
||||
// Pro Controller aka URCC
|
||||
enum WPADProButtons
|
||||
{
|
||||
kProButton_Up = 0x1,
|
||||
kProButton_Left = 0x2,
|
||||
kProButton_ZR = 0x4,
|
||||
kProButton_X = 0x8,
|
||||
kProButton_A = 0x10,
|
||||
kProButton_Y = 0x20,
|
||||
kProButton_B = 0x40,
|
||||
kProButton_ZL = 0x80,
|
||||
kProButton_R = 0x200,
|
||||
kProButton_Plus = 0x400,
|
||||
kProButton_Home = 0x800,
|
||||
kProButton_Minus = 0x1000,
|
||||
kProButton_L = 0x2000,
|
||||
kProButton_Down = 0x4000,
|
||||
kProButton_Right = 0x8000,
|
||||
kProButton_StickR = 0x10000,
|
||||
kProButton_StickL = 0x20000
|
||||
};
|
||||
|
||||
enum WPADDataFormat {
|
||||
kDataFormat_CORE = 0,
|
||||
kDataFormat_CORE_ACC = 1,
|
||||
kDataFormat_CORE_ACC_DPD = 2,
|
||||
kDataFormat_FREESTYLE = 3,
|
||||
kDataFormat_FREESTYLE_ACC = 4,
|
||||
kDataFormat_FREESTYLE_ACC_DPD = 5,
|
||||
kDataFormat_CLASSIC = 6,
|
||||
kDataFormat_CLASSIC_ACC = 7,
|
||||
kDataFormat_CLASSIC_ACC_DPD = 8,
|
||||
kDataFormat_CORE_ACC_DPD_FULL = 9, // buttons, motion, pointing
|
||||
kDataFormat_TRAIN = 10,
|
||||
kDataFormat_GUITAR = 11,
|
||||
kDataFormat_BALANCE_CHECKER = 12,
|
||||
kDataFormat_DRUM = 15,
|
||||
kDataFormat_MPLS = 16, // buttons, motion, pointing, motion plus
|
||||
kDataFormat_TAIKO = 17,
|
||||
kDataFormat_URCC = 22, // buttons, URCC aka pro
|
||||
};
|
||||
|
||||
class WPADController : public EmulatedController
|
||||
{
|
||||
using base_type = EmulatedController;
|
||||
public:
|
||||
WPADController(size_t player_index, WPADDataFormat data_format);
|
||||
|
||||
uint32 get_emulated_button_flag(WPADDataFormat format, uint32 id) const;
|
||||
|
||||
virtual WPADDeviceType get_device_type() const = 0;
|
||||
|
||||
WPADDataFormat get_data_format() const { return m_data_format; }
|
||||
void set_data_format(WPADDataFormat data_format) { m_data_format = data_format; }
|
||||
|
||||
void WPADRead(WPADStatus_t* status);
|
||||
|
||||
void KPADRead(KPADStatus_t& status, const BtnRepeat& repeat);
|
||||
virtual bool is_mpls_attached() { return false; }
|
||||
|
||||
enum class ConnectCallbackStatus
|
||||
{
|
||||
None, // do nothing
|
||||
ReportDisconnect, // call disconnect
|
||||
ReportConnect, // call connect
|
||||
};
|
||||
ConnectCallbackStatus m_status = ConnectCallbackStatus::ReportConnect;
|
||||
ConnectCallbackStatus m_extension_status = ConnectCallbackStatus::ReportConnect;
|
||||
|
||||
WPADDataFormat get_default_data_format() const;
|
||||
|
||||
protected:
|
||||
WPADDataFormat m_data_format;
|
||||
|
||||
private:
|
||||
uint32be m_last_holdvalue = 0;
|
||||
|
||||
std::chrono::steady_clock::time_point m_last_hold_change{}, m_last_pulse{};
|
||||
|
||||
|
||||
};
|
184
src/input/emulated/WiimoteController.cpp
Normal file
184
src/input/emulated/WiimoteController.cpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
#include "input/emulated/WiimoteController.h"
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/Wiimote/NativeWiimoteController.h"
|
||||
|
||||
|
||||
WiimoteController::WiimoteController(size_t player_index)
|
||||
: WPADController(player_index, kDataFormat_CORE_ACC_DPD)
|
||||
{
|
||||
}
|
||||
|
||||
void WiimoteController::set_device_type(WPADDeviceType device_type)
|
||||
{
|
||||
m_device_type = device_type;
|
||||
m_data_format = get_default_data_format();
|
||||
}
|
||||
|
||||
bool WiimoteController::is_mpls_attached()
|
||||
{
|
||||
return m_device_type == kWAPDevMPLS || m_device_type == kWAPDevMPLSClassic || m_device_type == kWAPDevMPLSFreeStyle;
|
||||
}
|
||||
|
||||
uint32 WiimoteController::get_emulated_button_flag(uint32 id) const
|
||||
{
|
||||
return s_get_emulated_button_flag(id);
|
||||
}
|
||||
|
||||
bool WiimoteController::set_default_mapping(const std::shared_ptr<ControllerBase>& controller)
|
||||
{
|
||||
std::vector<std::pair<uint64, uint64>> mapping;
|
||||
switch (controller->api())
|
||||
{
|
||||
case InputAPI::Wiimote: {
|
||||
const auto sdl_controller = std::static_pointer_cast<NativeWiimoteController>(controller);
|
||||
mapping =
|
||||
{
|
||||
{kButtonId_A, kWiimoteButton_A},
|
||||
{kButtonId_B, kWiimoteButton_B},
|
||||
{kButtonId_1, kWiimoteButton_One},
|
||||
{kButtonId_2, kWiimoteButton_Two},
|
||||
|
||||
{kButtonId_Home, kWiimoteButton_Home},
|
||||
|
||||
{kButtonId_Plus, kWiimoteButton_Plus},
|
||||
{kButtonId_Minus, kWiimoteButton_Minus},
|
||||
|
||||
{kButtonId_Up, kWiimoteButton_Up},
|
||||
{kButtonId_Down, kWiimoteButton_Down},
|
||||
{kButtonId_Left, kWiimoteButton_Left},
|
||||
{kButtonId_Right, kWiimoteButton_Right},
|
||||
|
||||
{kButtonId_Nunchuck_Z, kWiimoteButton_Z},
|
||||
{kButtonId_Nunchuck_C, kWiimoteButton_C},
|
||||
|
||||
{kButtonId_Nunchuck_Up, kAxisYP},
|
||||
{kButtonId_Nunchuck_Down, kAxisYN},
|
||||
{kButtonId_Nunchuck_Left, kAxisXN},
|
||||
{kButtonId_Nunchuck_Right, kAxisXP},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
bool mapping_updated = false;
|
||||
std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m)
|
||||
{
|
||||
if (m_mappings.find(m.first) == m_mappings.cend())
|
||||
{
|
||||
set_mapping(m.first, controller, m.second);
|
||||
mapping_updated = true;
|
||||
}
|
||||
});
|
||||
|
||||
return mapping_updated;
|
||||
}
|
||||
|
||||
glm::vec2 WiimoteController::get_axis() const
|
||||
{
|
||||
const auto left = get_axis_value(kButtonId_Nunchuck_Left);
|
||||
const auto right = get_axis_value(kButtonId_Nunchuck_Right);
|
||||
|
||||
const auto up = get_axis_value(kButtonId_Nunchuck_Up);
|
||||
const auto down = get_axis_value(kButtonId_Nunchuck_Down);
|
||||
|
||||
glm::vec2 result;
|
||||
result.x = (left > right) ? -left : right;
|
||||
result.y = (up > down) ? up : -down;
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 WiimoteController::get_rotation() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
glm::vec2 WiimoteController::get_trigger() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void WiimoteController::load(const pugi::xml_node& node)
|
||||
{
|
||||
base_type::load(node);
|
||||
|
||||
if (const auto value = node.child("device_type"))
|
||||
m_device_type = ConvertString<WPADDeviceType>(value.child_value());
|
||||
}
|
||||
|
||||
void WiimoteController::save(pugi::xml_node& node)
|
||||
{
|
||||
base_type::save(node);
|
||||
|
||||
node.append_child("device_type").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (int)m_device_type).c_str());
|
||||
}
|
||||
|
||||
uint32 WiimoteController::s_get_emulated_button_flag(uint32 id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case kButtonId_A:
|
||||
return kWPADButton_A;
|
||||
case kButtonId_B:
|
||||
return kWPADButton_B;
|
||||
case kButtonId_1:
|
||||
return kWPADButton_1;
|
||||
case kButtonId_2:
|
||||
return kWPADButton_2;
|
||||
|
||||
case kButtonId_Plus:
|
||||
return kWPADButton_Plus;
|
||||
case kButtonId_Minus:
|
||||
return kWPADButton_Minus;
|
||||
case kButtonId_Home:
|
||||
return kWPADButton_Home;
|
||||
|
||||
case kButtonId_Up:
|
||||
return kWPADButton_Up;
|
||||
case kButtonId_Down:
|
||||
return kWPADButton_Down;
|
||||
case kButtonId_Left:
|
||||
return kWPADButton_Left;
|
||||
case kButtonId_Right:
|
||||
return kWPADButton_Right;
|
||||
|
||||
case kButtonId_Nunchuck_Z:
|
||||
return kWPADButton_Z;
|
||||
case kButtonId_Nunchuck_C:
|
||||
return kWPADButton_C;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string_view WiimoteController::get_button_name(ButtonId id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case kButtonId_A: return "A";
|
||||
case kButtonId_B: return "B";
|
||||
case kButtonId_1: return "1";
|
||||
case kButtonId_2: return "2";
|
||||
|
||||
case kButtonId_Home: return "home";
|
||||
case kButtonId_Plus: return "+";
|
||||
case kButtonId_Minus: return "-";
|
||||
|
||||
case kButtonId_Up: return "up";
|
||||
case kButtonId_Down: return "down";
|
||||
case kButtonId_Left: return "left";
|
||||
case kButtonId_Right: return "right";
|
||||
|
||||
case kButtonId_Nunchuck_Z: return "Z";
|
||||
case kButtonId_Nunchuck_C: return "C";
|
||||
|
||||
case kButtonId_Nunchuck_Up: return "up";
|
||||
case kButtonId_Nunchuck_Down: return "down";
|
||||
case kButtonId_Nunchuck_Left: return "left";
|
||||
case kButtonId_Nunchuck_Right: return "right";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
78
src/input/emulated/WiimoteController.h
Normal file
78
src/input/emulated/WiimoteController.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/emulated/WPADController.h"
|
||||
#include "gui/input/panels/WiimoteInputPanel.h"
|
||||
|
||||
class WiimoteController : public WPADController
|
||||
{
|
||||
using base_type = WPADController;
|
||||
public:
|
||||
enum ButtonId
|
||||
{
|
||||
kButtonId_None,
|
||||
|
||||
kButtonId_A,
|
||||
kButtonId_B,
|
||||
kButtonId_1,
|
||||
kButtonId_2,
|
||||
|
||||
kButtonId_Nunchuck_Z,
|
||||
kButtonId_Nunchuck_C,
|
||||
|
||||
kButtonId_Plus,
|
||||
kButtonId_Minus,
|
||||
|
||||
|
||||
kButtonId_Up,
|
||||
kButtonId_Down,
|
||||
kButtonId_Left,
|
||||
kButtonId_Right,
|
||||
|
||||
|
||||
kButtonId_Nunchuck_Up,
|
||||
kButtonId_Nunchuck_Down,
|
||||
kButtonId_Nunchuck_Left,
|
||||
kButtonId_Nunchuck_Right,
|
||||
|
||||
kButtonId_Home,
|
||||
|
||||
kButtonId_Max,
|
||||
};
|
||||
|
||||
WiimoteController(size_t player_index);
|
||||
|
||||
Type type() const override { return Type::Wiimote; }
|
||||
WPADDeviceType get_device_type() const override { return m_device_type; }
|
||||
void set_device_type(WPADDeviceType device_type);
|
||||
|
||||
bool is_mpls_attached() override;
|
||||
|
||||
uint32 get_emulated_button_flag(uint32 id) const override;
|
||||
size_t get_highest_mapping_id() const override { return kButtonId_Max; }
|
||||
bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_Nunchuck_Up && mapping <= kButtonId_Nunchuck_Right; }
|
||||
|
||||
bool set_default_mapping(const std::shared_ptr<ControllerBase>& controller) override;
|
||||
|
||||
glm::vec2 get_axis() const override;
|
||||
glm::vec2 get_rotation() const override;
|
||||
glm::vec2 get_trigger() const override;
|
||||
|
||||
void load(const pugi::xml_node& node) override;
|
||||
void save(pugi::xml_node& node) override;
|
||||
|
||||
static uint32 s_get_emulated_button_flag(uint32 id);
|
||||
|
||||
static std::string_view get_button_name(ButtonId id);
|
||||
|
||||
bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); }
|
||||
bool is_left_down() const override { return is_mapping_down(kButtonId_Left); }
|
||||
bool is_right_down() const override { return is_mapping_down(kButtonId_Right); }
|
||||
bool is_up_down() const override { return is_mapping_down(kButtonId_Up); }
|
||||
bool is_down_down() const override { return is_mapping_down(kButtonId_Down); }
|
||||
bool is_a_down() const override { return is_mapping_down(kButtonId_A); }
|
||||
bool is_b_down() const override { return is_mapping_down(kButtonId_B); }
|
||||
bool is_home_down() const override { return is_mapping_down(kButtonId_Home); }
|
||||
|
||||
private:
|
||||
WPADDeviceType m_device_type = kWAPDevCore;
|
||||
};
|
165
src/input/motion/Mahony.h
Normal file
165
src/input/motion/Mahony.h
Normal file
|
@ -0,0 +1,165 @@
|
|||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
#include "util/math/quaternion.h"
|
||||
|
||||
class MahonySensorFusion
|
||||
{
|
||||
public:
|
||||
MahonySensorFusion()
|
||||
{
|
||||
// assume default forward pose (holding controller in hand, tilted forward so the sticks/buttons face upward)
|
||||
m_imuQ.Assign(sqrtf(0.5), sqrtf(0.5), 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// gx, gy, gz are in radians/sec
|
||||
void updateIMU(float deltaTime, float gx, float gy, float gz, float ax, float ay, float az)
|
||||
{
|
||||
Vector3f av(ax, ay, az);
|
||||
Vector3f gv(gx, gy, gz);
|
||||
if (deltaTime > 0.2f)
|
||||
deltaTime = 0.2f; // dont let stutter mess up the internal state
|
||||
updateGyroBias(gx, gy, gz);
|
||||
gv.x -= m_gyroBias[0];
|
||||
gv.y -= m_gyroBias[1];
|
||||
gv.z -= m_gyroBias[2];
|
||||
|
||||
// ignore small angles to avoid drift due to bias (especially on yaw)
|
||||
if (fabs(gv.x) < 0.015f)
|
||||
gv.x = 0.0f;
|
||||
if (fabs(gv.y) < 0.015f)
|
||||
gv.y = 0.0f;
|
||||
if (fabs(gv.z) < 0.015f)
|
||||
gv.z = 0.0f;
|
||||
|
||||
// forceLogDebug_printf("[IMU Quat] time %7.4lf | %7.2lf %7.2lf %7.2lf %7.2lf | gyro( - bias) %7.4lf %7.4lf %7.4lf | acc %7.2lf %7.2lf %7.2lf | GyroBias %7.4lf %7.4lf %7.4lf", deltaTime, m_imuQ.x, m_imuQ.y, m_imuQ.z, m_imuQ.w, gv.x, gv.y, gv.z, ax, ay, az, m_gyroBias[0], m_gyroBias[1], m_gyroBias[2]);
|
||||
|
||||
if (fabs(av.x) > 0.000001f || fabs(av.y) > 0.000001f || fabs(av.z) > 0.000001f)
|
||||
{
|
||||
av.Normalize();
|
||||
Vector3f grav = m_imuQ.GetVectorZ();
|
||||
grav.Scale(0.5f);
|
||||
Vector3f errorFeedback = grav.Cross(av);
|
||||
// apply scaled feedback
|
||||
gv -= errorFeedback;
|
||||
}
|
||||
gv.Scale(0.5f * deltaTime);
|
||||
m_imuQ += (m_imuQ * Quaternionf(0.0f, gv.x, gv.y, gv.z));
|
||||
m_imuQ.NormalizeXYZW();
|
||||
updateOrientationAngles();
|
||||
}
|
||||
|
||||
float getRollRadians()
|
||||
{
|
||||
return m_roll + (float)m_rollWinding * 2.0f * 3.14159265f;
|
||||
}
|
||||
|
||||
float getPitchRadians()
|
||||
{
|
||||
return m_pitch + (float)m_pitchWinding * 2.0f * 3.14159265f;
|
||||
}
|
||||
|
||||
float getYawRadians()
|
||||
{
|
||||
return m_yaw + (float)m_yawWinding * 2.0f * 3.14159265f;
|
||||
}
|
||||
|
||||
void getQuaternion(float q[4]) const
|
||||
{
|
||||
q[0] = m_imuQ.w;
|
||||
q[1] = m_imuQ.x;
|
||||
q[2] = m_imuQ.y;
|
||||
q[3] = m_imuQ.z;
|
||||
}
|
||||
|
||||
void getGyroBias(float gBias[3]) const
|
||||
{
|
||||
gBias[0] = m_gyroBias[0];
|
||||
gBias[1] = m_gyroBias[1];
|
||||
gBias[2] = m_gyroBias[2];
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// calculate roll, yaw and pitch in radians. (-0.5 to 0.5)
|
||||
void calcOrientation()
|
||||
{
|
||||
float sinr_cosp = 2.0f * (m_imuQ.z * m_imuQ.w + m_imuQ.x * m_imuQ.y);
|
||||
float cosr_cosp = 1.0f - 2.0f * (m_imuQ.w * m_imuQ.w + m_imuQ.x * m_imuQ.x);
|
||||
m_roll = std::atan2(sinr_cosp, cosr_cosp);
|
||||
|
||||
// pitch (y-axis rotation)
|
||||
float sinp = 2.0f * (m_imuQ.z * m_imuQ.x - m_imuQ.y * m_imuQ.w);
|
||||
if (std::abs(sinp) >= 1.0)
|
||||
m_pitch = std::copysign(3.14159265359f / 2.0f, sinp);
|
||||
else
|
||||
m_pitch = std::asin(sinp);
|
||||
|
||||
// yaw (z-axis rotation)
|
||||
float siny_cosp = 2.0f * (m_imuQ.z * m_imuQ.y + m_imuQ.w * m_imuQ.x);
|
||||
float cosy_cosp = 1.0f - 2.0f * (m_imuQ.x * m_imuQ.x + m_imuQ.y * m_imuQ.y);
|
||||
m_yaw = std::atan2(siny_cosp, cosy_cosp);
|
||||
}
|
||||
|
||||
void updateOrientationAngles()
|
||||
{
|
||||
auto calcWindingCountChange = [](float prevAngle, float newAngle) -> int
|
||||
{
|
||||
if (newAngle > prevAngle)
|
||||
{
|
||||
float angleDif = newAngle - prevAngle;
|
||||
if (angleDif > 3.14159265f)
|
||||
return -1;
|
||||
}
|
||||
else if (newAngle < prevAngle)
|
||||
{
|
||||
float angleDif = prevAngle - newAngle;
|
||||
if (angleDif > 3.14159265f)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
float prevRoll = m_roll;
|
||||
float prevPitch = m_pitch;
|
||||
float prevYaw = m_yaw;
|
||||
calcOrientation();
|
||||
// calculate roll, yaw and pitch including winding count to match what VPAD API returns
|
||||
m_rollWinding += calcWindingCountChange(prevRoll, m_roll);
|
||||
m_pitchWinding += calcWindingCountChange(prevPitch, m_pitch);
|
||||
m_yawWinding += calcWindingCountChange(prevYaw, m_yaw);
|
||||
}
|
||||
|
||||
void updateGyroBias(float gx, float gy, float gz)
|
||||
{
|
||||
// dont let actual movement influence the bias
|
||||
// but be careful about setting this too low, there are controllers out there with really bad bias (my Switch Pro had -0.0933 0.0619 0.0179 in resting state)
|
||||
if (fabs(gx) >= 0.35f || fabs(gy) >= 0.35f || fabs(gz) >= 0.35f)
|
||||
return;
|
||||
|
||||
m_gyroTotalSum[0] += gx;
|
||||
m_gyroTotalSum[1] += gy;
|
||||
m_gyroTotalSum[2] += gz;
|
||||
m_gyroTotalSampleCount++;
|
||||
if (m_gyroTotalSampleCount >= 200)
|
||||
{
|
||||
m_gyroBias[0] = (float)(m_gyroTotalSum[0] / (double)m_gyroTotalSampleCount);
|
||||
m_gyroBias[1] = (float)(m_gyroTotalSum[1] / (double)m_gyroTotalSampleCount);
|
||||
m_gyroBias[2] = (float)(m_gyroTotalSum[2] / (double)m_gyroTotalSampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Quaternionf m_imuQ; // current orientation
|
||||
// angle data
|
||||
float m_roll{};
|
||||
float m_pitch{};
|
||||
float m_yaw{};
|
||||
int m_rollWinding{};
|
||||
int m_pitchWinding{};
|
||||
int m_yawWinding{};
|
||||
// gyro bias
|
||||
float m_gyroBias[3]{};
|
||||
double m_gyroTotalSum[3]{};
|
||||
uint64 m_gyroTotalSampleCount{};
|
||||
};
|
60
src/input/motion/MotionHandler.h
Normal file
60
src/input/motion/MotionHandler.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
#include "Mahony.h"
|
||||
#include "MotionSample.h"
|
||||
|
||||
// utility class to translate external motion input (DSU, SDL GamePad sensors) into the values expected by VPAD API (and maybe others in the future)
|
||||
class WiiUMotionHandler
|
||||
{
|
||||
public:
|
||||
// gyro is in radians/sec
|
||||
void processMotionSample(
|
||||
float deltaTime,
|
||||
float gx, float gy, float gz,
|
||||
float accx, float accy, float accz)
|
||||
{
|
||||
m_gyro[0] = gx;
|
||||
m_gyro[1] = gy;
|
||||
m_gyro[2] = gz;
|
||||
m_prevAcc[0] = m_acc[0];
|
||||
m_prevAcc[1] = m_acc[1];
|
||||
m_prevAcc[2] = m_acc[2];
|
||||
m_acc[0] = accx;
|
||||
m_acc[1] = accy;
|
||||
m_acc[2] = accz;
|
||||
// integrate acc and gyro samples into IMU
|
||||
m_imu.updateIMU(deltaTime, gx, gy, gz, accx, accy, accz);
|
||||
|
||||
// get orientation from IMU
|
||||
m_orientation[0] = _radToOrientation(-m_imu.getYawRadians()) - 0.50f;
|
||||
m_orientation[1] = _radToOrientation(-m_imu.getPitchRadians()) - 0.50f;
|
||||
m_orientation[2] = _radToOrientation(m_imu.getRollRadians());
|
||||
}
|
||||
|
||||
MotionSample getMotionSample()
|
||||
{
|
||||
float q[4];
|
||||
m_imu.getQuaternion(q);
|
||||
float gBias[3];
|
||||
m_imu.getGyroBias(gBias);
|
||||
float gyroDebiased[3];
|
||||
gyroDebiased[0] = m_gyro[0] - gBias[0];
|
||||
gyroDebiased[1] = m_gyro[1] - gBias[1];
|
||||
gyroDebiased[2] = m_gyro[2] - gBias[2];
|
||||
return MotionSample(m_acc, MotionSample::calculateAccAcceleration(m_prevAcc, m_acc), gyroDebiased, m_orientation, q);
|
||||
}
|
||||
private:
|
||||
|
||||
// VPAD orientation unit is 1.0 = one revolution around the axis
|
||||
float _radToOrientation(float rad)
|
||||
{
|
||||
return rad / (2.0f * 3.14159265f);
|
||||
}
|
||||
|
||||
MahonySensorFusion m_imu;
|
||||
// state
|
||||
float m_gyro[3]{};
|
||||
float m_acc[3]{};
|
||||
float m_prevAcc[3]{};
|
||||
// calculated values
|
||||
float m_orientation[3]{};
|
||||
};
|
319
src/input/motion/MotionSample.h
Normal file
319
src/input/motion/MotionSample.h
Normal file
|
@ -0,0 +1,319 @@
|
|||
#pragma once
|
||||
#include "util/math/vector3.h"
|
||||
#include "util/math/quaternion.h"
|
||||
|
||||
struct Quat
|
||||
{
|
||||
float w;
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
|
||||
Quat()
|
||||
{
|
||||
w = 1.0f;
|
||||
x = 0.0f;
|
||||
y = 0.0f;
|
||||
z = 0.0f;
|
||||
}
|
||||
|
||||
Quat(float inW, float inX, float inY, float inZ)
|
||||
{
|
||||
w = inW;
|
||||
x = inX;
|
||||
y = inY;
|
||||
z = inZ;
|
||||
}
|
||||
|
||||
static Quat AngleAxis(float inAngle, float inX, float inY, float inZ)
|
||||
{
|
||||
Quat result = Quat(cosf(inAngle * 0.5f), inX, inY, inZ);
|
||||
result.Normalize();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Set(float inW, float inX, float inY, float inZ)
|
||||
{
|
||||
w = inW;
|
||||
x = inX;
|
||||
y = inY;
|
||||
z = inZ;
|
||||
}
|
||||
|
||||
Quat& operator*=(const Quat& rhs)
|
||||
{
|
||||
Set(w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z,
|
||||
w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y,
|
||||
w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x,
|
||||
w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend Quat operator*(Quat lhs, const Quat& rhs)
|
||||
{
|
||||
lhs *= rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
void Normalize()
|
||||
{
|
||||
//printf("Normalizing: %.4f, %.4f, %.4f, %.4f\n", w, x, y, z);
|
||||
const float length = sqrtf(x * x + y * y + z * z);
|
||||
float targetLength = 1.0f - w * w;
|
||||
if (targetLength <= 0.0f || length <= 0.0f)
|
||||
{
|
||||
Set(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
return;
|
||||
}
|
||||
targetLength = sqrtf(targetLength);
|
||||
const float fixFactor = targetLength / length;
|
||||
|
||||
x *= fixFactor;
|
||||
y *= fixFactor;
|
||||
z *= fixFactor;
|
||||
|
||||
//printf("Normalized: %.4f, %.4f, %.4f, %.4f\n", w, x, y, z);
|
||||
return;
|
||||
}
|
||||
|
||||
Quat Normalized() const
|
||||
{
|
||||
Quat result = *this;
|
||||
result.Normalize();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Conjugate()
|
||||
{
|
||||
x = -x;
|
||||
y = -y;
|
||||
z = -z;
|
||||
return;
|
||||
}
|
||||
|
||||
Quat Conjugated() const
|
||||
{
|
||||
Quat result = *this;
|
||||
result.Conjugate();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// helper class to store unified motion data
|
||||
// supports retrieving values in their API-specific (VPAD, KPAD etc.) format
|
||||
class MotionSample
|
||||
{
|
||||
public:
|
||||
MotionSample()
|
||||
{
|
||||
}
|
||||
|
||||
MotionSample(float acc[3], float accAcceleration, float gyro[3], float orientation[3],
|
||||
float quaternion[4]
|
||||
)
|
||||
{
|
||||
m_acc[0] = acc[0];
|
||||
m_acc[1] = acc[1];
|
||||
m_acc[2] = acc[2];
|
||||
m_accAcceleration = accAcceleration;
|
||||
m_gyro[0] = gyro[0];
|
||||
m_gyro[1] = gyro[1];
|
||||
m_gyro[2] = gyro[2];
|
||||
m_orientation[0] = orientation[0];
|
||||
m_orientation[1] = orientation[1];
|
||||
m_orientation[2] = orientation[2];
|
||||
m_q[0] = quaternion[0];
|
||||
m_q[1] = quaternion[1];
|
||||
m_q[2] = quaternion[2];
|
||||
m_q[3] = quaternion[3];
|
||||
m_accMagnitude = sqrtf(m_acc[0] * m_acc[0] + m_acc[1] * m_acc[1] + m_acc[2] * m_acc[2]);
|
||||
}
|
||||
|
||||
void getVPADOrientation(float orientation[3])
|
||||
{
|
||||
orientation[0] = m_orientation[0];
|
||||
orientation[1] = m_orientation[1];
|
||||
orientation[2] = m_orientation[2];
|
||||
}
|
||||
|
||||
void getVPADGyroChange(float gyro[3])
|
||||
{
|
||||
// filter noise
|
||||
if (fabs(gyro[0]) < 0.012f)
|
||||
gyro[0] = 0.0f;
|
||||
if (fabs(gyro[1]) < 0.012f)
|
||||
gyro[1] = 0.0f;
|
||||
if (fabs(gyro[2]) < 0.012f)
|
||||
gyro[2] = 0.0f;
|
||||
// convert
|
||||
gyro[0] = _radToOrientation(-m_gyro[0]);
|
||||
gyro[1] = _radToOrientation(-m_gyro[1]);
|
||||
gyro[2] = _radToOrientation(m_gyro[2]);
|
||||
}
|
||||
|
||||
void getVPADAccelerometer(float acc[3])
|
||||
{
|
||||
acc[0] = -m_acc[0];
|
||||
acc[1] = -m_acc[1];
|
||||
acc[2] = m_acc[2];
|
||||
}
|
||||
|
||||
float getVPADAccMagnitude()
|
||||
{
|
||||
return m_accMagnitude;
|
||||
}
|
||||
|
||||
float getVPADAccAcceleration() // Possibly not entirely correct. Our results are smaller than VPAD API ones
|
||||
{
|
||||
return m_accAcceleration;
|
||||
}
|
||||
|
||||
void getVPADAccXY(float accXY[2])
|
||||
{
|
||||
float invMag = 1.0f / m_accMagnitude;
|
||||
float normAcc[3];
|
||||
normAcc[0] = m_acc[0] * invMag;
|
||||
normAcc[1] = m_acc[1] * invMag;
|
||||
normAcc[2] = m_acc[2] * invMag;
|
||||
accXY[0] = sqrtf(normAcc[0] * normAcc[0] + normAcc[1] * normAcc[1]);
|
||||
accXY[1] = -sin(getAtanPitch(-normAcc[2], normAcc[0], -normAcc[1]));
|
||||
}
|
||||
|
||||
void getXVector(float vOut[3], Quaternionf& q)
|
||||
{
|
||||
float X = q.x;
|
||||
float Y = q.y;
|
||||
float Z = q.z;
|
||||
float W = q.w;
|
||||
float xy = X * Y;
|
||||
float xz = X * Z;
|
||||
float yy = Y * Y;
|
||||
float yw = Y * W;
|
||||
float zz = Z * Z;
|
||||
float zw = Z * W;
|
||||
vOut[0] = 1.0f - 2.0f * (yy + zz); // x.x
|
||||
vOut[2] = 2.0f * (xy + zw); // x.y
|
||||
vOut[1] = 2.0f * (xz - yw); // x.z
|
||||
}
|
||||
|
||||
void getVPADAttitudeMatrix(float mtx[9])
|
||||
{
|
||||
// VPADs attitude matrix has mixed axis handedness, the most sane way to replicate it is by generating Y and Z by rotating the X vector
|
||||
Quaternionf qImu(m_q[0], m_q[1], m_q[2], m_q[3]);
|
||||
Quaternionf qY = qImu * Quaternionf::FromAngleAxis(1.5708f * 1.0f, 0.0f, 0.0f, 1.0f);
|
||||
Quaternionf qZ = qImu * Quaternionf::FromAngleAxis(1.5708f * 1.0f, 0.0f, 1.0f, 0.0f);
|
||||
getXVector(mtx + 0, qImu);
|
||||
getXVector(mtx + 3, qY);
|
||||
getXVector(mtx + 6, qZ);
|
||||
}
|
||||
|
||||
static float calculateAccAcceleration(float prevAcc[3], float currentAcc[3])
|
||||
{
|
||||
float ax = currentAcc[0] - prevAcc[0];
|
||||
float ay = currentAcc[1] - prevAcc[1];
|
||||
float az = currentAcc[2] - prevAcc[2];
|
||||
return sqrtf(ax * ax + ay * ay + az * az);
|
||||
}
|
||||
|
||||
void getAccelerometer(float acc[3])
|
||||
{
|
||||
acc[0] = m_acc[0];
|
||||
acc[1] = m_acc[1];
|
||||
acc[2] = m_acc[2];
|
||||
}
|
||||
void getGyrometer(float gyro[3])
|
||||
{
|
||||
gyro[0] = m_gyro[0];
|
||||
gyro[1] = m_gyro[1];
|
||||
gyro[2] = m_gyro[2];
|
||||
}
|
||||
|
||||
private:
|
||||
static float _radToOrientation(float rad)
|
||||
{
|
||||
return rad / (2.0f * 3.14159265f);
|
||||
}
|
||||
|
||||
static float getAtanPitch(float X, float Y, float Z)
|
||||
{
|
||||
return atan2f(-X, sqrtf(Y * Y + Z * Z));
|
||||
}
|
||||
|
||||
// provided values
|
||||
float m_gyro[3]{};
|
||||
float m_acc[3]{};
|
||||
float m_accAcceleration{};
|
||||
float m_orientation[3]{};
|
||||
float m_q[4]{};
|
||||
// calculated values
|
||||
float m_accMagnitude{};
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Captured VPAD attitude values
|
||||
|
||||
Assuming GamePad is in a direct line between player (holder) and the monitor
|
||||
|
||||
DRC flat on table, screen facing up. Top pointing away (away from person, pointing towards monitor):
|
||||
1.00 -0.03 -0.00
|
||||
0.03 0.99 -0.13
|
||||
0.01 0.13 0.99
|
||||
|
||||
Turned 45° to the right:
|
||||
0.71 -0.03 0.71
|
||||
0.12 0.99 -0.08
|
||||
-0.70 0.14 0.70
|
||||
|
||||
Turned 45° to the right (top of GamePad pointing right now):
|
||||
0.08 -0.03 1.00 -> Z points towards person
|
||||
0.15 0.99 0.01
|
||||
-0.99 0.15 0.09 -> DRC Z-Axis now points towards X-minus
|
||||
|
||||
Turned 90° to the right (top of gamepad now pointing towards holder, away from monitor):
|
||||
-1.00 -0.01 0.06
|
||||
0.00 0.99 0.15
|
||||
-0.06 0.15 -0.99
|
||||
|
||||
Turned 90° to the right (pointing left):
|
||||
-0.17 -0.01 -0.99
|
||||
-0.13 0.99 0.02
|
||||
0.98 0.13 -0.17
|
||||
|
||||
After another 90° we end up in the initial position:
|
||||
0.99 -0.03 -0.11
|
||||
0.01 0.99 -0.13
|
||||
0.12 0.12 0.99
|
||||
|
||||
------
|
||||
From initial position, lean the GamePad on its left side. 45° up. So the screen is pointing to the top left
|
||||
0.66 -0.75 -0.03
|
||||
0.74 0.66 -0.11
|
||||
0.10 0.05 0.99
|
||||
|
||||
Further 45°, GamePad now on its left, screen pointing left:
|
||||
-0.03 -1.00 -0.00
|
||||
0.99 -0.03 -0.15
|
||||
0.15 -0.01 0.99
|
||||
|
||||
From initial position, lean the GamePad on its right side. 45° up. So the screen is pointing to the top right
|
||||
0.75 0.65 -0.11
|
||||
-0.65 0.76 0.07
|
||||
0.12 0.02 0.99
|
||||
|
||||
From initial position, tilt the GamePad up 90° (bottom side remains in touch with surface):
|
||||
0.99 -0.05 -0.10
|
||||
-0.10 0.01 -0.99
|
||||
0.05 1.00 0.01
|
||||
|
||||
From initial position, stand the GamePad on its top side:
|
||||
1.00 -0.01 -0.09
|
||||
0.09 -0.01 1.00
|
||||
-0.01 -1.00 -0.01
|
||||
|
||||
Rotate GamePad 180° around x axis, so it now lies on its screen (top of GamePad pointing to holder):
|
||||
0.99 -0.03 -0.15
|
||||
-0.04 -1.00 -0.08
|
||||
-0.15 0.09 -0.99
|
||||
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue