committed by
Narr the Reg
9 changed files with 2025 additions and 0 deletions
-
8src/core/CMakeLists.txt
-
208src/core/hid/emulated_console.cpp
-
142src/core/hid/emulated_console.h
-
745src/core/hid/emulated_controller.cpp
-
232src/core/hid/emulated_controller.h
-
349src/core/hid/emulated_devices.cpp
-
137src/core/hid/emulated_devices.h
-
144src/core/hid/hid_core.cpp
-
60src/core/hid/hid_core.h
@ -0,0 +1,208 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included
|
||||
|
|
||||
|
#include <fmt/format.h>
|
||||
|
|
||||
|
#include "core/hid/emulated_console.h"
|
||||
|
#include "core/hid/input_converter.h"
|
||||
|
|
||||
|
namespace Core::HID { |
||||
|
EmulatedConsole::EmulatedConsole() {} |
||||
|
|
||||
|
EmulatedConsole::~EmulatedConsole() = default; |
||||
|
|
||||
|
void EmulatedConsole::ReloadFromSettings() { |
||||
|
// Using first motion device from player 1. No need to assign a special config at the moment
|
||||
|
const auto& player = Settings::values.players.GetValue()[0]; |
||||
|
motion_params = Common::ParamPackage(player.motions[0]); |
||||
|
|
||||
|
ReloadInput(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::ReloadInput() { |
||||
|
motion_devices = Input::CreateDevice<Input::InputDevice>(motion_params); |
||||
|
if (motion_devices) { |
||||
|
Input::InputCallback motion_callback{ |
||||
|
[this](Input::CallbackStatus callback) { SetMotion(callback); }}; |
||||
|
motion_devices->SetCallback(motion_callback); |
||||
|
} |
||||
|
|
||||
|
// TODO: Fix this mess
|
||||
|
std::size_t index = 0; |
||||
|
const std::string mouse_device_string = |
||||
|
fmt::format("engine:mouse,axis_x:10,axis_y:11,button:{}", index); |
||||
|
touch_devices[index] = Input::CreateDeviceFromString<Input::InputDevice>(mouse_device_string); |
||||
|
Input::InputCallback trigger_callbackk{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetTouch(callback, index); }}; |
||||
|
touch_devices[index]->SetCallback(trigger_callbackk); |
||||
|
|
||||
|
index++; |
||||
|
const auto button_index = |
||||
|
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue()); |
||||
|
const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons; |
||||
|
for (const auto& config_entry : touch_buttons) { |
||||
|
Common::ParamPackage params{config_entry}; |
||||
|
Common::ParamPackage touch_button_params; |
||||
|
const int x = params.Get("x", 0); |
||||
|
const int y = params.Get("y", 0); |
||||
|
params.Erase("x"); |
||||
|
params.Erase("y"); |
||||
|
touch_button_params.Set("engine", "touch_from_button"); |
||||
|
touch_button_params.Set("button", params.Serialize()); |
||||
|
touch_button_params.Set("x", x); |
||||
|
touch_button_params.Set("y", y); |
||||
|
touch_button_params.Set("touch_id", static_cast<int>(index)); |
||||
|
LOG_ERROR(Common, "{} ", touch_button_params.Serialize()); |
||||
|
touch_devices[index] = |
||||
|
Input::CreateDeviceFromString<Input::InputDevice>(touch_button_params.Serialize()); |
||||
|
if (!touch_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
Input::InputCallback trigger_callback{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetTouch(callback, index); }}; |
||||
|
touch_devices[index]->SetCallback(trigger_callback); |
||||
|
index++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::UnloadInput() { |
||||
|
motion_devices.reset(); |
||||
|
for (auto& touch : touch_devices) { |
||||
|
touch.reset(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::EnableConfiguration() { |
||||
|
is_configuring = true; |
||||
|
SaveCurrentConfig(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::DisableConfiguration() { |
||||
|
is_configuring = false; |
||||
|
} |
||||
|
|
||||
|
bool EmulatedConsole::IsConfiguring() const { |
||||
|
return is_configuring; |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::SaveCurrentConfig() { |
||||
|
if (!is_configuring) { |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::RestoreConfig() { |
||||
|
if (!is_configuring) { |
||||
|
return; |
||||
|
} |
||||
|
ReloadFromSettings(); |
||||
|
} |
||||
|
|
||||
|
Common::ParamPackage EmulatedConsole::GetMotionParam() const { |
||||
|
return motion_params; |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::SetMotionParam(Common::ParamPackage param) { |
||||
|
motion_params = param; |
||||
|
ReloadInput(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::SetMotion(Input::CallbackStatus callback) { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
auto& raw_status = console.motion_values.raw_status; |
||||
|
auto& emulated = console.motion_values.emulated; |
||||
|
|
||||
|
raw_status = TransformToMotion(callback); |
||||
|
emulated.SetAcceleration(Common::Vec3f{ |
||||
|
raw_status.accel.x.value, |
||||
|
raw_status.accel.y.value, |
||||
|
raw_status.accel.z.value, |
||||
|
}); |
||||
|
emulated.SetGyroscope(Common::Vec3f{ |
||||
|
raw_status.gyro.x.value, |
||||
|
raw_status.gyro.y.value, |
||||
|
raw_status.gyro.z.value, |
||||
|
}); |
||||
|
emulated.UpdateRotation(raw_status.delta_timestamp); |
||||
|
emulated.UpdateOrientation(raw_status.delta_timestamp); |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
TriggerOnChange(ConsoleTriggerType::Motion); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
auto& motion = console.motion_state; |
||||
|
motion.accel = emulated.GetAcceleration(); |
||||
|
motion.gyro = emulated.GetGyroscope(); |
||||
|
motion.rotation = emulated.GetGyroscope(); |
||||
|
motion.orientation = emulated.GetOrientation(); |
||||
|
motion.quaternion = emulated.GetQuaternion(); |
||||
|
motion.is_at_rest = emulated.IsMoving(motion_sensitivity); |
||||
|
|
||||
|
TriggerOnChange(ConsoleTriggerType::Motion); |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::SetTouch(Input::CallbackStatus callback, [[maybe_unused]] std::size_t index) { |
||||
|
if (index >= console.touch_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
|
||||
|
console.touch_values[index] = TransformToTouch(callback); |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
TriggerOnChange(ConsoleTriggerType::Touch); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
console.touch_state[index] = { |
||||
|
.position = {console.touch_values[index].x.value, console.touch_values[index].y.value}, |
||||
|
.id = console.touch_values[index].id, |
||||
|
.pressed = console.touch_values[index].pressed.value, |
||||
|
}; |
||||
|
|
||||
|
TriggerOnChange(ConsoleTriggerType::Touch); |
||||
|
} |
||||
|
|
||||
|
ConsoleMotionValues EmulatedConsole::GetMotionValues() const { |
||||
|
return console.motion_values; |
||||
|
} |
||||
|
|
||||
|
TouchValues EmulatedConsole::GetTouchValues() const { |
||||
|
return console.touch_values; |
||||
|
} |
||||
|
|
||||
|
ConsoleMotion EmulatedConsole::GetMotion() const { |
||||
|
return console.motion_state; |
||||
|
} |
||||
|
|
||||
|
TouchFingerState EmulatedConsole::GetTouch() const { |
||||
|
return console.touch_state; |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) { |
||||
|
for (const std::pair<int, ConsoleUpdateCallback> poller_pair : callback_list) { |
||||
|
const ConsoleUpdateCallback& poller = poller_pair.second; |
||||
|
if (poller.on_change) { |
||||
|
poller.on_change(type); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
callback_list.insert_or_assign(last_callback_key, update_callback); |
||||
|
return last_callback_key++; |
||||
|
} |
||||
|
|
||||
|
void EmulatedConsole::DeleteCallback(int key) { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
if (!callback_list.contains(key)) { |
||||
|
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); |
||||
|
return; |
||||
|
} |
||||
|
callback_list.erase(key); |
||||
|
} |
||||
|
} // namespace Core::HID
|
||||
@ -0,0 +1,142 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
#include <mutex> |
||||
|
#include <unordered_map> |
||||
|
|
||||
|
#include "common/input.h" |
||||
|
#include "common/param_package.h" |
||||
|
#include "common/point.h" |
||||
|
#include "common/quaternion.h" |
||||
|
#include "common/settings.h" |
||||
|
#include "common/vector_math.h" |
||||
|
#include "core/hid/hid_types.h" |
||||
|
#include "core/hid/motion_input.h" |
||||
|
|
||||
|
namespace Core::HID { |
||||
|
|
||||
|
struct ConsoleMotionInfo { |
||||
|
Input::MotionStatus raw_status; |
||||
|
MotionInput emulated{}; |
||||
|
}; |
||||
|
|
||||
|
using ConsoleMotionDevices = std::unique_ptr<Input::InputDevice>; |
||||
|
using TouchDevices = std::array<std::unique_ptr<Input::InputDevice>, 16>; |
||||
|
|
||||
|
using ConsoleMotionParams = Common::ParamPackage; |
||||
|
using TouchParams = std::array<Common::ParamPackage, 16>; |
||||
|
|
||||
|
using ConsoleMotionValues = ConsoleMotionInfo; |
||||
|
using TouchValues = std::array<Input::TouchStatus, 16>; |
||||
|
|
||||
|
struct TouchFinger { |
||||
|
u64_le last_touch{}; |
||||
|
Common::Point<float> position{}; |
||||
|
u32_le id{}; |
||||
|
bool pressed{}; |
||||
|
Core::HID::TouchAttribute attribute{}; |
||||
|
}; |
||||
|
|
||||
|
struct ConsoleMotion { |
||||
|
bool is_at_rest{}; |
||||
|
Common::Vec3f accel{}; |
||||
|
Common::Vec3f gyro{}; |
||||
|
Common::Vec3f rotation{}; |
||||
|
std::array<Common::Vec3f, 3> orientation{}; |
||||
|
Common::Quaternion<f32> quaternion{}; |
||||
|
}; |
||||
|
|
||||
|
using TouchFingerState = std::array<TouchFinger, 16>; |
||||
|
|
||||
|
struct ConsoleStatus { |
||||
|
// Data from input_common |
||||
|
ConsoleMotionValues motion_values{}; |
||||
|
TouchValues touch_values{}; |
||||
|
|
||||
|
// Data for Nintendo devices; |
||||
|
ConsoleMotion motion_state{}; |
||||
|
TouchFingerState touch_state{}; |
||||
|
}; |
||||
|
|
||||
|
enum class ConsoleTriggerType { |
||||
|
Motion, |
||||
|
Touch, |
||||
|
All, |
||||
|
}; |
||||
|
|
||||
|
struct ConsoleUpdateCallback { |
||||
|
std::function<void(ConsoleTriggerType)> on_change; |
||||
|
}; |
||||
|
|
||||
|
class EmulatedConsole { |
||||
|
public: |
||||
|
/** |
||||
|
* TODO: Write description |
||||
|
* |
||||
|
* @param npad_id_type |
||||
|
*/ |
||||
|
explicit EmulatedConsole(); |
||||
|
~EmulatedConsole(); |
||||
|
|
||||
|
YUZU_NON_COPYABLE(EmulatedConsole); |
||||
|
YUZU_NON_MOVEABLE(EmulatedConsole); |
||||
|
|
||||
|
void ReloadFromSettings(); |
||||
|
void ReloadInput(); |
||||
|
void UnloadInput(); |
||||
|
|
||||
|
void EnableConfiguration(); |
||||
|
void DisableConfiguration(); |
||||
|
bool IsConfiguring() const; |
||||
|
void SaveCurrentConfig(); |
||||
|
void RestoreConfig(); |
||||
|
|
||||
|
Common::ParamPackage GetMotionParam() const; |
||||
|
|
||||
|
void SetMotionParam(Common::ParamPackage param); |
||||
|
|
||||
|
ConsoleMotionValues GetMotionValues() const; |
||||
|
TouchValues GetTouchValues() const; |
||||
|
|
||||
|
ConsoleMotion GetMotion() const; |
||||
|
TouchFingerState GetTouch() const; |
||||
|
|
||||
|
int SetCallback(ConsoleUpdateCallback update_callback); |
||||
|
void DeleteCallback(int key); |
||||
|
|
||||
|
private: |
||||
|
/** |
||||
|
* Sets the status of a button. Applies toggle properties to the output. |
||||
|
* |
||||
|
* @param A CallbackStatus and a button index number |
||||
|
*/ |
||||
|
void SetMotion(Input::CallbackStatus callback); |
||||
|
void SetTouch(Input::CallbackStatus callback, std::size_t index); |
||||
|
|
||||
|
/** |
||||
|
* Triggers a callback that something has changed |
||||
|
* |
||||
|
* @param Input type of the trigger |
||||
|
*/ |
||||
|
void TriggerOnChange(ConsoleTriggerType type); |
||||
|
|
||||
|
bool is_configuring{false}; |
||||
|
f32 motion_sensitivity{0.01f}; |
||||
|
|
||||
|
ConsoleMotionParams motion_params; |
||||
|
TouchParams touch_params; |
||||
|
|
||||
|
ConsoleMotionDevices motion_devices; |
||||
|
TouchDevices touch_devices; |
||||
|
|
||||
|
mutable std::mutex mutex; |
||||
|
std::unordered_map<int, ConsoleUpdateCallback> callback_list; |
||||
|
int last_callback_key = 0; |
||||
|
ConsoleStatus console; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Core::HID |
||||
@ -0,0 +1,745 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included
|
||||
|
|
||||
|
#include <fmt/format.h>
|
||||
|
|
||||
|
#include "core/hid/emulated_controller.h"
|
||||
|
#include "core/hid/input_converter.h"
|
||||
|
|
||||
|
namespace Core::HID { |
||||
|
constexpr s32 HID_JOYSTICK_MAX = 0x7fff; |
||||
|
constexpr s32 HID_TRIGGER_MAX = 0x7fff; |
||||
|
|
||||
|
EmulatedController::EmulatedController(NpadIdType npad_id_type_) : npad_id_type(npad_id_type_) {} |
||||
|
|
||||
|
EmulatedController::~EmulatedController() = default; |
||||
|
|
||||
|
NpadType EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) { |
||||
|
switch (type) { |
||||
|
case Settings::ControllerType::ProController: |
||||
|
return NpadType::ProController; |
||||
|
case Settings::ControllerType::DualJoyconDetached: |
||||
|
return NpadType::JoyconDual; |
||||
|
case Settings::ControllerType::LeftJoycon: |
||||
|
return NpadType::JoyconLeft; |
||||
|
case Settings::ControllerType::RightJoycon: |
||||
|
return NpadType::JoyconRight; |
||||
|
case Settings::ControllerType::Handheld: |
||||
|
return NpadType::Handheld; |
||||
|
case Settings::ControllerType::GameCube: |
||||
|
return NpadType::GameCube; |
||||
|
default: |
||||
|
return NpadType::ProController; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadType type) { |
||||
|
switch (type) { |
||||
|
case NpadType::ProController: |
||||
|
return Settings::ControllerType::ProController; |
||||
|
case NpadType::JoyconDual: |
||||
|
return Settings::ControllerType::DualJoyconDetached; |
||||
|
case NpadType::JoyconLeft: |
||||
|
return Settings::ControllerType::LeftJoycon; |
||||
|
case NpadType::JoyconRight: |
||||
|
return Settings::ControllerType::RightJoycon; |
||||
|
case NpadType::Handheld: |
||||
|
return Settings::ControllerType::Handheld; |
||||
|
case NpadType::GameCube: |
||||
|
return Settings::ControllerType::GameCube; |
||||
|
default: |
||||
|
return Settings::ControllerType::ProController; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::ReloadFromSettings() { |
||||
|
const auto player_index = NpadIdTypeToIndex(npad_id_type); |
||||
|
const auto& player = Settings::values.players.GetValue()[player_index]; |
||||
|
|
||||
|
for (std::size_t index = 0; index < player.buttons.size(); ++index) { |
||||
|
button_params[index] = Common::ParamPackage(player.buttons[index]); |
||||
|
} |
||||
|
for (std::size_t index = 0; index < player.analogs.size(); ++index) { |
||||
|
stick_params[index] = Common::ParamPackage(player.analogs[index]); |
||||
|
} |
||||
|
for (std::size_t index = 0; index < player.motions.size(); ++index) { |
||||
|
motion_params[index] = Common::ParamPackage(player.motions[index]); |
||||
|
} |
||||
|
ReloadInput(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::ReloadInput() { |
||||
|
const auto player_index = NpadIdTypeToIndex(npad_id_type); |
||||
|
const auto& player = Settings::values.players.GetValue()[player_index]; |
||||
|
const auto left_side = button_params[Settings::NativeButton::ZL]; |
||||
|
const auto right_side = button_params[Settings::NativeButton::ZR]; |
||||
|
|
||||
|
std::transform(button_params.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, |
||||
|
button_params.begin() + Settings::NativeButton::BUTTON_NS_END, |
||||
|
button_devices.begin(), Input::CreateDevice<Input::InputDevice>); |
||||
|
std::transform(stick_params.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, |
||||
|
stick_params.begin() + Settings::NativeAnalog::STICK_HID_END, |
||||
|
stick_devices.begin(), Input::CreateDevice<Input::InputDevice>); |
||||
|
std::transform(motion_params.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, |
||||
|
motion_params.begin() + Settings::NativeMotion::MOTION_HID_END, |
||||
|
motion_devices.begin(), Input::CreateDevice<Input::InputDevice>); |
||||
|
|
||||
|
trigger_devices[0] = |
||||
|
Input::CreateDevice<Input::InputDevice>(button_params[Settings::NativeButton::ZL]); |
||||
|
trigger_devices[1] = |
||||
|
Input::CreateDevice<Input::InputDevice>(button_params[Settings::NativeButton::ZR]); |
||||
|
|
||||
|
controller.colors_state.left = { |
||||
|
.body = player.body_color_left, |
||||
|
.button = player.button_color_left, |
||||
|
}; |
||||
|
|
||||
|
controller.colors_state.right = { |
||||
|
.body = player.body_color_right, |
||||
|
.button = player.button_color_right, |
||||
|
}; |
||||
|
|
||||
|
controller.colors_state.fullkey = controller.colors_state.left; |
||||
|
|
||||
|
battery_devices[0] = Input::CreateDevice<Input::InputDevice>(left_side); |
||||
|
battery_devices[1] = Input::CreateDevice<Input::InputDevice>(right_side); |
||||
|
|
||||
|
for (std::size_t index = 0; index < button_devices.size(); ++index) { |
||||
|
if (!button_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
Input::InputCallback button_callback{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetButton(callback, index); }}; |
||||
|
button_devices[index]->SetCallback(button_callback); |
||||
|
} |
||||
|
|
||||
|
for (std::size_t index = 0; index < stick_devices.size(); ++index) { |
||||
|
if (!stick_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
Input::InputCallback stick_callback{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetStick(callback, index); }}; |
||||
|
stick_devices[index]->SetCallback(stick_callback); |
||||
|
} |
||||
|
|
||||
|
for (std::size_t index = 0; index < trigger_devices.size(); ++index) { |
||||
|
if (!trigger_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
Input::InputCallback trigger_callback{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetTrigger(callback, index); }}; |
||||
|
trigger_devices[index]->SetCallback(trigger_callback); |
||||
|
} |
||||
|
|
||||
|
for (std::size_t index = 0; index < battery_devices.size(); ++index) { |
||||
|
if (!battery_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
Input::InputCallback battery_callback{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetBattery(callback, index); }}; |
||||
|
battery_devices[index]->SetCallback(battery_callback); |
||||
|
} |
||||
|
|
||||
|
for (std::size_t index = 0; index < motion_devices.size(); ++index) { |
||||
|
if (!motion_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
Input::InputCallback motion_callback{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetMotion(callback, index); }}; |
||||
|
motion_devices[index]->SetCallback(motion_callback); |
||||
|
} |
||||
|
|
||||
|
SetNpadType(MapSettingsTypeToNPad(player.controller_type)); |
||||
|
|
||||
|
if (player.connected) { |
||||
|
Connect(); |
||||
|
} else { |
||||
|
Disconnect(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::UnloadInput() { |
||||
|
for (auto& button : button_devices) { |
||||
|
button.reset(); |
||||
|
} |
||||
|
for (auto& stick : stick_devices) { |
||||
|
stick.reset(); |
||||
|
} |
||||
|
for (auto& motion : motion_devices) { |
||||
|
motion.reset(); |
||||
|
} |
||||
|
for (auto& trigger : trigger_devices) { |
||||
|
trigger.reset(); |
||||
|
} |
||||
|
for (auto& battery : battery_devices) { |
||||
|
battery.reset(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::EnableConfiguration() { |
||||
|
is_configuring = true; |
||||
|
SaveCurrentConfig(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::DisableConfiguration() { |
||||
|
is_configuring = false; |
||||
|
} |
||||
|
|
||||
|
bool EmulatedController::IsConfiguring() const { |
||||
|
return is_configuring; |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SaveCurrentConfig() { |
||||
|
if (!is_configuring) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const auto player_index = NpadIdTypeToIndex(npad_id_type); |
||||
|
auto& player = Settings::values.players.GetValue()[player_index]; |
||||
|
|
||||
|
for (std::size_t index = 0; index < player.buttons.size(); ++index) { |
||||
|
player.buttons[index] = button_params[index].Serialize(); |
||||
|
} |
||||
|
for (std::size_t index = 0; index < player.analogs.size(); ++index) { |
||||
|
player.analogs[index] = stick_params[index].Serialize(); |
||||
|
} |
||||
|
for (std::size_t index = 0; index < player.motions.size(); ++index) { |
||||
|
player.motions[index] = motion_params[index].Serialize(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::RestoreConfig() { |
||||
|
if (!is_configuring) { |
||||
|
return; |
||||
|
} |
||||
|
ReloadFromSettings(); |
||||
|
} |
||||
|
|
||||
|
std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices() const { |
||||
|
std::vector<Common::ParamPackage> devices; |
||||
|
for (const auto& param : button_params) { |
||||
|
if (!param.Has("engine")) { |
||||
|
continue; |
||||
|
} |
||||
|
const auto devices_it = std::find_if( |
||||
|
devices.begin(), devices.end(), [param](const Common::ParamPackage param_) { |
||||
|
return param.Get("engine", "") == param_.Get("engine", "") && |
||||
|
param.Get("guid", "") == param_.Get("guid", "") && |
||||
|
param.Get("port", "") == param_.Get("port", ""); |
||||
|
}); |
||||
|
if (devices_it != devices.end()) { |
||||
|
continue; |
||||
|
} |
||||
|
Common::ParamPackage device{}; |
||||
|
device.Set("engine", param.Get("engine", "")); |
||||
|
device.Set("guid", param.Get("guid", "")); |
||||
|
device.Set("port", param.Get("port", "")); |
||||
|
devices.push_back(device); |
||||
|
} |
||||
|
|
||||
|
for (const auto& param : stick_params) { |
||||
|
if (!param.Has("engine")) { |
||||
|
continue; |
||||
|
} |
||||
|
if (param.Get("engine", "") == "analog_from_button") { |
||||
|
continue; |
||||
|
} |
||||
|
const auto devices_it = std::find_if( |
||||
|
devices.begin(), devices.end(), [param](const Common::ParamPackage param_) { |
||||
|
return param.Get("engine", "") == param_.Get("engine", "") && |
||||
|
param.Get("guid", "") == param_.Get("guid", "") && |
||||
|
param.Get("port", "") == param_.Get("port", ""); |
||||
|
}); |
||||
|
if (devices_it != devices.end()) { |
||||
|
continue; |
||||
|
} |
||||
|
Common::ParamPackage device{}; |
||||
|
device.Set("engine", param.Get("engine", "")); |
||||
|
device.Set("guid", param.Get("guid", "")); |
||||
|
device.Set("port", param.Get("port", "")); |
||||
|
devices.push_back(device); |
||||
|
} |
||||
|
return devices; |
||||
|
} |
||||
|
|
||||
|
Common::ParamPackage EmulatedController::GetButtonParam(std::size_t index) const { |
||||
|
if (index >= button_params.size()) { |
||||
|
return {}; |
||||
|
} |
||||
|
return button_params[index]; |
||||
|
} |
||||
|
|
||||
|
Common::ParamPackage EmulatedController::GetStickParam(std::size_t index) const { |
||||
|
if (index >= stick_params.size()) { |
||||
|
return {}; |
||||
|
} |
||||
|
return stick_params[index]; |
||||
|
} |
||||
|
|
||||
|
Common::ParamPackage EmulatedController::GetMotionParam(std::size_t index) const { |
||||
|
if (index >= motion_params.size()) { |
||||
|
return {}; |
||||
|
} |
||||
|
return motion_params[index]; |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetButtonParam(std::size_t index, Common::ParamPackage param) { |
||||
|
if (index >= button_params.size()) { |
||||
|
return; |
||||
|
} |
||||
|
button_params[index] = param; |
||||
|
ReloadInput(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetStickParam(std::size_t index, Common::ParamPackage param) { |
||||
|
if (index >= stick_params.size()) { |
||||
|
return; |
||||
|
} |
||||
|
stick_params[index] = param; |
||||
|
ReloadInput(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetMotionParam(std::size_t index, Common::ParamPackage param) { |
||||
|
if (index >= motion_params.size()) { |
||||
|
return; |
||||
|
} |
||||
|
motion_params[index] = param; |
||||
|
ReloadInput(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetButton(Input::CallbackStatus callback, std::size_t index) { |
||||
|
if (index >= controller.button_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
bool value_changed = false; |
||||
|
const auto new_status = TransformToButton(callback); |
||||
|
auto& current_status = controller.button_values[index]; |
||||
|
current_status.toggle = new_status.toggle; |
||||
|
|
||||
|
// Update button status with current
|
||||
|
if (!current_status.toggle) { |
||||
|
current_status.locked = false; |
||||
|
if (current_status.value != new_status.value) { |
||||
|
current_status.value = new_status.value; |
||||
|
value_changed = true; |
||||
|
} |
||||
|
} else { |
||||
|
// Toggle button and lock status
|
||||
|
if (new_status.value && !current_status.locked) { |
||||
|
current_status.locked = true; |
||||
|
current_status.value = !current_status.value; |
||||
|
value_changed = true; |
||||
|
} |
||||
|
|
||||
|
// Unlock button ready for next press
|
||||
|
if (!new_status.value && current_status.locked) { |
||||
|
current_status.locked = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!value_changed) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
controller.npad_button_state.raw = NpadButton::None; |
||||
|
controller.debug_pad_button_state.raw = 0; |
||||
|
TriggerOnChange(ControllerTriggerType::Button); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
switch (index) { |
||||
|
case Settings::NativeButton::A: |
||||
|
controller.npad_button_state.a.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.a.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::B: |
||||
|
controller.npad_button_state.b.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.b.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::X: |
||||
|
controller.npad_button_state.x.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.x.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::Y: |
||||
|
controller.npad_button_state.y.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.y.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::LStick: |
||||
|
controller.npad_button_state.stick_l.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::RStick: |
||||
|
controller.npad_button_state.stick_r.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::L: |
||||
|
controller.npad_button_state.l.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.l.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::R: |
||||
|
controller.npad_button_state.r.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.r.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::ZL: |
||||
|
controller.npad_button_state.zl.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.zl.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::ZR: |
||||
|
controller.npad_button_state.zr.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.zr.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::Plus: |
||||
|
controller.npad_button_state.plus.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.plus.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::Minus: |
||||
|
controller.npad_button_state.minus.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.minus.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::DLeft: |
||||
|
controller.npad_button_state.left.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.d_left.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::DUp: |
||||
|
controller.npad_button_state.up.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.d_up.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::DRight: |
||||
|
controller.npad_button_state.right.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.d_right.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::DDown: |
||||
|
controller.npad_button_state.down.Assign(current_status.value); |
||||
|
controller.debug_pad_button_state.d_down.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::SL: |
||||
|
controller.npad_button_state.left_sl.Assign(current_status.value); |
||||
|
controller.npad_button_state.right_sl.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::SR: |
||||
|
controller.npad_button_state.left_sr.Assign(current_status.value); |
||||
|
controller.npad_button_state.right_sr.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeButton::Home: |
||||
|
case Settings::NativeButton::Screenshot: |
||||
|
break; |
||||
|
} |
||||
|
TriggerOnChange(ControllerTriggerType::Button); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetStick(Input::CallbackStatus callback, std::size_t index) { |
||||
|
if (index >= controller.stick_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
controller.stick_values[index] = TransformToStick(callback); |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
controller.analog_stick_state.left = {}; |
||||
|
controller.analog_stick_state.right = {}; |
||||
|
TriggerOnChange(ControllerTriggerType::Stick); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const AnalogStickState stick{ |
||||
|
.x = static_cast<s32>(controller.stick_values[index].x.value * HID_JOYSTICK_MAX), |
||||
|
.y = static_cast<s32>(controller.stick_values[index].y.value * HID_JOYSTICK_MAX), |
||||
|
}; |
||||
|
|
||||
|
switch (index) { |
||||
|
case Settings::NativeAnalog::LStick: |
||||
|
controller.analog_stick_state.left = stick; |
||||
|
controller.npad_button_state.stick_l_left.Assign(controller.stick_values[index].left); |
||||
|
controller.npad_button_state.stick_l_up.Assign(controller.stick_values[index].up); |
||||
|
controller.npad_button_state.stick_l_right.Assign(controller.stick_values[index].right); |
||||
|
controller.npad_button_state.stick_l_down.Assign(controller.stick_values[index].down); |
||||
|
break; |
||||
|
case Settings::NativeAnalog::RStick: |
||||
|
controller.analog_stick_state.right = stick; |
||||
|
controller.npad_button_state.stick_r_left.Assign(controller.stick_values[index].left); |
||||
|
controller.npad_button_state.stick_r_up.Assign(controller.stick_values[index].up); |
||||
|
controller.npad_button_state.stick_r_right.Assign(controller.stick_values[index].right); |
||||
|
controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
TriggerOnChange(ControllerTriggerType::Stick); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetTrigger(Input::CallbackStatus callback, std::size_t index) { |
||||
|
if (index >= controller.trigger_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
controller.trigger_values[index] = TransformToTrigger(callback); |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
controller.gc_trigger_state.left = 0; |
||||
|
controller.gc_trigger_state.right = 0; |
||||
|
TriggerOnChange(ControllerTriggerType::Trigger); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const auto trigger = controller.trigger_values[index]; |
||||
|
|
||||
|
switch (index) { |
||||
|
case Settings::NativeTrigger::LTrigger: |
||||
|
controller.gc_trigger_state.left = static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX); |
||||
|
controller.npad_button_state.zl.Assign(trigger.pressed); |
||||
|
break; |
||||
|
case Settings::NativeTrigger::RTrigger: |
||||
|
controller.gc_trigger_state.right = |
||||
|
static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX); |
||||
|
controller.npad_button_state.zr.Assign(trigger.pressed); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
TriggerOnChange(ControllerTriggerType::Trigger); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetMotion(Input::CallbackStatus callback, std::size_t index) { |
||||
|
if (index >= controller.motion_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
auto& raw_status = controller.motion_values[index].raw_status; |
||||
|
auto& emulated = controller.motion_values[index].emulated; |
||||
|
|
||||
|
raw_status = TransformToMotion(callback); |
||||
|
emulated.SetAcceleration(Common::Vec3f{ |
||||
|
raw_status.accel.x.value, |
||||
|
raw_status.accel.y.value, |
||||
|
raw_status.accel.z.value, |
||||
|
}); |
||||
|
emulated.SetGyroscope(Common::Vec3f{ |
||||
|
raw_status.gyro.x.value, |
||||
|
raw_status.gyro.y.value, |
||||
|
raw_status.gyro.z.value, |
||||
|
}); |
||||
|
emulated.UpdateRotation(raw_status.delta_timestamp); |
||||
|
emulated.UpdateOrientation(raw_status.delta_timestamp); |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
TriggerOnChange(ControllerTriggerType::Motion); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
auto& motion = controller.motion_state[index]; |
||||
|
motion.accel = emulated.GetAcceleration(); |
||||
|
motion.gyro = emulated.GetGyroscope(); |
||||
|
motion.rotation = emulated.GetGyroscope(); |
||||
|
motion.orientation = emulated.GetOrientation(); |
||||
|
motion.is_at_rest = emulated.IsMoving(motion_sensitivity); |
||||
|
|
||||
|
TriggerOnChange(ControllerTriggerType::Motion); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetBattery(Input::CallbackStatus callback, std::size_t index) { |
||||
|
if (index >= controller.battery_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
controller.battery_values[index] = TransformToBattery(callback); |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
TriggerOnChange(ControllerTriggerType::Battery); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
bool is_charging = false; |
||||
|
bool is_powered = false; |
||||
|
BatteryLevel battery_level = 0; |
||||
|
switch (controller.battery_values[index]) { |
||||
|
case Input::BatteryLevel::Charging: |
||||
|
is_charging = true; |
||||
|
is_powered = true; |
||||
|
battery_level = 6; |
||||
|
break; |
||||
|
case Input::BatteryLevel::Medium: |
||||
|
battery_level = 6; |
||||
|
break; |
||||
|
case Input::BatteryLevel::Low: |
||||
|
battery_level = 4; |
||||
|
break; |
||||
|
case Input::BatteryLevel::Critical: |
||||
|
battery_level = 2; |
||||
|
break; |
||||
|
case Input::BatteryLevel::Empty: |
||||
|
battery_level = 0; |
||||
|
break; |
||||
|
case Input::BatteryLevel::Full: |
||||
|
default: |
||||
|
is_powered = true; |
||||
|
battery_level = 8; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
switch (index) { |
||||
|
case 0: |
||||
|
controller.battery_state.left = { |
||||
|
.is_powered = is_powered, |
||||
|
.is_charging = is_charging, |
||||
|
.battery_level = battery_level, |
||||
|
}; |
||||
|
break; |
||||
|
case 1: |
||||
|
controller.battery_state.right = { |
||||
|
.is_powered = is_powered, |
||||
|
.is_charging = is_charging, |
||||
|
.battery_level = battery_level, |
||||
|
}; |
||||
|
break; |
||||
|
case 2: |
||||
|
controller.battery_state.dual = { |
||||
|
.is_powered = is_powered, |
||||
|
.is_charging = is_charging, |
||||
|
.battery_level = battery_level, |
||||
|
}; |
||||
|
break; |
||||
|
} |
||||
|
TriggerOnChange(ControllerTriggerType::Battery); |
||||
|
} |
||||
|
|
||||
|
bool EmulatedController::SetVibration([[maybe_unused]] std::size_t device_index, |
||||
|
[[maybe_unused]] VibrationValue vibration) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
int EmulatedController::TestVibration(std::size_t device_index) { |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::Connect() { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
if (is_connected) { |
||||
|
LOG_WARNING(Service_HID, "Tried to turn on a connected controller {}", npad_id_type); |
||||
|
return; |
||||
|
} |
||||
|
is_connected = true; |
||||
|
TriggerOnChange(ControllerTriggerType::Connected); |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::Disconnect() { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
if (!is_connected) { |
||||
|
LOG_WARNING(Service_HID, "Tried to turn off a disconnected controller {}", npad_id_type); |
||||
|
return; |
||||
|
} |
||||
|
is_connected = false; |
||||
|
TriggerOnChange(ControllerTriggerType::Disconnected); |
||||
|
} |
||||
|
|
||||
|
bool EmulatedController::IsConnected() const { |
||||
|
return is_connected; |
||||
|
} |
||||
|
|
||||
|
bool EmulatedController::IsVibrationEnabled() const { |
||||
|
return is_vibration_enabled; |
||||
|
} |
||||
|
|
||||
|
NpadIdType EmulatedController::GetNpadIdType() const { |
||||
|
return npad_id_type; |
||||
|
} |
||||
|
|
||||
|
NpadType EmulatedController::GetNpadType() const { |
||||
|
return npad_type; |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::SetNpadType(NpadType npad_type_) { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
if (npad_type == npad_type_) { |
||||
|
return; |
||||
|
} |
||||
|
npad_type = npad_type_; |
||||
|
TriggerOnChange(ControllerTriggerType::Type); |
||||
|
} |
||||
|
|
||||
|
ButtonValues EmulatedController::GetButtonsValues() const { |
||||
|
return controller.button_values; |
||||
|
} |
||||
|
|
||||
|
SticksValues EmulatedController::GetSticksValues() const { |
||||
|
return controller.stick_values; |
||||
|
} |
||||
|
|
||||
|
TriggerValues EmulatedController::GetTriggersValues() const { |
||||
|
return controller.trigger_values; |
||||
|
} |
||||
|
|
||||
|
ControllerMotionValues EmulatedController::GetMotionValues() const { |
||||
|
return controller.motion_values; |
||||
|
} |
||||
|
|
||||
|
ColorValues EmulatedController::GetColorsValues() const { |
||||
|
return controller.color_values; |
||||
|
} |
||||
|
|
||||
|
BatteryValues EmulatedController::GetBatteryValues() const { |
||||
|
return controller.battery_values; |
||||
|
} |
||||
|
|
||||
|
NpadButtonState EmulatedController::GetNpadButtons() const { |
||||
|
if (is_configuring) { |
||||
|
return {}; |
||||
|
} |
||||
|
return controller.npad_button_state; |
||||
|
} |
||||
|
|
||||
|
DebugPadButton EmulatedController::GetDebugPadButtons() const { |
||||
|
if (is_configuring) { |
||||
|
return {}; |
||||
|
} |
||||
|
return controller.debug_pad_button_state; |
||||
|
} |
||||
|
|
||||
|
AnalogSticks EmulatedController::GetSticks() const { |
||||
|
if (is_configuring) { |
||||
|
return {}; |
||||
|
} |
||||
|
return controller.analog_stick_state; |
||||
|
} |
||||
|
|
||||
|
NpadGcTriggerState EmulatedController::GetTriggers() const { |
||||
|
if (is_configuring) { |
||||
|
return {}; |
||||
|
} |
||||
|
return controller.gc_trigger_state; |
||||
|
} |
||||
|
|
||||
|
MotionState EmulatedController::GetMotions() const { |
||||
|
return controller.motion_state; |
||||
|
} |
||||
|
|
||||
|
ControllerColors EmulatedController::GetColors() const { |
||||
|
return controller.colors_state; |
||||
|
} |
||||
|
|
||||
|
BatteryLevelState EmulatedController::GetBattery() const { |
||||
|
return controller.battery_state; |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::TriggerOnChange(ControllerTriggerType type) { |
||||
|
for (const std::pair<int, ControllerUpdateCallback> poller_pair : callback_list) { |
||||
|
const ControllerUpdateCallback& poller = poller_pair.second; |
||||
|
if (poller.on_change) { |
||||
|
poller.on_change(type); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int EmulatedController::SetCallback(ControllerUpdateCallback update_callback) { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
callback_list.insert_or_assign(last_callback_key, update_callback); |
||||
|
return last_callback_key++; |
||||
|
} |
||||
|
|
||||
|
void EmulatedController::DeleteCallback(int key) { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
if (!callback_list.contains(key)) { |
||||
|
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); |
||||
|
return; |
||||
|
} |
||||
|
callback_list.erase(key); |
||||
|
} |
||||
|
} // namespace Core::HID
|
||||
@ -0,0 +1,232 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
#include <mutex> |
||||
|
#include <unordered_map> |
||||
|
|
||||
|
#include "common/input.h" |
||||
|
#include "common/param_package.h" |
||||
|
#include "common/point.h" |
||||
|
#include "common/quaternion.h" |
||||
|
#include "common/settings.h" |
||||
|
#include "common/vector_math.h" |
||||
|
#include "core/hid/hid_types.h" |
||||
|
#include "core/hid/motion_input.h" |
||||
|
|
||||
|
namespace Core::HID { |
||||
|
|
||||
|
struct ControllerMotionInfo { |
||||
|
Input::MotionStatus raw_status; |
||||
|
MotionInput emulated{}; |
||||
|
}; |
||||
|
|
||||
|
using ButtonDevices = |
||||
|
std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeButton::NumButtons>; |
||||
|
using StickDevices = |
||||
|
std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>; |
||||
|
using ControllerMotionDevices = |
||||
|
std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeMotion::NumMotions>; |
||||
|
using TriggerDevices = |
||||
|
std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeTrigger::NumTriggers>; |
||||
|
using BatteryDevices = std::array<std::unique_ptr<Input::InputDevice>, 2>; |
||||
|
|
||||
|
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; |
||||
|
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; |
||||
|
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>; |
||||
|
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; |
||||
|
using BatteryParams = std::array<Common::ParamPackage, 2>; |
||||
|
|
||||
|
using ButtonValues = std::array<Input::ButtonStatus, Settings::NativeButton::NumButtons>; |
||||
|
using SticksValues = std::array<Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; |
||||
|
using TriggerValues = std::array<Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>; |
||||
|
using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>; |
||||
|
using ColorValues = std::array<Input::BodyColorStatus, 3>; |
||||
|
using BatteryValues = std::array<Input::BatteryStatus, 3>; |
||||
|
using VibrationValues = std::array<Input::VibrationStatus, 2>; |
||||
|
|
||||
|
struct AnalogSticks { |
||||
|
AnalogStickState left; |
||||
|
AnalogStickState right; |
||||
|
}; |
||||
|
|
||||
|
struct ControllerColors { |
||||
|
NpadControllerColor fullkey; |
||||
|
NpadControllerColor left; |
||||
|
NpadControllerColor right; |
||||
|
}; |
||||
|
|
||||
|
struct BatteryLevelState { |
||||
|
NpadPowerInfo dual; |
||||
|
NpadPowerInfo left; |
||||
|
NpadPowerInfo right; |
||||
|
}; |
||||
|
|
||||
|
struct ControllerMotion { |
||||
|
bool is_at_rest; |
||||
|
Common::Vec3f accel{}; |
||||
|
Common::Vec3f gyro{}; |
||||
|
Common::Vec3f rotation{}; |
||||
|
std::array<Common::Vec3f, 3> orientation{}; |
||||
|
}; |
||||
|
|
||||
|
using MotionState = std::array<ControllerMotion, 2>; |
||||
|
|
||||
|
struct ControllerStatus { |
||||
|
// Data from input_common |
||||
|
ButtonValues button_values{}; |
||||
|
SticksValues stick_values{}; |
||||
|
ControllerMotionValues motion_values{}; |
||||
|
TriggerValues trigger_values{}; |
||||
|
ColorValues color_values{}; |
||||
|
BatteryValues battery_values{}; |
||||
|
VibrationValues vibration_values{}; |
||||
|
|
||||
|
// Data for Nintendo devices |
||||
|
NpadButtonState npad_button_state{}; |
||||
|
DebugPadButton debug_pad_button_state{}; |
||||
|
AnalogSticks analog_stick_state{}; |
||||
|
MotionState motion_state{}; |
||||
|
NpadGcTriggerState gc_trigger_state{}; |
||||
|
ControllerColors colors_state{}; |
||||
|
BatteryLevelState battery_state{}; |
||||
|
}; |
||||
|
enum class ControllerTriggerType { |
||||
|
Button, |
||||
|
Stick, |
||||
|
Trigger, |
||||
|
Motion, |
||||
|
Color, |
||||
|
Battery, |
||||
|
Vibration, |
||||
|
Connected, |
||||
|
Disconnected, |
||||
|
Type, |
||||
|
All, |
||||
|
}; |
||||
|
|
||||
|
struct ControllerUpdateCallback { |
||||
|
std::function<void(ControllerTriggerType)> on_change; |
||||
|
}; |
||||
|
|
||||
|
class EmulatedController { |
||||
|
public: |
||||
|
/** |
||||
|
* TODO: Write description |
||||
|
* |
||||
|
* @param npad_id_type |
||||
|
*/ |
||||
|
explicit EmulatedController(NpadIdType npad_id_type_); |
||||
|
~EmulatedController(); |
||||
|
|
||||
|
YUZU_NON_COPYABLE(EmulatedController); |
||||
|
YUZU_NON_MOVEABLE(EmulatedController); |
||||
|
|
||||
|
static NpadType MapSettingsTypeToNPad(Settings::ControllerType type); |
||||
|
static Settings::ControllerType MapNPadToSettingsType(NpadType type); |
||||
|
|
||||
|
/// Gets the NpadIdType for this controller. |
||||
|
NpadIdType GetNpadIdType() const; |
||||
|
|
||||
|
/// Sets the NpadType for this controller. |
||||
|
void SetNpadType(NpadType npad_type_); |
||||
|
|
||||
|
/// Gets the NpadType for this controller. |
||||
|
NpadType GetNpadType() const; |
||||
|
|
||||
|
void Connect(); |
||||
|
void Disconnect(); |
||||
|
|
||||
|
bool IsConnected() const; |
||||
|
bool IsVibrationEnabled() const; |
||||
|
|
||||
|
void ReloadFromSettings(); |
||||
|
void ReloadInput(); |
||||
|
void UnloadInput(); |
||||
|
|
||||
|
void EnableConfiguration(); |
||||
|
void DisableConfiguration(); |
||||
|
bool IsConfiguring() const; |
||||
|
void SaveCurrentConfig(); |
||||
|
void RestoreConfig(); |
||||
|
|
||||
|
std::vector<Common::ParamPackage> GetMappedDevices() const; |
||||
|
|
||||
|
Common::ParamPackage GetButtonParam(std::size_t index) const; |
||||
|
Common::ParamPackage GetStickParam(std::size_t index) const; |
||||
|
Common::ParamPackage GetMotionParam(std::size_t index) const; |
||||
|
|
||||
|
void SetButtonParam(std::size_t index, Common::ParamPackage param); |
||||
|
void SetStickParam(std::size_t index, Common::ParamPackage param); |
||||
|
void SetMotionParam(std::size_t index, Common::ParamPackage param); |
||||
|
|
||||
|
ButtonValues GetButtonsValues() const; |
||||
|
SticksValues GetSticksValues() const; |
||||
|
TriggerValues GetTriggersValues() const; |
||||
|
ControllerMotionValues GetMotionValues() const; |
||||
|
ColorValues GetColorsValues() const; |
||||
|
BatteryValues GetBatteryValues() const; |
||||
|
|
||||
|
NpadButtonState GetNpadButtons() const; |
||||
|
DebugPadButton GetDebugPadButtons() const; |
||||
|
AnalogSticks GetSticks() const; |
||||
|
NpadGcTriggerState GetTriggers() const; |
||||
|
MotionState GetMotions() const; |
||||
|
ControllerColors GetColors() const; |
||||
|
BatteryLevelState GetBattery() const; |
||||
|
|
||||
|
bool SetVibration(std::size_t device_index, VibrationValue vibration); |
||||
|
int TestVibration(std::size_t device_index); |
||||
|
|
||||
|
int SetCallback(ControllerUpdateCallback update_callback); |
||||
|
void DeleteCallback(int key); |
||||
|
|
||||
|
private: |
||||
|
/** |
||||
|
* Sets the status of a button. Applies toggle properties to the output. |
||||
|
* |
||||
|
* @param A CallbackStatus and a button index number |
||||
|
*/ |
||||
|
void SetButton(Input::CallbackStatus callback, std::size_t index); |
||||
|
void SetStick(Input::CallbackStatus callback, std::size_t index); |
||||
|
void SetTrigger(Input::CallbackStatus callback, std::size_t index); |
||||
|
void SetMotion(Input::CallbackStatus callback, std::size_t index); |
||||
|
void SetBattery(Input::CallbackStatus callback, std::size_t index); |
||||
|
|
||||
|
/** |
||||
|
* Triggers a callback that something has changed |
||||
|
* |
||||
|
* @param Input type of the trigger |
||||
|
*/ |
||||
|
void TriggerOnChange(ControllerTriggerType type); |
||||
|
|
||||
|
NpadIdType npad_id_type; |
||||
|
NpadType npad_type{NpadType::None}; |
||||
|
bool is_connected{false}; |
||||
|
bool is_configuring{false}; |
||||
|
bool is_vibration_enabled{true}; |
||||
|
f32 motion_sensitivity{0.01f}; |
||||
|
|
||||
|
ButtonParams button_params; |
||||
|
StickParams stick_params; |
||||
|
ControllerMotionParams motion_params; |
||||
|
TriggerParams trigger_params; |
||||
|
BatteryParams battery_params; |
||||
|
|
||||
|
ButtonDevices button_devices; |
||||
|
StickDevices stick_devices; |
||||
|
ControllerMotionDevices motion_devices; |
||||
|
TriggerDevices trigger_devices; |
||||
|
BatteryDevices battery_devices; |
||||
|
// VibrationDevices vibration_devices; |
||||
|
|
||||
|
mutable std::mutex mutex; |
||||
|
std::unordered_map<int, ControllerUpdateCallback> callback_list; |
||||
|
int last_callback_key = 0; |
||||
|
ControllerStatus controller; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Core::HID |
||||
@ -0,0 +1,349 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included
|
||||
|
|
||||
|
#include <fmt/format.h>
|
||||
|
|
||||
|
#include "core/hid/emulated_devices.h"
|
||||
|
#include "core/hid/input_converter.h"
|
||||
|
|
||||
|
namespace Core::HID { |
||||
|
|
||||
|
EmulatedDevices::EmulatedDevices() {} |
||||
|
|
||||
|
EmulatedDevices::~EmulatedDevices() = default; |
||||
|
|
||||
|
void EmulatedDevices::ReloadFromSettings() { |
||||
|
const auto& mouse = Settings::values.mouse_buttons; |
||||
|
|
||||
|
for (std::size_t index = 0; index < mouse.size(); ++index) { |
||||
|
mouse_button_params[index] = Common::ParamPackage(mouse[index]); |
||||
|
} |
||||
|
ReloadInput(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::ReloadInput() { |
||||
|
std::transform(mouse_button_params.begin() + Settings::NativeMouseButton::MOUSE_HID_BEGIN, |
||||
|
mouse_button_params.begin() + Settings::NativeMouseButton::MOUSE_HID_END, |
||||
|
mouse_button_devices.begin(), Input::CreateDevice<Input::InputDevice>); |
||||
|
|
||||
|
std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(), |
||||
|
keyboard_devices.begin(), Input::CreateDeviceFromString<Input::InputDevice>); |
||||
|
|
||||
|
std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(), |
||||
|
keyboard_modifier_devices.begin(), |
||||
|
Input::CreateDeviceFromString<Input::InputDevice>); |
||||
|
|
||||
|
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) { |
||||
|
if (!mouse_button_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
Input::InputCallback button_callback{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetMouseButton(callback, index); }}; |
||||
|
mouse_button_devices[index]->SetCallback(button_callback); |
||||
|
} |
||||
|
|
||||
|
for (std::size_t index = 0; index < keyboard_devices.size(); ++index) { |
||||
|
if (!keyboard_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
Input::InputCallback button_callback{ |
||||
|
[this, index](Input::CallbackStatus callback) { SetKeyboardButton(callback, index); }}; |
||||
|
keyboard_devices[index]->SetCallback(button_callback); |
||||
|
} |
||||
|
|
||||
|
for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) { |
||||
|
if (!keyboard_modifier_devices[index]) { |
||||
|
continue; |
||||
|
} |
||||
|
Input::InputCallback button_callback{[this, index](Input::CallbackStatus callback) { |
||||
|
SetKeyboardModifier(callback, index); |
||||
|
}}; |
||||
|
keyboard_modifier_devices[index]->SetCallback(button_callback); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::UnloadInput() { |
||||
|
for (auto& button : mouse_button_devices) { |
||||
|
button.reset(); |
||||
|
} |
||||
|
for (auto& button : keyboard_devices) { |
||||
|
button.reset(); |
||||
|
} |
||||
|
for (auto& button : keyboard_modifier_devices) { |
||||
|
button.reset(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::EnableConfiguration() { |
||||
|
is_configuring = true; |
||||
|
SaveCurrentConfig(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::DisableConfiguration() { |
||||
|
is_configuring = false; |
||||
|
} |
||||
|
|
||||
|
bool EmulatedDevices::IsConfiguring() const { |
||||
|
return is_configuring; |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::SaveCurrentConfig() { |
||||
|
if (!is_configuring) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
auto& mouse = Settings::values.mouse_buttons; |
||||
|
|
||||
|
for (std::size_t index = 0; index < mouse.size(); ++index) { |
||||
|
mouse[index] = mouse_button_params[index].Serialize(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::RestoreConfig() { |
||||
|
if (!is_configuring) { |
||||
|
return; |
||||
|
} |
||||
|
ReloadFromSettings(); |
||||
|
} |
||||
|
|
||||
|
Common::ParamPackage EmulatedDevices::GetMouseButtonParam(std::size_t index) const { |
||||
|
if (index >= mouse_button_params.size()) { |
||||
|
return {}; |
||||
|
} |
||||
|
return mouse_button_params[index]; |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::SetButtonParam(std::size_t index, Common::ParamPackage param) { |
||||
|
if (index >= mouse_button_params.size()) { |
||||
|
return; |
||||
|
} |
||||
|
mouse_button_params[index] = param; |
||||
|
ReloadInput(); |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::SetKeyboardButton(Input::CallbackStatus callback, std::size_t index) { |
||||
|
if (index >= device_status.keyboard_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
bool value_changed = false; |
||||
|
const auto new_status = TransformToButton(callback); |
||||
|
auto& current_status = device_status.keyboard_values[index]; |
||||
|
current_status.toggle = new_status.toggle; |
||||
|
|
||||
|
// Update button status with current
|
||||
|
if (!current_status.toggle) { |
||||
|
current_status.locked = false; |
||||
|
if (current_status.value != new_status.value) { |
||||
|
current_status.value = new_status.value; |
||||
|
value_changed = true; |
||||
|
} |
||||
|
} else { |
||||
|
// Toggle button and lock status
|
||||
|
if (new_status.value && !current_status.locked) { |
||||
|
current_status.locked = true; |
||||
|
current_status.value = !current_status.value; |
||||
|
value_changed = true; |
||||
|
} |
||||
|
|
||||
|
// Unlock button ready for next press
|
||||
|
if (!new_status.value && current_status.locked) { |
||||
|
current_status.locked = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!value_changed) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
TriggerOnChange(DeviceTriggerType::Keyboard); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// TODO(german77): Do this properly
|
||||
|
// switch (index) {
|
||||
|
// case Settings::NativeKeyboard::A:
|
||||
|
// interface_status.keyboard_state.a.Assign(current_status.value);
|
||||
|
// break;
|
||||
|
// ....
|
||||
|
//}
|
||||
|
|
||||
|
TriggerOnChange(DeviceTriggerType::Keyboard); |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::SetKeyboardModifier(Input::CallbackStatus callback, std::size_t index) { |
||||
|
if (index >= device_status.keyboard_moddifier_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
bool value_changed = false; |
||||
|
const auto new_status = TransformToButton(callback); |
||||
|
auto& current_status = device_status.keyboard_moddifier_values[index]; |
||||
|
current_status.toggle = new_status.toggle; |
||||
|
|
||||
|
// Update button status with current
|
||||
|
if (!current_status.toggle) { |
||||
|
current_status.locked = false; |
||||
|
if (current_status.value != new_status.value) { |
||||
|
current_status.value = new_status.value; |
||||
|
value_changed = true; |
||||
|
} |
||||
|
} else { |
||||
|
// Toggle button and lock status
|
||||
|
if (new_status.value && !current_status.locked) { |
||||
|
current_status.locked = true; |
||||
|
current_status.value = !current_status.value; |
||||
|
value_changed = true; |
||||
|
} |
||||
|
|
||||
|
// Unlock button ready for next press
|
||||
|
if (!new_status.value && current_status.locked) { |
||||
|
current_status.locked = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!value_changed) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
TriggerOnChange(DeviceTriggerType::KeyboardModdifier); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
switch (index) { |
||||
|
case Settings::NativeKeyboard::LeftControl: |
||||
|
case Settings::NativeKeyboard::RightControl: |
||||
|
device_status.keyboard_moddifier_state.control.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeKeyboard::LeftShift: |
||||
|
case Settings::NativeKeyboard::RightShift: |
||||
|
device_status.keyboard_moddifier_state.shift.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeKeyboard::LeftAlt: |
||||
|
device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeKeyboard::RightAlt: |
||||
|
device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeKeyboard::CapsLock: |
||||
|
device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeKeyboard::ScrollLock: |
||||
|
device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeKeyboard::NumLock: |
||||
|
device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
TriggerOnChange(DeviceTriggerType::KeyboardModdifier); |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::SetMouseButton(Input::CallbackStatus callback, std::size_t index) { |
||||
|
if (index >= device_status.mouse_button_values.size()) { |
||||
|
return; |
||||
|
} |
||||
|
std::lock_guard lock{mutex}; |
||||
|
bool value_changed = false; |
||||
|
const auto new_status = TransformToButton(callback); |
||||
|
auto& current_status = device_status.mouse_button_values[index]; |
||||
|
current_status.toggle = new_status.toggle; |
||||
|
|
||||
|
// Update button status with current
|
||||
|
if (!current_status.toggle) { |
||||
|
current_status.locked = false; |
||||
|
if (current_status.value != new_status.value) { |
||||
|
current_status.value = new_status.value; |
||||
|
value_changed = true; |
||||
|
} |
||||
|
} else { |
||||
|
// Toggle button and lock status
|
||||
|
if (new_status.value && !current_status.locked) { |
||||
|
current_status.locked = true; |
||||
|
current_status.value = !current_status.value; |
||||
|
value_changed = true; |
||||
|
} |
||||
|
|
||||
|
// Unlock button ready for next press
|
||||
|
if (!new_status.value && current_status.locked) { |
||||
|
current_status.locked = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!value_changed) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (is_configuring) { |
||||
|
TriggerOnChange(DeviceTriggerType::Mouse); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
switch (index) { |
||||
|
case Settings::NativeMouseButton::Left: |
||||
|
device_status.mouse_button_state.left.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeMouseButton::Right: |
||||
|
device_status.mouse_button_state.right.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeMouseButton::Middle: |
||||
|
device_status.mouse_button_state.middle.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeMouseButton::Forward: |
||||
|
device_status.mouse_button_state.forward.Assign(current_status.value); |
||||
|
break; |
||||
|
case Settings::NativeMouseButton::Back: |
||||
|
device_status.mouse_button_state.back.Assign(current_status.value); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
TriggerOnChange(DeviceTriggerType::Mouse); |
||||
|
} |
||||
|
|
||||
|
MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const { |
||||
|
return device_status.mouse_button_values; |
||||
|
} |
||||
|
|
||||
|
KeyboardKey EmulatedDevices::GetKeyboard() const { |
||||
|
return device_status.keyboard_state; |
||||
|
} |
||||
|
|
||||
|
KeyboardModifier EmulatedDevices::GetKeyboardModifier() const { |
||||
|
return device_status.keyboard_moddifier_state; |
||||
|
} |
||||
|
|
||||
|
MouseButton EmulatedDevices::GetMouseButtons() const { |
||||
|
return device_status.mouse_button_state; |
||||
|
} |
||||
|
|
||||
|
MousePosition EmulatedDevices::GetMousePosition() const { |
||||
|
return device_status.mouse_position_state; |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) { |
||||
|
for (const std::pair<int, InterfaceUpdateCallback> poller_pair : callback_list) { |
||||
|
const InterfaceUpdateCallback& poller = poller_pair.second; |
||||
|
if (poller.on_change) { |
||||
|
poller.on_change(type); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
callback_list.insert_or_assign(last_callback_key, update_callback); |
||||
|
return last_callback_key++; |
||||
|
} |
||||
|
|
||||
|
void EmulatedDevices::DeleteCallback(int key) { |
||||
|
std::lock_guard lock{mutex}; |
||||
|
if (!callback_list.contains(key)) { |
||||
|
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); |
||||
|
return; |
||||
|
} |
||||
|
callback_list.erase(key); |
||||
|
} |
||||
|
} // namespace Core::HID
|
||||
@ -0,0 +1,137 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
#include <mutex> |
||||
|
#include <unordered_map> |
||||
|
|
||||
|
#include "common/input.h" |
||||
|
#include "common/param_package.h" |
||||
|
#include "common/settings.h" |
||||
|
#include "core/hid/hid_types.h" |
||||
|
#include "core/hid/motion_input.h" |
||||
|
|
||||
|
namespace Core::HID { |
||||
|
|
||||
|
using KeyboardDevices = |
||||
|
std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeKeyboard::NumKeyboardKeys>; |
||||
|
using KeyboardModifierDevices = |
||||
|
std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeKeyboard::NumKeyboardMods>; |
||||
|
using MouseButtonDevices = |
||||
|
std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeMouseButton::NumMouseButtons>; |
||||
|
|
||||
|
using MouseButtonParams = |
||||
|
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>; |
||||
|
|
||||
|
using KeyboardValues = std::array<Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>; |
||||
|
using KeyboardModifierValues = |
||||
|
std::array<Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>; |
||||
|
using MouseButtonValues = |
||||
|
std::array<Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>; |
||||
|
|
||||
|
struct MousePosition { |
||||
|
s32 x; |
||||
|
s32 y; |
||||
|
s32 delta_wheel_x; |
||||
|
s32 delta_wheel_y; |
||||
|
}; |
||||
|
|
||||
|
struct DeviceStatus { |
||||
|
// Data from input_common |
||||
|
KeyboardValues keyboard_values{}; |
||||
|
KeyboardModifierValues keyboard_moddifier_values{}; |
||||
|
MouseButtonValues mouse_button_values{}; |
||||
|
|
||||
|
// Data for Nintendo devices |
||||
|
KeyboardKey keyboard_state{}; |
||||
|
KeyboardModifier keyboard_moddifier_state{}; |
||||
|
MouseButton mouse_button_state{}; |
||||
|
MousePosition mouse_position_state{}; |
||||
|
}; |
||||
|
|
||||
|
enum class DeviceTriggerType { |
||||
|
Keyboard, |
||||
|
KeyboardModdifier, |
||||
|
Mouse, |
||||
|
}; |
||||
|
|
||||
|
struct InterfaceUpdateCallback { |
||||
|
std::function<void(DeviceTriggerType)> on_change; |
||||
|
}; |
||||
|
|
||||
|
class EmulatedDevices { |
||||
|
public: |
||||
|
/** |
||||
|
* TODO: Write description |
||||
|
* |
||||
|
* @param npad_id_type |
||||
|
*/ |
||||
|
explicit EmulatedDevices(); |
||||
|
~EmulatedDevices(); |
||||
|
|
||||
|
YUZU_NON_COPYABLE(EmulatedDevices); |
||||
|
YUZU_NON_MOVEABLE(EmulatedDevices); |
||||
|
|
||||
|
void ReloadFromSettings(); |
||||
|
void ReloadInput(); |
||||
|
void UnloadInput(); |
||||
|
|
||||
|
void EnableConfiguration(); |
||||
|
void DisableConfiguration(); |
||||
|
bool IsConfiguring() const; |
||||
|
void SaveCurrentConfig(); |
||||
|
void RestoreConfig(); |
||||
|
|
||||
|
std::vector<Common::ParamPackage> GetMappedDevices() const; |
||||
|
|
||||
|
Common::ParamPackage GetMouseButtonParam(std::size_t index) const; |
||||
|
|
||||
|
void SetButtonParam(std::size_t index, Common::ParamPackage param); |
||||
|
|
||||
|
KeyboardValues GetKeyboardValues() const; |
||||
|
KeyboardModifierValues GetKeyboardModdifierValues() const; |
||||
|
MouseButtonValues GetMouseButtonsValues() const; |
||||
|
|
||||
|
KeyboardKey GetKeyboard() const; |
||||
|
KeyboardModifier GetKeyboardModifier() const; |
||||
|
MouseButton GetMouseButtons() const; |
||||
|
MousePosition GetMousePosition() const; |
||||
|
|
||||
|
int SetCallback(InterfaceUpdateCallback update_callback); |
||||
|
void DeleteCallback(int key); |
||||
|
|
||||
|
private: |
||||
|
/** |
||||
|
* Sets the status of a button. Applies toggle properties to the output. |
||||
|
* |
||||
|
* @param A CallbackStatus and a button index number |
||||
|
*/ |
||||
|
void SetKeyboardButton(Input::CallbackStatus callback, std::size_t index); |
||||
|
void SetKeyboardModifier(Input::CallbackStatus callback, std::size_t index); |
||||
|
void SetMouseButton(Input::CallbackStatus callback, std::size_t index); |
||||
|
|
||||
|
/** |
||||
|
* Triggers a callback that something has changed |
||||
|
* |
||||
|
* @param Input type of the trigger |
||||
|
*/ |
||||
|
void TriggerOnChange(DeviceTriggerType type); |
||||
|
|
||||
|
bool is_configuring{false}; |
||||
|
|
||||
|
MouseButtonParams mouse_button_params; |
||||
|
|
||||
|
KeyboardDevices keyboard_devices; |
||||
|
KeyboardModifierDevices keyboard_modifier_devices; |
||||
|
MouseButtonDevices mouse_button_devices; |
||||
|
|
||||
|
mutable std::mutex mutex; |
||||
|
std::unordered_map<int, InterfaceUpdateCallback> callback_list; |
||||
|
int last_callback_key = 0; |
||||
|
DeviceStatus device_status; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Core::HID |
||||
@ -0,0 +1,144 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/assert.h"
|
||||
|
#include "core/hid/hid_core.h"
|
||||
|
|
||||
|
namespace Core::HID { |
||||
|
|
||||
|
HIDCore::HIDCore() |
||||
|
: player_1{std::make_unique<EmulatedController>(NpadIdType::Player1)}, |
||||
|
player_2{std::make_unique<EmulatedController>(NpadIdType::Player2)}, |
||||
|
player_3{std::make_unique<EmulatedController>(NpadIdType::Player3)}, |
||||
|
player_4{std::make_unique<EmulatedController>(NpadIdType::Player4)}, |
||||
|
player_5{std::make_unique<EmulatedController>(NpadIdType::Player5)}, |
||||
|
player_6{std::make_unique<EmulatedController>(NpadIdType::Player6)}, |
||||
|
player_7{std::make_unique<EmulatedController>(NpadIdType::Player7)}, |
||||
|
player_8{std::make_unique<EmulatedController>(NpadIdType::Player8)}, |
||||
|
other{std::make_unique<EmulatedController>(NpadIdType::Other)}, |
||||
|
handheld{std::make_unique<EmulatedController>(NpadIdType::Handheld)}, |
||||
|
console{std::make_unique<EmulatedConsole>()}, devices{std::make_unique<EmulatedDevices>()} {} |
||||
|
|
||||
|
HIDCore::~HIDCore() = default; |
||||
|
|
||||
|
EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) { |
||||
|
switch (npad_id_type) { |
||||
|
case NpadIdType::Player1: |
||||
|
return player_1.get(); |
||||
|
case NpadIdType::Player2: |
||||
|
return player_2.get(); |
||||
|
case NpadIdType::Player3: |
||||
|
return player_3.get(); |
||||
|
case NpadIdType::Player4: |
||||
|
return player_4.get(); |
||||
|
case NpadIdType::Player5: |
||||
|
return player_5.get(); |
||||
|
case NpadIdType::Player6: |
||||
|
return player_6.get(); |
||||
|
case NpadIdType::Player7: |
||||
|
return player_7.get(); |
||||
|
case NpadIdType::Player8: |
||||
|
return player_8.get(); |
||||
|
case NpadIdType::Other: |
||||
|
return other.get(); |
||||
|
case NpadIdType::Handheld: |
||||
|
return handheld.get(); |
||||
|
case NpadIdType::Invalid: |
||||
|
default: |
||||
|
UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type); |
||||
|
return nullptr; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) const { |
||||
|
switch (npad_id_type) { |
||||
|
case NpadIdType::Player1: |
||||
|
return player_1.get(); |
||||
|
case NpadIdType::Player2: |
||||
|
return player_2.get(); |
||||
|
case NpadIdType::Player3: |
||||
|
return player_3.get(); |
||||
|
case NpadIdType::Player4: |
||||
|
return player_4.get(); |
||||
|
case NpadIdType::Player5: |
||||
|
return player_5.get(); |
||||
|
case NpadIdType::Player6: |
||||
|
return player_6.get(); |
||||
|
case NpadIdType::Player7: |
||||
|
return player_7.get(); |
||||
|
case NpadIdType::Player8: |
||||
|
return player_8.get(); |
||||
|
case NpadIdType::Other: |
||||
|
return other.get(); |
||||
|
case NpadIdType::Handheld: |
||||
|
return handheld.get(); |
||||
|
case NpadIdType::Invalid: |
||||
|
default: |
||||
|
UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type); |
||||
|
return nullptr; |
||||
|
} |
||||
|
} |
||||
|
EmulatedConsole* HIDCore::GetEmulatedConsole() { |
||||
|
return console.get(); |
||||
|
} |
||||
|
|
||||
|
const EmulatedConsole* HIDCore::GetEmulatedConsole() const { |
||||
|
return console.get(); |
||||
|
} |
||||
|
|
||||
|
EmulatedDevices* HIDCore::GetEmulatedDevices() { |
||||
|
return devices.get(); |
||||
|
} |
||||
|
|
||||
|
const EmulatedDevices* HIDCore::GetEmulatedDevices() const { |
||||
|
return devices.get(); |
||||
|
} |
||||
|
|
||||
|
EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) { |
||||
|
return GetEmulatedController(IndexToNpadIdType(index)); |
||||
|
} |
||||
|
|
||||
|
const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const { |
||||
|
return GetEmulatedController(IndexToNpadIdType(index)); |
||||
|
} |
||||
|
|
||||
|
void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) { |
||||
|
supported_style_tag.raw = style_tag.raw; |
||||
|
} |
||||
|
|
||||
|
NpadStyleTag HIDCore::GetSupportedStyleTag() const { |
||||
|
return supported_style_tag; |
||||
|
} |
||||
|
|
||||
|
void HIDCore::ReloadInputDevices() { |
||||
|
player_1->ReloadFromSettings(); |
||||
|
player_2->ReloadFromSettings(); |
||||
|
player_3->ReloadFromSettings(); |
||||
|
player_4->ReloadFromSettings(); |
||||
|
player_5->ReloadFromSettings(); |
||||
|
player_6->ReloadFromSettings(); |
||||
|
player_7->ReloadFromSettings(); |
||||
|
player_8->ReloadFromSettings(); |
||||
|
other->ReloadFromSettings(); |
||||
|
handheld->ReloadFromSettings(); |
||||
|
console->ReloadFromSettings(); |
||||
|
devices->ReloadFromSettings(); |
||||
|
} |
||||
|
|
||||
|
void HIDCore::UnloadInputDevices() { |
||||
|
player_1->UnloadInput(); |
||||
|
player_2->UnloadInput(); |
||||
|
player_3->UnloadInput(); |
||||
|
player_4->UnloadInput(); |
||||
|
player_5->UnloadInput(); |
||||
|
player_6->UnloadInput(); |
||||
|
player_7->UnloadInput(); |
||||
|
player_8->UnloadInput(); |
||||
|
other->UnloadInput(); |
||||
|
handheld->UnloadInput(); |
||||
|
console->UnloadInput(); |
||||
|
devices->UnloadInput(); |
||||
|
} |
||||
|
|
||||
|
} // namespace Core::HID
|
||||
@ -0,0 +1,60 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
|
||||
|
#include "core/hid/emulated_console.h" |
||||
|
#include "core/hid/emulated_controller.h" |
||||
|
#include "core/hid/emulated_devices.h" |
||||
|
|
||||
|
namespace Core::HID { |
||||
|
|
||||
|
class HIDCore { |
||||
|
public: |
||||
|
explicit HIDCore(); |
||||
|
~HIDCore(); |
||||
|
|
||||
|
YUZU_NON_COPYABLE(HIDCore); |
||||
|
YUZU_NON_MOVEABLE(HIDCore); |
||||
|
|
||||
|
EmulatedController* GetEmulatedController(NpadIdType npad_id_type); |
||||
|
const EmulatedController* GetEmulatedController(NpadIdType npad_id_type) const; |
||||
|
|
||||
|
EmulatedController* GetEmulatedControllerByIndex(std::size_t index); |
||||
|
const EmulatedController* GetEmulatedControllerByIndex(std::size_t index) const; |
||||
|
|
||||
|
EmulatedConsole* GetEmulatedConsole(); |
||||
|
const EmulatedConsole* GetEmulatedConsole() const; |
||||
|
|
||||
|
EmulatedDevices* GetEmulatedDevices(); |
||||
|
const EmulatedDevices* GetEmulatedDevices() const; |
||||
|
|
||||
|
void SetSupportedStyleTag(NpadStyleTag style_tag); |
||||
|
NpadStyleTag GetSupportedStyleTag() const; |
||||
|
|
||||
|
// Reloads all input devices from settings |
||||
|
void ReloadInputDevices(); |
||||
|
|
||||
|
// Removes all callbacks from input common |
||||
|
void UnloadInputDevices(); |
||||
|
|
||||
|
private: |
||||
|
std::unique_ptr<EmulatedController> player_1; |
||||
|
std::unique_ptr<EmulatedController> player_2; |
||||
|
std::unique_ptr<EmulatedController> player_3; |
||||
|
std::unique_ptr<EmulatedController> player_4; |
||||
|
std::unique_ptr<EmulatedController> player_5; |
||||
|
std::unique_ptr<EmulatedController> player_6; |
||||
|
std::unique_ptr<EmulatedController> player_7; |
||||
|
std::unique_ptr<EmulatedController> player_8; |
||||
|
std::unique_ptr<EmulatedController> other; |
||||
|
std::unique_ptr<EmulatedController> handheld; |
||||
|
std::unique_ptr<EmulatedConsole> console; |
||||
|
std::unique_ptr<EmulatedDevices> devices; |
||||
|
NpadStyleTag supported_style_tag; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Core::HID |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue