Files
citra/src/core/hid/emulated_controller.cpp
2023-05-14 01:10:13 +03:00

519 lines
18 KiB
C++

// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "core/hid/emulated_controller.h"
#include "core/hid/input_converter.h"
namespace Core::HID {
// Use a common UUID for Virtual Gamepad
constexpr Common::UUID VIRTUAL_UUID =
Common::UUID{{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
// xperia64: 0x9A seems to be the calibrated limit of the circle pad
// Verified by using Input Redirector with very large-value digital inputs
// on the circle pad and calibrating using the system settings application
constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position
EmulatedController::EmulatedController() {}
EmulatedController::~EmulatedController() = default;
void EmulatedController::LoadDevices() {
LoadVirtualGamepadParams();
std::ranges::transform(button_params, button_devices.begin(), Common::Input::CreateInputDevice);
std::ranges::transform(stick_params, stick_devices.begin(), Common::Input::CreateInputDevice);
std::ranges::transform(motion_params, motion_devices.begin(), Common::Input::CreateInputDevice);
// Initialize virtual gamepad devices
std::ranges::transform(virtual_button_params, virtual_button_devices.begin(),
Common::Input::CreateInputDevice);
std::ranges::transform(virtual_stick_params, virtual_stick_devices.begin(),
Common::Input::CreateInputDevice);
}
void EmulatedController::LoadVirtualGamepadParams() {
Common::ParamPackage common_params{};
common_params.Set("engine", "virtual_gamepad");
common_params.Set("port", 0);
for (auto& param : virtual_button_params) {
param = common_params;
}
for (auto& param : virtual_stick_params) {
param = common_params;
}
// TODO(german77): Replace this with an input profile or something better
virtual_button_params[Settings::NativeButton::A].Set("button", 0);
virtual_button_params[Settings::NativeButton::B].Set("button", 1);
virtual_button_params[Settings::NativeButton::X].Set("button", 2);
virtual_button_params[Settings::NativeButton::Y].Set("button", 3);
virtual_button_params[Settings::NativeButton::DUp].Set("button", 4);
virtual_button_params[Settings::NativeButton::DDown].Set("button", 5);
virtual_button_params[Settings::NativeButton::DLeft].Set("button", 6);
virtual_button_params[Settings::NativeButton::DRight].Set("button", 7);
virtual_button_params[Settings::NativeButton::L].Set("button", 8);
virtual_button_params[Settings::NativeButton::R].Set("button", 9);
virtual_button_params[Settings::NativeButton::Start].Set("button", 10);
virtual_button_params[Settings::NativeButton::Select].Set("button", 11);
virtual_button_params[Settings::NativeButton::Debug].Set("button", 12);
virtual_button_params[Settings::NativeButton::Gpio14].Set("button", 13);
virtual_button_params[Settings::NativeButton::ZL].Set("button", 14);
virtual_button_params[Settings::NativeButton::ZR].Set("button", 15);
virtual_button_params[Settings::NativeButton::Home].Set("button", 16);
virtual_stick_params[Settings::NativeAnalog::CirclePad].Set("axis_x", 0);
virtual_stick_params[Settings::NativeAnalog::CirclePad].Set("axis_y", 1);
virtual_stick_params[Settings::NativeAnalog::CStick].Set("axis_x", 2);
virtual_stick_params[Settings::NativeAnalog::CStick].Set("axis_y", 3);
}
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& button : virtual_button_devices) {
button.reset();
}
for (auto& stick : virtual_stick_devices) {
stick.reset();
}
}
void EmulatedController::EnableConfiguration() {
is_configuring = true;
}
void EmulatedController::DisableConfiguration() {
is_configuring = false;
}
void EmulatedController::ReloadInput() {
// If you load any device here add the equivalent to the UnloadInput() function
LoadDevices();
for (std::size_t index = 0; index < button_devices.size(); ++index) {
if (!button_devices[index]) {
continue;
}
const auto uuid = Common::UUID{button_params[index].Get("guid", "")};
button_devices[index]->SetCallback({
.on_change =
[this, index, uuid](const Common::Input::CallbackStatus& callback) {
SetButton(callback, index, uuid);
},
});
button_devices[index]->ForceUpdate();
}
for (std::size_t index = 0; index < stick_devices.size(); ++index) {
if (!stick_devices[index]) {
continue;
}
const auto uuid = Common::UUID{stick_params[index].Get("guid", "")};
stick_devices[index]->SetCallback({
.on_change =
[this, index, uuid](const Common::Input::CallbackStatus& callback) {
SetStick(callback, index, uuid);
},
});
stick_devices[index]->ForceUpdate();
}
for (std::size_t index = 0; index < motion_devices.size(); ++index) {
if (!motion_devices[index]) {
continue;
}
motion_devices[index]->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
// SetMotion(callback, index);
},
});
motion_devices[index]->ForceUpdate();
}
// Register virtual devices. No need to force update
for (std::size_t index = 0; index < virtual_button_devices.size(); ++index) {
if (!virtual_button_devices[index]) {
continue;
}
virtual_button_devices[index]->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
SetButton(callback, index, VIRTUAL_UUID);
},
});
}
for (std::size_t index = 0; index < virtual_stick_devices.size(); ++index) {
if (!virtual_stick_devices[index]) {
continue;
}
virtual_stick_devices[index]->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
SetStick(callback, index, VIRTUAL_UUID);
},
});
}
}
void EmulatedController::ReloadFromSettings() {
const auto& player = Settings::values.players.GetValue();
for (std::size_t i = 0; i < player.buttons.size(); i++) {
button_params[i] = Common::ParamPackage(player.buttons[i]);
}
for (std::size_t i = 0; i < player.analogs.size(); i++) {
stick_params[i] = Common::ParamPackage(player.analogs[i]);
}
for (std::size_t index = 0; index < player.motions.size(); ++index) {
motion_params[index] = Common::ParamPackage(player.motions[index]);
}
ReloadInput();
}
void EmulatedController::SaveCurrentConfig() {
auto& player = Settings::values.players.GetValue();
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();
}
}
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", 0) == param_.Get("port", 0) &&
param.Get("pad", 0) == param_.Get("pad", 0);
});
if (devices_it != devices.end()) {
continue;
}
auto& device = devices.emplace_back();
device.Set("engine", param.Get("engine", ""));
device.Set("guid", param.Get("guid", ""));
device.Set("port", param.Get("port", 0));
device.Set("pad", param.Get("pad", 0));
}
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", 0) == param_.Get("port", 0) &&
param.Get("pad", 0) == param_.Get("pad", 0);
});
if (devices_it != devices.end()) {
continue;
}
auto& device = devices.emplace_back();
device.Set("engine", param.Get("engine", ""));
device.Set("guid", param.Get("guid", ""));
device.Set("port", param.Get("port", 0));
device.Set("pad", param.Get("pad", 0));
}
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] = std::move(param);
ReloadInput();
}
void EmulatedController::SetStickParam(std::size_t index, Common::ParamPackage param) {
if (index >= stick_params.size()) {
return;
}
stick_params[index] = std::move(param);
ReloadInput();
}
void EmulatedController::SetMotionParam(std::size_t index, Common::ParamPackage param) {
if (index >= motion_params.size()) {
return;
}
motion_params[index] = std::move(param);
ReloadInput();
}
ButtonValues EmulatedController::GetButtonsValues() const {
std::scoped_lock lock{mutex};
return controller.button_values;
}
SticksValues EmulatedController::GetSticksValues() const {
std::scoped_lock lock{mutex};
return controller.stick_values;
}
PadState EmulatedController::GetPadState() const {
std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
return controller.pad_state;
}
ExtraState EmulatedController::GetExtraState() const {
std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
return controller.extra_state;
}
AnalogSticks EmulatedController::GetSticks() const {
std::unique_lock lock{mutex};
if (is_configuring) {
return {};
}
return controller.analog_stick_state;
}
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
Common::UUID uuid) {
if (index >= controller.button_values.size()) {
return;
}
std::unique_lock lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = controller.button_values[index];
// Only read button values that have the same uuid or are pressed once
if (current_status.uuid != uuid) {
if (!new_status.value) {
return;
}
}
current_status.toggle = new_status.toggle;
current_status.uuid = uuid;
// 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.pad_state.hex = 0;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Button, false);
return;
}
switch (index) {
case Settings::NativeButton::A:
controller.pad_state.a.Assign(current_status.value);
break;
case Settings::NativeButton::B:
controller.pad_state.b.Assign(current_status.value);
break;
case Settings::NativeButton::X:
controller.pad_state.x.Assign(current_status.value);
break;
case Settings::NativeButton::Y:
controller.pad_state.y.Assign(current_status.value);
break;
case Settings::NativeButton::L:
controller.pad_state.l.Assign(current_status.value);
break;
case Settings::NativeButton::R:
controller.pad_state.r.Assign(current_status.value);
break;
case Settings::NativeButton::ZL:
controller.extra_state.zl = current_status.value;
break;
case Settings::NativeButton::ZR:
controller.extra_state.zr = current_status.value;
break;
case Settings::NativeButton::Start:
controller.pad_state.start.Assign(current_status.value);
break;
case Settings::NativeButton::Select:
controller.pad_state.select.Assign(current_status.value);
break;
case Settings::NativeButton::DLeft:
controller.pad_state.left.Assign(current_status.value);
break;
case Settings::NativeButton::DUp:
controller.pad_state.up.Assign(current_status.value);
break;
case Settings::NativeButton::DRight:
controller.pad_state.right.Assign(current_status.value);
break;
case Settings::NativeButton::DDown:
controller.pad_state.down.Assign(current_status.value);
break;
case Settings::NativeButton::Debug:
controller.pad_state.debug.Assign(current_status.value);
break;
case Settings::NativeButton::Gpio14:
controller.pad_state.gpio14.Assign(current_status.value);
break;
}
lock.unlock();
TriggerOnChange(ControllerTriggerType::Button, true);
}
void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, std::size_t index,
Common::UUID uuid) {
if (index >= controller.stick_values.size()) {
return;
}
std::unique_lock lock{mutex};
const auto stick_value = TransformToStick(callback);
// Only read stick values that have the same uuid or are over the threshold to avoid flapping
if (controller.stick_values[index].uuid != uuid) {
if (!stick_value.down && !stick_value.up && !stick_value.left && !stick_value.right) {
return;
}
}
controller.stick_values[index] = stick_value;
controller.stick_values[index].uuid = uuid;
if (is_configuring) {
controller.analog_stick_state.circle_pad = {};
controller.analog_stick_state.c_stick = {};
lock.unlock();
TriggerOnChange(ControllerTriggerType::Stick, false);
return;
}
const AnalogStickState stick{
.x = static_cast<s16>(
std::roundf(controller.stick_values[index].x.value * MAX_CIRCLEPAD_POS)),
.y = static_cast<s16>(
std::roundf(controller.stick_values[index].y.value * MAX_CIRCLEPAD_POS)),
};
switch (index) {
case Settings::NativeAnalog::CirclePad:
controller.analog_stick_state.circle_pad = stick;
controller.pad_state.circle_left.Assign(controller.stick_values[index].left);
controller.pad_state.circle_up.Assign(controller.stick_values[index].up);
controller.pad_state.circle_right.Assign(controller.stick_values[index].right);
controller.pad_state.circle_down.Assign(controller.stick_values[index].down);
break;
case Settings::NativeAnalog::CStick:
controller.analog_stick_state.c_stick = stick;
controller.extra_state.c_stick_left = controller.stick_values[index].left;
controller.extra_state.c_stick_up = controller.stick_values[index].up;
controller.extra_state.c_stick_right = controller.stick_values[index].right;
controller.extra_state.c_stick_down = controller.stick_values[index].down;
break;
}
lock.unlock();
TriggerOnChange(ControllerTriggerType::Stick, true);
}
void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_hid_service_update) {
std::scoped_lock lock{callback_mutex};
for (const auto& poller_pair : callback_list) {
const ControllerUpdateCallback& poller = poller_pair.second;
if (!is_hid_service_update && poller.is_hid_service) {
continue;
}
if (poller.on_change) {
poller.on_change(type);
}
}
}
int EmulatedController::SetCallback(ControllerUpdateCallback update_callback) {
std::scoped_lock lock{callback_mutex};
callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
return last_callback_key++;
}
void EmulatedController::DeleteCallback(int key) {
std::scoped_lock lock{callback_mutex};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
return;
}
callback_list.erase(iterator);
}
} // namespace Core::HID