Add all the files

This commit is contained in:
Exzap 2022-08-22 22:21:23 +02:00
parent e3db07a16a
commit d60742f52b
1445 changed files with 430238 additions and 0 deletions

98
src/input/CMakeLists.txt Normal file
View 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 ../)

View 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 {};
}
}

View 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
View 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
View 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());
}
}
};

View 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
View 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>;

View 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{};
};

View 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);*/
}

View 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);
}
};

View 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;
}

View 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;
};

View 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;
}

View 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{};
};

View 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));
}

View 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)

View 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;
}

View 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{};
};

View 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();
}

View 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

View 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

View 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

View 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

View 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
View 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));
}
}

View 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;
}

View 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;
};

View 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;
}

View 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;
};

View 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;
}

View 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{};
};

View 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;
}
}
}
}

View 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{};
};

View 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;
}

View 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;
};

File diff suppressed because it is too large Load diff

View 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);
};

View 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>;

View 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;
};

View 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;
}

View 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;

View 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;
}

View 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;
};

View 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;
}

View 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

View 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;
}

View 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;
};

View 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);
}

View 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)));
}
};

View 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;
}

View 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;
};

View 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;
}

View 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{};
};

View 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);
}
}

View 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{};
};

View 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 "";
}
}

View 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
View 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{};
};

View 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]{};
};

View 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
*/