// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #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 EmulatedController::GetMappedDevices() const { std::vector devices; for (const auto& param : button_params) { if (!param.Has("engine")) { continue; } const auto devices_it = std::find_if( devices.begin(), devices.end(), [¶m](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(), [¶m](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( std::roundf(controller.stick_values[index].x.value * MAX_CIRCLEPAD_POS)), .y = static_cast( 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