diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index e1221530f..1b0a35608 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -1,44 +1,61 @@ add_library(input_common STATIC - analog_from_button.cpp - analog_from_button.h - keyboard.cpp - keyboard.h + drivers/keyboard.cpp + drivers/keyboard.h + drivers/mouse.cpp + drivers/mouse.h + drivers/touch_screen.cpp + drivers/touch_screen.h + drivers/udp_client.cpp + drivers/udp_client.h + drivers/virtual_gamepad.cpp + drivers/virtual_gamepad.h + helpers/stick_from_buttons.cpp + helpers/stick_from_buttons.h + helpers/touch_from_buttons.cpp + helpers/touch_from_buttons.h + helpers/udp_protocol.cpp + helpers/udp_protocol.h + input_engine.cpp + input_engine.h + input_mapping.cpp + input_mapping.h + input_poller.cpp + input_poller.h main.cpp main.h - motion_emu.cpp - motion_emu.h precompiled_headers.h - touch_from_button.cpp - touch_from_button.h - sdl/sdl.cpp - sdl/sdl.h - udp/client.cpp - udp/client.h - udp/protocol.cpp - udp/protocol.h - udp/udp.cpp - udp/udp.h ) -if(ENABLE_SDL2) +if (MSVC) + target_compile_options(input_common PRIVATE + /W4 + + /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data + /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /we4800 # Implicit conversion from 'type' to bool. Possible information loss + ) +else() + target_compile_options(input_common PRIVATE + -Werror=conversion + ) +endif() + +if (ENABLE_SDL2) target_sources(input_common PRIVATE - sdl/sdl_impl.cpp - sdl/sdl_impl.h + drivers/sdl_driver.cpp + drivers/sdl_driver.h ) target_link_libraries(input_common PRIVATE SDL2::SDL2) target_compile_definitions(input_common PRIVATE HAVE_SDL2) endif() -if(ENABLE_LIBUSB) +if (ENABLE_LIBUSB) target_sources(input_common PRIVATE - gcadapter/gc_adapter.cpp - gcadapter/gc_adapter.h - gcadapter/gc_poller.cpp - gcadapter/gc_poller.h + drivers/gc_adapter.cpp + drivers/gc_adapter.h ) - target_include_directories(input_common PRIVATE ${LIBUSB_INCLUDE_DIR}) - target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES}) - add_definitions(-DENABLE_GCADAPTER) + target_link_libraries(input_common PRIVATE libusb::usb) + target_compile_definitions(input_common PRIVATE HAVE_LIBUSB) endif() create_target_directory_groups(input_common) diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp deleted file mode 100755 index e1a260762..000000000 --- a/src/input_common/analog_from_button.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "input_common/analog_from_button.h" - -namespace InputCommon { - -class Analog final : public Input::AnalogDevice { -public: - using Button = std::unique_ptr; - - Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_, - float modifier_scale_) - : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), - right(std::move(right_)), modifier(std::move(modifier_)), - modifier_scale(modifier_scale_) {} - - std::tuple GetStatus() const override { - constexpr float SQRT_HALF = 0.707106781f; - int x = 0, y = 0; - - if (right->GetStatus()) - ++x; - if (left->GetStatus()) - --x; - if (up->GetStatus()) - ++y; - if (down->GetStatus()) - --y; - - float coef = modifier->GetStatus() ? modifier_scale : 1.0f; - return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF), - y * coef * (x == 0 ? 1.0f : SQRT_HALF)); - } - -private: - Button up; - Button down; - Button left; - Button right; - Button modifier; - float modifier_scale; -}; - -std::unique_ptr AnalogFromButton::Create(const Common::ParamPackage& params) { - const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); - auto up = Input::CreateDevice(params.Get("up", null_engine)); - auto down = Input::CreateDevice(params.Get("down", null_engine)); - auto left = Input::CreateDevice(params.Get("left", null_engine)); - auto right = Input::CreateDevice(params.Get("right", null_engine)); - auto modifier = Input::CreateDevice(params.Get("modifier", null_engine)); - auto modifier_scale = params.Get("modifier_scale", 0.5f); - return std::make_unique(std::move(up), std::move(down), std::move(left), - std::move(right), std::move(modifier), modifier_scale); -} - -} // namespace InputCommon diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp new file mode 100644 index 000000000..826fa2109 --- /dev/null +++ b/src/input_common/drivers/gc_adapter.cpp @@ -0,0 +1,546 @@ +// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "common/settings_input.h" +#include "common/thread.h" +#include "input_common/drivers/gc_adapter.h" + +namespace InputCommon { + +class LibUSBContext { +public: + explicit LibUSBContext() { + init_result = libusb_init(&ctx); + } + + ~LibUSBContext() { + libusb_exit(ctx); + } + + LibUSBContext& operator=(const LibUSBContext&) = delete; + LibUSBContext(const LibUSBContext&) = delete; + + LibUSBContext& operator=(LibUSBContext&&) noexcept = delete; + LibUSBContext(LibUSBContext&&) noexcept = delete; + + [[nodiscard]] int InitResult() const noexcept { + return init_result; + } + + [[nodiscard]] libusb_context* get() noexcept { + return ctx; + } + +private: + libusb_context* ctx; + int init_result{}; +}; + +class LibUSBDeviceHandle { +public: + explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept { + handle = libusb_open_device_with_vid_pid(ctx, vid, pid); + } + + ~LibUSBDeviceHandle() noexcept { + if (handle) { + libusb_release_interface(handle, 1); + libusb_close(handle); + } + } + + LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete; + LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete; + + LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete; + LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete; + + [[nodiscard]] libusb_device_handle* get() noexcept { + return handle; + } + +private: + libusb_device_handle* handle{}; +}; + +GCAdapter::GCAdapter(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + if (usb_adapter_handle) { + return; + } + LOG_DEBUG(Input, "Initialization started"); + + libusb_ctx = std::make_unique(); + const int init_res = libusb_ctx->InitResult(); + if (init_res == LIBUSB_SUCCESS) { + adapter_scan_thread = + std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); }); + } else { + LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); + } +} + +GCAdapter::~GCAdapter() { + Reset(); +} + +void GCAdapter::AdapterInputThread(std::stop_token stop_token) { + LOG_DEBUG(Input, "Input thread started"); + Common::SetCurrentThreadName("GCAdapter"); + s32 payload_size{}; + AdapterPayload adapter_payload{}; + + adapter_scan_thread = {}; + + while (!stop_token.stop_requested()) { + libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(), + static_cast(adapter_payload.size()), &payload_size, 16); + if (IsPayloadCorrect(adapter_payload, payload_size)) { + UpdateControllers(adapter_payload); + UpdateVibrations(); + } + std::this_thread::yield(); + } + + if (restart_scan_thread) { + adapter_scan_thread = + std::jthread([this](std::stop_token token) { AdapterScanThread(token); }); + restart_scan_thread = false; + } +} + +bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { + if (payload_size != static_cast(adapter_payload.size()) || + adapter_payload[0] != LIBUSB_DT_HID) { + LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, + adapter_payload[0]); + if (input_error_counter++ > 20) { + LOG_ERROR(Input, "Timeout, Is the adapter connected?"); + adapter_input_thread.request_stop(); + restart_scan_thread = true; + } + return false; + } + + input_error_counter = 0; + return true; +} + +void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) { + for (std::size_t port = 0; port < pads.size(); ++port) { + const std::size_t offset = 1 + (9 * port); + const auto type = static_cast(adapter_payload[offset] >> 4); + UpdatePadType(port, type); + if (DeviceConnected(port)) { + const u8 b1 = adapter_payload[offset + 1]; + const u8 b2 = adapter_payload[offset + 2]; + UpdateStateButtons(port, b1, b2); + UpdateStateAxes(port, adapter_payload); + } + } +} + +void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { + if (pads[port].type == pad_type) { + return; + } + // Device changed reset device and set new type + pads[port].axis_origin = {}; + pads[port].reset_origin_counter = {}; + pads[port].enable_vibration = {}; + pads[port].rumble_amplitude = {}; + pads[port].type = pad_type; +} + +void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1, + [[maybe_unused]] u8 b2) { + if (port >= pads.size()) { + return; + } + + static constexpr std::array b1_buttons{ + PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY, + PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, + }; + + static constexpr std::array b2_buttons{ + PadButton::ButtonStart, + PadButton::TriggerZ, + PadButton::TriggerR, + PadButton::TriggerL, + }; + + for (std::size_t i = 0; i < b1_buttons.size(); ++i) { + const bool button_status = (b1 & (1U << i)) != 0; + const int button = static_cast(b1_buttons[i]); + SetButton(pads[port].identifier, button, button_status); + } + + for (std::size_t j = 0; j < b2_buttons.size(); ++j) { + const bool button_status = (b2 & (1U << j)) != 0; + const int button = static_cast(b2_buttons[j]); + SetButton(pads[port].identifier, button, button_status); + } +} + +void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { + if (port >= pads.size()) { + return; + } + + const std::size_t offset = 1 + (9 * port); + static constexpr std::array axes{ + PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX, + PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, + }; + + for (const PadAxes axis : axes) { + const auto index = static_cast(axis); + const u8 axis_value = adapter_payload[offset + 3 + index]; + if (pads[port].reset_origin_counter <= 18) { + if (pads[port].axis_origin[index] != axis_value) { + pads[port].reset_origin_counter = 0; + } + pads[port].axis_origin[index] = axis_value; + pads[port].reset_origin_counter++; + } + const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f; + SetAxis(pads[port].identifier, static_cast(index), axis_status); + } +} + +void GCAdapter::AdapterScanThread(std::stop_token stop_token) { + Common::SetCurrentThreadName("ScanGCAdapter"); + usb_adapter_handle = nullptr; + pads = {}; + while (!stop_token.stop_requested() && !Setup()) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + } +} + +bool GCAdapter::Setup() { + constexpr u16 nintendo_vid = 0x057e; + constexpr u16 gc_adapter_pid = 0x0337; + usb_adapter_handle = + std::make_unique(libusb_ctx->get(), nintendo_vid, gc_adapter_pid); + if (!usb_adapter_handle->get()) { + return false; + } + if (!CheckDeviceAccess()) { + usb_adapter_handle = nullptr; + return false; + } + + libusb_device* const device = libusb_get_device(usb_adapter_handle->get()); + + LOG_INFO(Input, "GC adapter is now connected"); + // GC Adapter found and accessible, registering it + if (GetGCEndpoint(device)) { + rumble_enabled = true; + input_error_counter = 0; + output_error_counter = 0; + + std::size_t port = 0; + for (GCController& pad : pads) { + pad.identifier = { + .guid = Common::UUID{}, + .port = port++, + .pad = 0, + }; + PreSetController(pad.identifier); + } + + adapter_input_thread = + std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); }); + return true; + } + return false; +} + +bool GCAdapter::CheckDeviceAccess() { + s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0); + if (kernel_driver_error == 1) { + kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0); + if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { + LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", + kernel_driver_error); + } + } + + if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { + usb_adapter_handle = nullptr; + return false; + } + + const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0); + if (interface_claim_error) { + LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); + usb_adapter_handle = nullptr; + return false; + } + + // This fixes payload problems from offbrand GCAdapters + const s32 control_transfer_error = + libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000); + if (control_transfer_error < 0) { + LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); + } + + return true; +} + +bool GCAdapter::GetGCEndpoint(libusb_device* device) { + libusb_config_descriptor* config = nullptr; + const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); + if (config_descriptor_return != LIBUSB_SUCCESS) { + LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}", + config_descriptor_return); + return false; + } + + for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { + const libusb_interface* interfaceContainer = &config->interface[ic]; + for (int i = 0; i < interfaceContainer->num_altsetting; i++) { + const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; + for (u8 e = 0; e < interface->bNumEndpoints; e++) { + const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; + if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) { + input_endpoint = endpoint->bEndpointAddress; + } else { + output_endpoint = endpoint->bEndpointAddress; + } + } + } + } + // This transfer seems to be responsible for clearing the state of the adapter + // Used to clear the "busy" state of when the device is unexpectedly unplugged + unsigned char clear_payload = 0x13; + libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload, + sizeof(clear_payload), nullptr, 16); + return true; +} + +Common::Input::VibrationError GCAdapter::SetVibration( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { + const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; + const auto processed_amplitude = + static_cast((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); + + pads[identifier.port].rumble_amplitude = processed_amplitude; + + if (!rumble_enabled) { + return Common::Input::VibrationError::Disabled; + } + return Common::Input::VibrationError::None; +} + +bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { + return rumble_enabled; +} + +void GCAdapter::UpdateVibrations() { + // Use 8 states to keep the switching between on/off fast enough for + // a human to feel different vibration strenght + // More states == more rumble strengths == slower update time + constexpr u8 vibration_states = 8; + + vibration_counter = (vibration_counter + 1) % vibration_states; + + for (GCController& pad : pads) { + const bool vibrate = pad.rumble_amplitude > vibration_counter; + vibration_changed |= vibrate != pad.enable_vibration; + pad.enable_vibration = vibrate; + } + SendVibrations(); +} + +void GCAdapter::SendVibrations() { + if (!rumble_enabled || !vibration_changed) { + return; + } + s32 size{}; + constexpr u8 rumble_command = 0x11; + const u8 p1 = pads[0].enable_vibration; + const u8 p2 = pads[1].enable_vibration; + const u8 p3 = pads[2].enable_vibration; + const u8 p4 = pads[3].enable_vibration; + std::array payload = {rumble_command, p1, p2, p3, p4}; + const int err = + libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(), + static_cast(payload.size()), &size, 16); + if (err) { + LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err)); + if (output_error_counter++ > 5) { + LOG_ERROR(Input, "Output timeout, Rumble disabled"); + rumble_enabled = false; + } + return; + } + output_error_counter = 0; + vibration_changed = false; +} + +bool GCAdapter::DeviceConnected(std::size_t port) const { + return pads[port].type != ControllerTypes::None; +} + +void GCAdapter::Reset() { + adapter_scan_thread = {}; + adapter_input_thread = {}; + usb_adapter_handle = nullptr; + pads = {}; + libusb_ctx = nullptr; +} + +std::vector GCAdapter::GetInputDevices() const { + std::vector devices; + for (std::size_t port = 0; port < pads.size(); ++port) { + if (!DeviceConnected(port)) { + continue; + } + Common::ParamPackage identifier{}; + identifier.Set("engine", GetEngineName()); + identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1)); + identifier.Set("port", static_cast(port)); + devices.emplace_back(identifier); + } + return devices; +} + +ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) { + // This list is missing ZL/ZR since those are not considered buttons. + // We will add those afterwards + // This list also excludes any button that can't be really mapped + static constexpr std::array, 12> + switch_to_gcadapter_button = { + std::pair{Settings::NativeButton::A, PadButton::ButtonA}, + {Settings::NativeButton::B, PadButton::ButtonB}, + {Settings::NativeButton::X, PadButton::ButtonX}, + {Settings::NativeButton::Y, PadButton::ButtonY}, + {Settings::NativeButton::Plus, PadButton::ButtonStart}, + {Settings::NativeButton::DLeft, PadButton::ButtonLeft}, + {Settings::NativeButton::DUp, PadButton::ButtonUp}, + {Settings::NativeButton::DRight, PadButton::ButtonRight}, + {Settings::NativeButton::DDown, PadButton::ButtonDown}, + {Settings::NativeButton::SL, PadButton::TriggerL}, + {Settings::NativeButton::SR, PadButton::TriggerR}, + {Settings::NativeButton::R, PadButton::TriggerZ}, + }; + if (!params.Has("port")) { + return {}; + } + + ButtonMapping mapping{}; + for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { + Common::ParamPackage button_params{}; + button_params.Set("engine", GetEngineName()); + button_params.Set("port", params.Get("port", 0)); + button_params.Set("button", static_cast(gcadapter_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + // Add the missing bindings for ZL/ZR + static constexpr std::array, 2> + switch_to_gcadapter_axis = { + std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft}, + {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight}, + }; + for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) { + Common::ParamPackage button_params{}; + button_params.Set("engine", GetEngineName()); + button_params.Set("port", params.Get("port", 0)); + button_params.Set("button", static_cast(gcadapter_buton)); + button_params.Set("axis", static_cast(gcadapter_axis)); + button_params.Set("threshold", 0.5f); + button_params.Set("range", 1.9f); + button_params.Set("direction", "+"); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + return mapping; +} + +AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("port")) { + return {}; + } + + AnalogMapping mapping = {}; + Common::ParamPackage left_analog_params; + left_analog_params.Set("engine", GetEngineName()); + left_analog_params.Set("port", params.Get("port", 0)); + left_analog_params.Set("axis_x", static_cast(PadAxes::StickX)); + left_analog_params.Set("axis_y", static_cast(PadAxes::StickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", GetEngineName()); + right_analog_params.Set("port", params.Get("port", 0)); + right_analog_params.Set("axis_x", static_cast(PadAxes::SubstickX)); + right_analog_params.Set("axis_y", static_cast(PadAxes::SubstickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const { + PadButton button = static_cast(params.Get("button", 0)); + switch (button) { + case PadButton::ButtonLeft: + return Common::Input::ButtonNames::ButtonLeft; + case PadButton::ButtonRight: + return Common::Input::ButtonNames::ButtonRight; + case PadButton::ButtonDown: + return Common::Input::ButtonNames::ButtonDown; + case PadButton::ButtonUp: + return Common::Input::ButtonNames::ButtonUp; + case PadButton::TriggerZ: + return Common::Input::ButtonNames::TriggerZ; + case PadButton::TriggerR: + return Common::Input::ButtonNames::TriggerR; + case PadButton::TriggerL: + return Common::Input::ButtonNames::TriggerL; + case PadButton::ButtonA: + return Common::Input::ButtonNames::ButtonA; + case PadButton::ButtonB: + return Common::Input::ButtonNames::ButtonB; + case PadButton::ButtonX: + return Common::Input::ButtonNames::ButtonX; + case PadButton::ButtonY: + return Common::Input::ButtonNames::ButtonY; + case PadButton::ButtonStart: + return Common::Input::ButtonNames::ButtonStart; + default: + return Common::Input::ButtonNames::Undefined; + } +} + +Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return GetUIButtonName(params); + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + + return Common::Input::ButtonNames::Invalid; +} + +bool GCAdapter::IsStickInverted(const Common::ParamPackage& params) { + if (!params.Has("port")) { + return false; + } + + const auto x_axis = static_cast(params.Get("axis_x", 0)); + const auto y_axis = static_cast(params.Get("axis_y", 0)); + if (x_axis != PadAxes::StickY && x_axis != PadAxes::SubstickY) { + return false; + } + if (y_axis != PadAxes::StickX && y_axis != PadAxes::SubstickX) { + return false; + } + return true; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h new file mode 100644 index 000000000..b5270fd0b --- /dev/null +++ b/src/input_common/drivers/gc_adapter.h @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/polyfill_thread.h" +#include "input_common/input_engine.h" + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + +namespace InputCommon { + +class LibUSBContext; +class LibUSBDeviceHandle; + +class GCAdapter : public InputEngine { +public: + explicit GCAdapter(std::string input_engine_); + ~GCAdapter() override; + + Common::Input::VibrationError SetVibration( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; + + bool IsVibrationEnabled(const PadIdentifier& identifier) override; + + /// Used for automapping features + std::vector GetInputDevices() const override; + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + + bool IsStickInverted(const Common::ParamPackage& params) override; + +private: + enum class PadButton { + Undefined = 0x0000, + ButtonLeft = 0x0001, + ButtonRight = 0x0002, + ButtonDown = 0x0004, + ButtonUp = 0x0008, + TriggerZ = 0x0010, + TriggerR = 0x0020, + TriggerL = 0x0040, + ButtonA = 0x0100, + ButtonB = 0x0200, + ButtonX = 0x0400, + ButtonY = 0x0800, + ButtonStart = 0x1000, + }; + + enum class PadAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + TriggerLeft, + TriggerRight, + Undefined, + }; + + enum class ControllerTypes { + None, + Wired, + Wireless, + }; + + struct GCController { + ControllerTypes type = ControllerTypes::None; + PadIdentifier identifier{}; + bool enable_vibration = false; + u8 rumble_amplitude{}; + std::array axis_origin{}; + u8 reset_origin_counter{}; + }; + + using AdapterPayload = std::array; + + void UpdatePadType(std::size_t port, ControllerTypes pad_type); + void UpdateControllers(const AdapterPayload& adapter_payload); + void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); + void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); + + void AdapterInputThread(std::stop_token stop_token); + + void AdapterScanThread(std::stop_token stop_token); + + bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); + + /// For use in initialization, querying devices to find the adapter + bool Setup(); + + /// Returns true if we successfully gain access to GC Adapter + bool CheckDeviceAccess(); + + /// Captures GC Adapter endpoint address + /// Returns true if the endpoint was set correctly + bool GetGCEndpoint(libusb_device* device); + + /// Returns true if there is a device connected to port + bool DeviceConnected(std::size_t port) const; + + /// For shutting down, clear all data, join all threads, release usb + void Reset(); + + void UpdateVibrations(); + + /// Updates vibration state of all controllers + void SendVibrations(); + + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + + std::unique_ptr usb_adapter_handle; + std::array pads; + + std::jthread adapter_input_thread; + std::jthread adapter_scan_thread; + bool restart_scan_thread{}; + + std::unique_ptr libusb_ctx; + + u8 input_endpoint{0}; + u8 output_endpoint{0}; + u8 input_error_counter{0}; + u8 output_error_counter{0}; + int vibration_counter{0}; + + bool rumble_enabled{true}; + bool vibration_changed{true}; +}; +} // namespace InputCommon diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp new file mode 100644 index 000000000..7731772c8 --- /dev/null +++ b/src/input_common/drivers/keyboard.cpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/param_package.h" +#include "input_common/drivers/keyboard.h" + +namespace InputCommon { + +constexpr PadIdentifier key_identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, +}; + +Keyboard::Keyboard(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + PreSetController(key_identifier); +} + +void Keyboard::PressKey(int key_code) { + SetButton(key_identifier, key_code, true); +} + +void Keyboard::ReleaseKey(int key_code) { + SetButton(key_identifier, key_code, false); +} + +void Keyboard::ReleaseAllKeys() { + ResetButtonState(); +} + +std::vector Keyboard::GetInputDevices() const { + std::vector devices; + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", "Keyboard Only"}, + }); + return devices; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h new file mode 100644 index 000000000..ded68dc00 --- /dev/null +++ b/src/input_common/drivers/keyboard.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Keyboard final : public InputEngine { +public: + explicit Keyboard(std::string input_engine_); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void PressKey(int key_code); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void ReleaseKey(int key_code); + + /// Sets all keys to the non pressed state + void ReleaseAllKeys(); + + /// Used for automapping features + std::vector GetInputDevices() const override; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp new file mode 100644 index 000000000..6b705f98f --- /dev/null +++ b/src/input_common/drivers/mouse.cpp @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "input_common/drivers/mouse.h" + +namespace InputCommon { +constexpr int mouse_axis_x = 0; +constexpr int mouse_axis_y = 1; +constexpr int wheel_axis_x = 2; +constexpr int wheel_axis_y = 3; +constexpr int motion_wheel_y = 4; +constexpr int touch_axis_x = 10; +constexpr int touch_axis_y = 11; +constexpr PadIdentifier identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, +}; + +Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + PreSetController(identifier); + PreSetAxis(identifier, mouse_axis_x); + PreSetAxis(identifier, mouse_axis_y); + PreSetAxis(identifier, wheel_axis_x); + PreSetAxis(identifier, wheel_axis_y); + PreSetAxis(identifier, motion_wheel_y); + PreSetAxis(identifier, touch_axis_x); + PreSetAxis(identifier, touch_axis_y); + update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); +} + +void Mouse::UpdateThread(std::stop_token stop_token) { + Common::SetCurrentThreadName("Mouse"); + constexpr int update_time = 10; + while (!stop_token.stop_requested()) { + SetAxis(identifier, motion_wheel_y, 0.0f); + std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); + } +} + +void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) { + SetAxis(identifier, touch_axis_x, touch_x); + SetAxis(identifier, touch_axis_y, touch_y); + + if (button_pressed) { + constexpr float sensitivity = 0.0012f; + const auto mouse_move = Common::MakeVec(x, y) - mouse_origin; + SetAxis(identifier, mouse_axis_x, static_cast(mouse_move.x) * sensitivity); + SetAxis(identifier, mouse_axis_y, static_cast(-mouse_move.y) * sensitivity); + } +} + +void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) { + SetAxis(identifier, touch_axis_x, touch_x); + SetAxis(identifier, touch_axis_y, touch_y); + SetButton(identifier, static_cast(button), true); + // Set initial analog parameters + mouse_origin = {x, y}; + last_mouse_position = {x, y}; + button_pressed = true; +} + +void Mouse::ReleaseButton(MouseButton button) { + SetButton(identifier, static_cast(button), false); + button_pressed = false; +} + +void Mouse::MouseWheelChange(int x, int y) { + wheel_position.x += x; + wheel_position.y += y; + SetAxis(identifier, wheel_axis_x, static_cast(wheel_position.x)); + SetAxis(identifier, wheel_axis_y, static_cast(wheel_position.y)); + SetAxis(identifier, motion_wheel_y, static_cast(y) / 100.0f); +} + +void Mouse::ReleaseAllButtons() { + ResetButtonState(); + button_pressed = false; +} + +std::vector Mouse::GetInputDevices() const { + std::vector devices; + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", "Keyboard/Mouse"}, + }); + return devices; +} + +AnalogMapping Mouse::GetAnalogMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + // Only overwrite different buttons from default + AnalogMapping mapping = {}; + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", GetEngineName()); + right_analog_params.Set("axis_x", 0); + right_analog_params.Set("axis_y", 1); + right_analog_params.Set("threshold", 0.5f); + right_analog_params.Set("range", 1.0f); + right_analog_params.Set("deadzone", 0.0f); + mapping.insert_or_assign(Settings::NativeAnalog::CStick, std::move(right_analog_params)); + return mapping; +} + +Common::Input::ButtonNames Mouse::GetUIButtonName(const Common::ParamPackage& params) const { + const auto button = static_cast(params.Get("button", 0)); + switch (button) { + case MouseButton::Left: + return Common::Input::ButtonNames::ButtonLeft; + case MouseButton::Right: + return Common::Input::ButtonNames::ButtonRight; + case MouseButton::Wheel: + return Common::Input::ButtonNames::ButtonMouseWheel; + case MouseButton::Backward: + return Common::Input::ButtonNames::ButtonBackward; + case MouseButton::Forward: + return Common::Input::ButtonNames::ButtonForward; + case MouseButton::Task: + return Common::Input::ButtonNames::ButtonTask; + case MouseButton::Extra: + return Common::Input::ButtonNames::ButtonExtra; + case MouseButton::Undefined: + default: + return Common::Input::ButtonNames::Undefined; + } +} + +Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return GetUIButtonName(params); + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { + return Common::Input::ButtonNames::Engine; + } + + return Common::Input::ButtonNames::Invalid; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h new file mode 100644 index 000000000..dc39d39a8 --- /dev/null +++ b/src/input_common/drivers/mouse.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/vector_math.h" +#include "input_common/input_engine.h" + +namespace InputCommon { + +enum class MouseButton { + Left, + Right, + Wheel, + Backward, + Forward, + Task, + Extra, + Undefined, +}; + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Mouse final : public InputEngine { +public: + explicit Mouse(std::string input_engine_); + + /** + * Signals that mouse has moved. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param center_x the x-coordinate of the middle of the screen + * @param center_y the y-coordinate of the middle of the screen + */ + void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void ReleaseButton(MouseButton button); + + /** + * Sets the status of the mouse wheel + * @param x delta movement in the x direction + * @param y delta movement in the y direction + */ + void MouseWheelChange(int x, int y); + + void ReleaseAllButtons(); + + std::vector GetInputDevices() const override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + +private: + void UpdateThread(std::stop_token stop_token); + + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + + Common::Vec2 mouse_origin; + Common::Vec2 last_mouse_position; + Common::Vec2 last_mouse_change; + Common::Vec2 wheel_position; + bool button_pressed; + std::jthread update_thread; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp new file mode 100644 index 000000000..473d3e0da --- /dev/null +++ b/src/input_common/drivers/sdl_driver.cpp @@ -0,0 +1,818 @@ +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "common/vector_math.h" +#include "input_common/drivers/sdl_driver.h" + +namespace InputCommon { + +namespace { +Common::UUID GetGUID(SDL_Joystick* joystick) { + const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + std::array data{}; + std::memcpy(data.data(), guid.data, sizeof(data)); + // Clear controller name crc + std::memset(data.data() + 2, 0, sizeof(u16)); + return Common::UUID{data}; +} +} // Anonymous namespace + +static int SDLEventWatcher(void* user_data, SDL_Event* event) { + auto* const sdl_state = static_cast(user_data); + + sdl_state->HandleGameControllerEvent(*event); + + return 0; +} + +class SDLJoystick { +public: + SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, + SDL_GameController* game_controller) + : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, + sdl_controller{game_controller, &SDL_GameControllerClose} { + EnableMotion(); + } + + void EnableMotion() { + if (sdl_controller) { + SDL_GameController* controller = sdl_controller.get(); + has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; + has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; + if (has_accel) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + } + if (has_gyro) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); + } + } + } + + bool HasGyro() const { + return has_gyro; + } + + bool HasAccel() const { + return has_accel; + } + + bool UpdateMotion(SDL_ControllerSensorEvent event) { + constexpr float gravity_constant = 9.80665f; + std::scoped_lock lock{mutex}; + const u64 time_difference = event.timestamp - last_motion_update; + last_motion_update = event.timestamp; + switch (event.sensor) { + case SDL_SENSOR_ACCEL: { + motion.accel_x = -event.data[0] / gravity_constant; + motion.accel_y = event.data[2] / gravity_constant; + motion.accel_z = -event.data[1] / gravity_constant; + break; + } + case SDL_SENSOR_GYRO: { + motion.gyro_x = event.data[0] / (Common::PI * 2); + motion.gyro_y = -event.data[2] / (Common::PI * 2); + motion.gyro_z = event.data[1] / (Common::PI * 2); + break; + } + } + + // Ignore duplicated timestamps + if (time_difference == 0) { + return false; + } + motion.delta_timestamp = time_difference * 1000; + return true; + } + + const BasicMotion& GetMotion() const { + return motion; + } + + /** + * The Pad identifier of the joystick + */ + const PadIdentifier GetPadIdentifier() const { + return { + .guid = guid, + .port = static_cast(port), + .pad = 0, + }; + } + + /** + * The guid of the joystick + */ + const Common::UUID& GetGUID() const { + return guid; + } + + /** + * The number of joystick from the same type that were connected before this joystick + */ + int GetPort() const { + return port; + } + + SDL_Joystick* GetSDLJoystick() const { + return sdl_joystick.get(); + } + + SDL_GameController* GetSDLGameController() const { + return sdl_controller.get(); + } + + void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + sdl_joystick.reset(joystick); + sdl_controller.reset(controller); + } + + std::string GetControllerName() const { + if (sdl_controller) { + switch (SDL_GameControllerGetType(sdl_controller.get())) { + case SDL_CONTROLLER_TYPE_XBOX360: + return "Xbox 360 Controller"; + case SDL_CONTROLLER_TYPE_XBOXONE: + return "Xbox One Controller"; + case SDL_CONTROLLER_TYPE_PS3: + return "DualShock 3 Controller"; + case SDL_CONTROLLER_TYPE_PS4: + return "DualShock 4 Controller"; + case SDL_CONTROLLER_TYPE_PS5: + return "DualSense Controller"; + default: + break; + } + const auto name = SDL_GameControllerName(sdl_controller.get()); + if (name) { + return name; + } + } + + if (sdl_joystick) { + const auto name = SDL_JoystickName(sdl_joystick.get()); + if (name) { + return name; + } + } + + return "Unknown"; + } + +private: + Common::UUID guid; + int port; + std::unique_ptr sdl_joystick; + std::unique_ptr sdl_controller; + mutable std::mutex mutex; + + u64 last_motion_update{}; + bool has_gyro{false}; + bool has_accel{false}; + BasicMotion motion; +}; + +std::shared_ptr SDLDriver::GetSDLJoystickByGUID(const Common::UUID& guid, int port) { + std::scoped_lock lock{joystick_map_mutex}; + const auto it = joystick_map.find(guid); + + if (it != joystick_map.end()) { + while (it->second.size() <= static_cast(port)) { + auto joystick = std::make_shared(guid, static_cast(it->second.size()), + nullptr, nullptr); + it->second.emplace_back(std::move(joystick)); + } + + return it->second[static_cast(port)]; + } + + auto joystick = std::make_shared(guid, 0, nullptr, nullptr); + + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +std::shared_ptr SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) { + return GetSDLJoystickByGUID(Common::UUID{guid}, port); +} + +std::shared_ptr SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { + auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + const auto guid = GetGUID(sdl_joystick); + + std::scoped_lock lock{joystick_map_mutex}; + const auto map_it = joystick_map.find(guid); + + if (map_it == joystick_map.end()) { + return nullptr; + } + + const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [&sdl_joystick](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + + if (vec_it == map_it->second.end()) { + return nullptr; + } + + return *vec_it; +} + +void SDLDriver::InitJoystick(int joystick_index) { + SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); + SDL_GameController* sdl_gamecontroller = nullptr; + + if (SDL_IsGameController(joystick_index)) { + sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); + } + + if (!sdl_joystick) { + LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); + return; + } + + const auto guid = GetGUID(sdl_joystick); + + std::scoped_lock lock{joystick_map_mutex}; + if (joystick_map.find(guid) == joystick_map.end()) { + auto joystick = std::make_shared(guid, 0, sdl_joystick, sdl_gamecontroller); + PreSetController(joystick->GetPadIdentifier()); + joystick->EnableMotion(); + joystick_map[guid].emplace_back(std::move(joystick)); + return; + } + + auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = + std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [](const auto& joystick) { return !joystick->GetSDLJoystick(); }); + + if (joystick_it != joystick_guid_list.end()) { + (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); + (*joystick_it)->EnableMotion(); + return; + } + + const int port = static_cast(joystick_guid_list.size()); + auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); + PreSetController(joystick->GetPadIdentifier()); + joystick->EnableMotion(); + joystick_guid_list.emplace_back(std::move(joystick)); +} + +void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) { + const auto guid = GetGUID(sdl_joystick); + + std::scoped_lock lock{joystick_map_mutex}; + // This call to guid is safe since the joystick is guaranteed to be in the map + const auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [&sdl_joystick](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + + if (joystick_it != joystick_guid_list.end()) { + (*joystick_it)->SetSDLJoystick(nullptr, nullptr); + } +} + +void SDLDriver::PumpEvents() const { + if (initialized) { + SDL_PumpEvents(); + } +} + +void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_JOYBUTTONUP: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetButton(identifier, event.jbutton.button, false); + } + break; + } + case SDL_JOYBUTTONDOWN: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetButton(identifier, event.jbutton.button, true); + } + break; + } + case SDL_JOYHATMOTION: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetHatButton(identifier, event.jhat.hat, event.jhat.value); + } + break; + } + case SDL_JOYAXISMOTION: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); + } + break; + } + case SDL_CONTROLLERSENSORUPDATE: { + if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { + if (joystick->UpdateMotion(event.csensor)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetMotion(identifier, 0, joystick->GetMotion()); + } + } + break; + } + case SDL_JOYDEVICEREMOVED: + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); + CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + break; + case SDL_JOYDEVICEADDED: + LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); + InitJoystick(event.jdevice.which); + break; + } +} + +void SDLDriver::CloseJoysticks() { + std::scoped_lock lock{joystick_map_mutex}; + joystick_map.clear(); +} + +SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + // Prevent SDL from adding undesired axis + SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); + + // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + + // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and + // not a generic one + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + + // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native + // driver on Linux. + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); + + // If the frontend is going to manage the event loop, then we don't start one here + start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0; + if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { + LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); + return; + } + + SDL_AddEventWatch(&SDLEventWatcher, this); + + initialized = true; + // Because the events for joystick connection happens before we have our event watcher added, we + // can just open all the joysticks right here + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + InitJoystick(i); + } +} + +SDLDriver::~SDLDriver() { + CloseJoysticks(); + SDL_DelEventWatch(&SDLEventWatcher, this); + + initialized = false; + if (start_thread) { + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); + } +} + +std::vector SDLDriver::GetInputDevices() const { + std::vector devices; + std::unordered_map> joycon_pairs; + for (const auto& [key, value] : joystick_map) { + for (const auto& joystick : value) { + if (!joystick->GetSDLJoystick()) { + continue; + } + const std::string name = + fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID().RawString()}, + {"port", std::to_string(joystick->GetPort())}, + }); + } + } + + return devices; +} + +Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, const Common::UUID& guid, + s32 axis, float value) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", guid.RawString()); + params.Set("axis", axis); + params.Set("threshold", "0.5"); + params.Set("invert", value < 0 ? "-" : "+"); + return params; +} + +Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, const Common::UUID& guid, + s32 button) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", guid.RawString()); + params.Set("button", button); + return params; +} + +Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, const Common::UUID& guid, + s32 hat, u8 value) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", guid.RawString()); + params.Set("hat", hat); + params.Set("direction", GetHatButtonName(value)); + return params; +} + +Common::ParamPackage SDLDriver::BuildMotionParam(int port, const Common::UUID& guid) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("motion", 0); + params.Set("port", port); + params.Set("guid", guid.RawString()); + return params; +} + +Common::ParamPackage SDLDriver::BuildParamPackageForBinding( + int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const { + switch (binding.bindType) { + case SDL_CONTROLLER_BINDTYPE_NONE: + break; + case SDL_CONTROLLER_BINDTYPE_AXIS: + return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); + case SDL_CONTROLLER_BINDTYPE_BUTTON: + return BuildButtonParamPackageForButton(port, guid, binding.value.button); + case SDL_CONTROLLER_BINDTYPE_HAT: + return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, + static_cast(binding.value.hat.hat_mask)); + } + return {}; +} + +Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, + int axis_y, float offset_x, + float offset_y) const { + Common::ParamPackage params; + params.Set("engine", GetEngineName()); + params.Set("port", static_cast(identifier.port)); + params.Set("guid", identifier.guid.RawString()); + params.Set("axis_x", axis_x); + params.Set("axis_y", axis_y); + params.Set("offset_x", offset_x); + params.Set("offset_y", offset_y); + params.Set("invert_x", "+"); + params.Set("invert_y", "+"); + return params; +} + +ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. + // We will add those afterwards + // This list also excludes Screenshot since theres not really a mapping for that + ButtonBindings switch_to_sdl_button; + + if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) { + switch_to_sdl_button = GetNintendoButtonBinding(joystick); + } else { + switch_to_sdl_button = GetDefaultButtonBinding(); + } + + // Add the missing bindings for ZL/ZR + static constexpr ZButtonBindings switch_to_sdl_axis{{ + {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, + {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, + }}; + + // Parameters contain two joysticks return dual + if (params.Has("guid2")) { + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + + if (joystick2->GetSDLGameController() != nullptr) { + return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, + switch_to_sdl_axis); + } + } + + return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); +} + +ButtonBindings SDLDriver::GetDefaultButtonBinding() const { + return { + std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, + {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, + {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, + {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, + {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Start, SDL_CONTROLLER_BUTTON_START}, + {Settings::NativeButton::Select, SDL_CONTROLLER_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {Settings::NativeButton::ZL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::ZR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, + }; +} + +ButtonBindings SDLDriver::GetNintendoButtonBinding( + const std::shared_ptr& joystick) const { + return { + std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A}, + {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B}, + {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X}, + {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y}, + {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Start, SDL_CONTROLLER_BUTTON_START}, + {Settings::NativeButton::Select, SDL_CONTROLLER_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {Settings::NativeButton::ZL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::ZR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, + }; +} + +ButtonMapping SDLDriver::GetSingleControllerMapping( + const std::shared_ptr& joystick, const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const { + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + auto* controller = joystick->GetSDLGameController(); + + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { + const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + + return mapping; +} + +ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr& joystick, + const std::shared_ptr& joystick2, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const { + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + auto* controller = joystick->GetSDLGameController(); + auto* controller2 = joystick2->GetSDLGameController(); + + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { + if (IsButtonOnLeftSide(switch_button)) { + const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); + continue; + } + const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + if (IsButtonOnLeftSide(switch_button)) { + const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); + continue; + } + const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + + return mapping; +} + +bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const { + switch (button) { + case Settings::NativeButton::DDown: + case Settings::NativeButton::DLeft: + case Settings::NativeButton::DRight: + case Settings::NativeButton::DUp: + case Settings::NativeButton::L: + case Settings::NativeButton::Start: + case Settings::NativeButton::ZL: + return true; + default: + return false; + } +} + +AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + AnalogMapping mapping = {}; + const auto& binding_left_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + const auto& binding_left_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + if (params.Has("guid2")) { + const auto identifier = joystick2->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_left_x.value.axis); + PreSetAxis(identifier, binding_left_y.value.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); + const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::CirclePad, + BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, + binding_left_y.value.axis, + left_offset_x, left_offset_y)); + } else { + const auto identifier = joystick->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_left_x.value.axis); + PreSetAxis(identifier, binding_left_y.value.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); + const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::CirclePad, + BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, + binding_left_y.value.axis, + left_offset_x, left_offset_y)); + } + const auto& binding_right_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); + const auto& binding_right_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + const auto identifier = joystick->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_right_x.value.axis); + PreSetAxis(identifier, binding_right_y.value.axis); + const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis); + const auto right_offset_y = GetAxis(identifier, binding_right_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::CStick, + BuildParamPackageForAnalog(identifier, binding_right_x.value.axis, + binding_right_y.value.axis, right_offset_x, + right_offset_y)); + return mapping; +} + +MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + MotionMapping mapping = {}; + joystick->EnableMotion(); + + if (joystick->HasGyro() || joystick->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, + BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); + } + if (params.Has("guid2")) { + joystick2->EnableMotion(); + if (joystick2->HasGyro() || joystick2->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, + BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); + } + } else { + if (joystick->HasGyro() || joystick->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, + BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); + } + } + + return mapping; +} + +Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + // TODO(German77): Find how to substitue the values for real button names + return Common::Input::ButtonNames::Value; + } + if (params.Has("hat")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("motion")) { + return Common::Input::ButtonNames::Engine; + } + + return Common::Input::ButtonNames::Invalid; +} + +std::string SDLDriver::GetHatButtonName(u8 direction_value) const { + switch (direction_value) { + case SDL_HAT_UP: + return "up"; + case SDL_HAT_DOWN: + return "down"; + case SDL_HAT_LEFT: + return "left"; + case SDL_HAT_RIGHT: + return "right"; + default: + return {}; + } +} + +u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const { + Uint8 direction; + if (direction_name == "up") { + direction = SDL_HAT_UP; + } else if (direction_name == "down") { + direction = SDL_HAT_DOWN; + } else if (direction_name == "left") { + direction = SDL_HAT_LEFT; + } else if (direction_name == "right") { + direction = SDL_HAT_RIGHT; + } else { + direction = 0; + } + return direction; +} + +bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return false; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + if (joystick == nullptr) { + return false; + } + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return false; + } + + const auto& axis_x = params.Get("axis_x", 0); + const auto& axis_y = params.Get("axis_y", 0); + const auto& binding_left_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + const auto& binding_right_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); + const auto& binding_left_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + const auto& binding_right_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + + if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) { + return false; + } + if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) { + return false; + } + return true; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h new file mode 100644 index 000000000..9e88f17e4 --- /dev/null +++ b/src/input_common/drivers/sdl_driver.h @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include + +#include "common/common_types.h" +#include "common/threadsafe_queue.h" +#include "input_common/input_engine.h" + +union SDL_Event; +using SDL_GameController = struct _SDL_GameController; +using SDL_Joystick = struct _SDL_Joystick; +using SDL_JoystickID = s32; + +namespace InputCommon { + +class SDLJoystick; + +using ButtonBindings = + std::array, 18>; +using ZButtonBindings = + std::array, 2>; + +class SDLDriver : public InputEngine { +public: + /// Initializes and registers SDL device factories + explicit SDLDriver(std::string input_engine_); + + /// Unregisters SDL device factories and shut them down. + ~SDLDriver() override; + + void PumpEvents() const; + + /// Handle SDL_Events for joysticks from SDL_PollEvent + void HandleGameControllerEvent(const SDL_Event& event); + + /// Get the nth joystick with the corresponding GUID + std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); + + /** + * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so + * tie it to a SDLJoystick with the same guid and that port + */ + std::shared_ptr GetSDLJoystickByGUID(const Common::UUID& guid, int port); + std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port); + + std::vector GetInputDevices() const override; + + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + + std::string GetHatButtonName(u8 direction_value) const override; + u8 GetHatButtonId(const std::string& direction_name) const override; + + bool IsStickInverted(const Common::ParamPackage& params) override; + +private: + void InitJoystick(int joystick_index); + void CloseJoystick(SDL_Joystick* sdl_joystick); + + /// Needs to be called before SDL_QuitSubSystem. + void CloseJoysticks(); + + Common::ParamPackage BuildAnalogParamPackageForButton(int port, const Common::UUID& guid, + s32 axis, float value = 0.1f) const; + Common::ParamPackage BuildButtonParamPackageForButton(int port, const Common::UUID& guid, + s32 button) const; + + Common::ParamPackage BuildHatParamPackageForButton(int port, const Common::UUID& guid, s32 hat, + u8 value) const; + + Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const; + + Common::ParamPackage BuildParamPackageForBinding( + int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const; + + Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, + int axis_y, float offset_x, + float offset_y) const; + + /// Returns the default button bindings list for generic controllers + ButtonBindings GetDefaultButtonBinding() const; + + /// Returns the default button bindings list for nintendo controllers + ButtonBindings GetNintendoButtonBinding(const std::shared_ptr& joystick) const; + + /// Returns the button mappings from a single controller + ButtonMapping GetSingleControllerMapping(const std::shared_ptr& joystick, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const; + + /// Returns the button mappings from two different controllers + ButtonMapping GetDualControllerMapping(const std::shared_ptr& joystick, + const std::shared_ptr& joystick2, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const; + + /// Returns true if the button is on the left joycon + bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; + + /// Map of GUID of a list of corresponding virtual Joysticks + std::unordered_map>> joystick_map; + std::mutex joystick_map_mutex; + + bool start_thread = false; + std::atomic initialized = false; +}; +} // namespace InputCommon diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp new file mode 100644 index 000000000..1753e0893 --- /dev/null +++ b/src/input_common/drivers/touch_screen.cpp @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/param_package.h" +#include "input_common/drivers/touch_screen.h" + +namespace InputCommon { + +constexpr PadIdentifier identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, +}; + +TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + PreSetController(identifier); + ReleaseAllTouch(); +} + +void TouchScreen::TouchMoved(float x, float y, std::size_t finger_id) { + const auto index = GetIndexFromFingerId(finger_id); + if (!index) { + // Touch doesn't exist handle it as a new one + TouchPressed(x, y, finger_id); + return; + } + const auto i = index.value(); + fingers[i].is_active = true; + SetButton(identifier, static_cast(i), true); + SetAxis(identifier, static_cast(i * 2), x); + SetAxis(identifier, static_cast(i * 2 + 1), y); +} + +void TouchScreen::TouchPressed(float x, float y, std::size_t finger_id) { + if (GetIndexFromFingerId(finger_id)) { + // Touch already exist. Just update the data + TouchMoved(x, y, finger_id); + return; + } + const auto index = GetNextFreeIndex(); + if (!index) { + // No free entries. Ignore input + return; + } + const auto i = index.value(); + fingers[i].is_enabled = true; + fingers[i].finger_id = finger_id; + TouchMoved(x, y, finger_id); +} + +void TouchScreen::TouchReleased(std::size_t finger_id) { + const auto index = GetIndexFromFingerId(finger_id); + if (!index) { + return; + } + const auto i = index.value(); + fingers[i].is_enabled = false; + SetButton(identifier, static_cast(i), false); + SetAxis(identifier, static_cast(i * 2), 0.0f); + SetAxis(identifier, static_cast(i * 2 + 1), 0.0f); +} + +std::optional TouchScreen::GetIndexFromFingerId(std::size_t finger_id) const { + for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) { + const auto& finger = fingers[index]; + if (!finger.is_enabled) { + continue; + } + if (finger.finger_id == finger_id) { + return index; + } + } + return std::nullopt; +} + +std::optional TouchScreen::GetNextFreeIndex() const { + for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) { + if (!fingers[index].is_enabled) { + return index; + } + } + return std::nullopt; +} + +void TouchScreen::ClearActiveFlag() { + for (auto& finger : fingers) { + finger.is_active = false; + } +} + +void TouchScreen::ReleaseInactiveTouch() { + for (const auto& finger : fingers) { + if (!finger.is_active) { + TouchReleased(finger.finger_id); + } + } +} + +void TouchScreen::ReleaseAllTouch() { + for (const auto& finger : fingers) { + if (finger.is_enabled) { + TouchReleased(finger.finger_id); + } + } +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h new file mode 100644 index 000000000..f46036ffd --- /dev/null +++ b/src/input_common/drivers/touch_screen.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A touch device factory representing a touch screen. It receives touch events and forward them + * to all touch devices it created. + */ +class TouchScreen final : public InputEngine { +public: + explicit TouchScreen(std::string input_engine_); + + /** + * Signals that touch has moved and marks this touch point as active + * @param x new horizontal position + * @param y new vertical position + * @param finger_id of the touch point to be updated + */ + void TouchMoved(float x, float y, std::size_t finger_id); + + /** + * Signals and creates a new touch point with this finger id + * @param x starting horizontal position + * @param y starting vertical position + * @param finger_id to be assigned to the new touch point + */ + void TouchPressed(float x, float y, std::size_t finger_id); + + /** + * Signals and resets the touch point related to the this finger id + * @param finger_id to be released + */ + void TouchReleased(std::size_t finger_id); + + /// Resets the active flag for each touch point + void ClearActiveFlag(); + + /// Releases all touch that haven't been marked as active + void ReleaseInactiveTouch(); + + /// Resets all inputs to their initial value + void ReleaseAllTouch(); + +private: + static constexpr std::size_t MAX_FINGER_COUNT = 16; + + struct TouchStatus { + std::size_t finger_id{}; + bool is_enabled{}; + bool is_active{}; + }; + + std::optional GetIndexFromFingerId(std::size_t finger_id) const; + + std::optional GetNextFreeIndex() const; + + std::array fingers{}; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp new file mode 100644 index 000000000..8f6afe3fb --- /dev/null +++ b/src/input_common/drivers/udp_client.cpp @@ -0,0 +1,648 @@ +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "common/settings.h" +#include "input_common/drivers/udp_client.h" +#include "input_common/helpers/udp_protocol.h" + +using boost::asio::ip::udp; + +namespace InputCommon::CemuhookUDP { + +struct SocketCallback { + std::function version; + std::function port_info; + std::function pad_data; +}; + +class Socket { +public: + using clock = std::chrono::system_clock; + + explicit Socket(const std::string& host, u16 port, SocketCallback callback_) + : callback(std::move(callback_)), timer(io_service), + socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) { + boost::system::error_code ec{}; + auto ipv4 = boost::asio::ip::make_address_v4(host, ec); + if (ec.value() != boost::system::errc::success) { + LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host); + ipv4 = boost::asio::ip::address_v4{}; + } + + send_endpoint = {udp::endpoint(ipv4, port)}; + } + + void Stop() { + io_service.stop(); + } + + void Loop() { + io_service.run(); + } + + void StartSend(const clock::time_point& from) { + timer.expires_at(from + std::chrono::seconds(3)); + timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); + } + + void StartReceive() { + socket.async_receive_from( + boost::asio::buffer(receive_buffer), receive_endpoint, + [this](const boost::system::error_code& error, std::size_t bytes_transferred) { + HandleReceive(error, bytes_transferred); + }); + } + +private: + u32 GenerateRandomClientId() const { + std::random_device device; + return device(); + } + + void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) { + if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { + switch (*type) { + case Type::Version: { + Response::Version version; + std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); + callback.version(std::move(version)); + break; + } + case Type::PortInfo: { + Response::PortInfo port_info; + std::memcpy(&port_info, &receive_buffer[sizeof(Header)], + sizeof(Response::PortInfo)); + callback.port_info(std::move(port_info)); + break; + } + case Type::PadData: { + Response::PadData pad_data; + std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); + callback.pad_data(std::move(pad_data)); + break; + } + } + } + StartReceive(); + } + + void HandleSend(const boost::system::error_code&) { + boost::system::error_code _ignored{}; + // Send a request for getting port info for the pad + const Request::PortInfo port_info{4, {0, 1, 2, 3}}; + const auto port_message = Request::Create(port_info, client_id); + std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); + socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); + + // Send a request for getting pad data for the pad + const Request::PadData pad_data{ + Request::RegisterFlags::AllPads, + 0, + EMPTY_MAC_ADDRESS, + }; + const auto pad_message = Request::Create(pad_data, client_id); + std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); + socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); + StartSend(timer.expiry()); + } + + SocketCallback callback; + boost::asio::io_service io_service; + boost::asio::basic_waitable_timer timer; + udp::socket socket; + + const u32 client_id; + + static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); + static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); + std::array send_buffer1; + std::array send_buffer2; + udp::endpoint send_endpoint; + + std::array receive_buffer; + udp::endpoint receive_endpoint; +}; + +static void SocketLoop(Socket* socket) { + socket->StartReceive(); + socket->StartSend(Socket::clock::now()); + socket->Loop(); +} + +UDPClient::UDPClient(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + LOG_INFO(Input, "Udp Initialization started"); + ReloadSockets(); +} + +UDPClient::~UDPClient() { + Reset(); +} + +UDPClient::ClientConnection::ClientConnection() = default; + +UDPClient::ClientConnection::~ClientConnection() = default; + +void UDPClient::ReloadSockets() { + Reset(); + + std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue()); + std::string server_token; + std::size_t client = 0; + while (std::getline(servers_ss, server_token, ',')) { + if (client == MAX_UDP_CLIENTS) { + break; + } + std::stringstream server_ss(server_token); + std::string token; + std::getline(server_ss, token, ':'); + std::string udp_input_address = token; + std::getline(server_ss, token, ':'); + char* temp; + const u16 udp_input_port = static_cast(std::strtol(token.c_str(), &temp, 0)); + if (*temp != '\0') { + LOG_ERROR(Input, "Port number is not valid {}", token); + continue; + } + + const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port); + if (client_number != MAX_UDP_CLIENTS) { + LOG_ERROR(Input, "Duplicated UDP servers found"); + continue; + } + StartCommunication(client++, udp_input_address, udp_input_port); + } +} + +std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const { + for (std::size_t client = 0; client < clients.size(); client++) { + if (clients[client].active == -1) { + continue; + } + if (clients[client].host == host && clients[client].port == port) { + return client; + } + } + return MAX_UDP_CLIENTS; +} + +Common::Input::BatteryLevel UDPClient::GetBatteryLevel(Response::Battery battery) const { + switch (battery) { + case Response::Battery::Dying: + return Common::Input::BatteryLevel::Empty; + case Response::Battery::Low: + return Common::Input::BatteryLevel::Critical; + case Response::Battery::Medium: + return Common::Input::BatteryLevel::Low; + case Response::Battery::High: + return Common::Input::BatteryLevel::Medium; + case Response::Battery::Full: + case Response::Battery::Charged: + return Common::Input::BatteryLevel::Full; + case Response::Battery::Charging: + default: + return Common::Input::BatteryLevel::Charging; + } +} + +void UDPClient::OnVersion([[maybe_unused]] Response::Version data) { + LOG_TRACE(Input, "Version packet received: {}", data.version); +} + +void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) { + LOG_TRACE(Input, "PortInfo packet received: {}", data.model); +} + +void UDPClient::OnPadData(Response::PadData data, std::size_t client) { + const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id; + + if (pad_index >= pads.size()) { + LOG_ERROR(Input, "Invalid pad id {}", data.info.id); + return; + } + + LOG_TRACE(Input, "PadData packet received"); + if (data.packet_counter == pads[pad_index].packet_sequence) { + LOG_WARNING( + Input, + "PadData packet dropped because its stale info. Current count: {} Packet count: {}", + pads[pad_index].packet_sequence, data.packet_counter); + pads[pad_index].connected = false; + return; + } + + clients[client].active = 1; + pads[pad_index].connected = true; + pads[pad_index].packet_sequence = data.packet_counter; + + const auto now = std::chrono::steady_clock::now(); + const auto time_difference = static_cast( + std::chrono::duration_cast(now - pads[pad_index].last_update) + .count()); + pads[pad_index].last_update = now; + + // Gyroscope values are not it the correct scale from better joy. + // Dividing by 312 allows us to make one full turn = 1 turn + // This must be a configurable valued called sensitivity + const float gyro_scale = 1.0f / 312.0f; + + const BasicMotion motion{ + .gyro_x = data.gyro.pitch * gyro_scale, + .gyro_y = data.gyro.roll * gyro_scale, + .gyro_z = -data.gyro.yaw * gyro_scale, + .accel_x = data.accel.x, + .accel_y = -data.accel.z, + .accel_z = data.accel.y, + .delta_timestamp = time_difference, + }; + const PadIdentifier identifier = GetPadIdentifier(pad_index); + SetMotion(identifier, 0, motion); + + for (std::size_t id = 0; id < data.touch.size(); ++id) { + const auto touch_pad = data.touch[id]; + const auto touch_axis_x_id = + static_cast(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X); + const auto touch_axis_y_id = + static_cast(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y); + const auto touch_button_id = + static_cast(id == 0 ? PadButton::Touch1 : PadButton::Touch2); + + // TODO: Use custom calibration per device + const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); + const u16 min_x = static_cast(touch_param.Get("min_x", 100)); + const u16 min_y = static_cast(touch_param.Get("min_y", 50)); + const u16 max_x = static_cast(touch_param.Get("max_x", 1800)); + const u16 max_y = static_cast(touch_param.Get("max_y", 850)); + + const f32 x = + static_cast(std::clamp(static_cast(touch_pad.x), min_x, max_x) - min_x) / + static_cast(max_x - min_x); + const f32 y = + static_cast(std::clamp(static_cast(touch_pad.y), min_y, max_y) - min_y) / + static_cast(max_y - min_y); + + if (touch_pad.is_active) { + SetAxis(identifier, touch_axis_x_id, x); + SetAxis(identifier, touch_axis_y_id, y); + SetButton(identifier, touch_button_id, true); + continue; + } + SetAxis(identifier, touch_axis_x_id, 0); + SetAxis(identifier, touch_axis_y_id, 0); + SetButton(identifier, touch_button_id, false); + } + + SetAxis(identifier, static_cast(PadAxes::LeftStickX), + (data.left_stick_x - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::LeftStickY), + (data.left_stick_y - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::RightStickX), + (data.right_stick_x - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::RightStickY), + (data.right_stick_y - 127.0f) / 127.0f); + + static constexpr std::array buttons{ + PadButton::Share, PadButton::L3, PadButton::R3, PadButton::Options, + PadButton::Up, PadButton::Right, PadButton::Down, PadButton::Left, + PadButton::L2, PadButton::R2, PadButton::L1, PadButton::R1, + PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square}; + + for (std::size_t i = 0; i < buttons.size(); ++i) { + const bool button_status = (data.digital_button & (1U << i)) != 0; + const int button = static_cast(buttons[i]); + SetButton(identifier, button, button_status); + } + + SetButton(identifier, static_cast(PadButton::Home), data.home != 0); + SetButton(identifier, static_cast(PadButton::TouchHardPress), data.touch_hard_press != 0); + + SetBattery(identifier, GetBatteryLevel(data.info.battery)); +} + +void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) { + SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, + [this](Response::PortInfo info) { OnPortInfo(info); }, + [this, client](Response::PadData data) { OnPadData(data, client); }}; + LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); + clients[client].uuid = GetHostUUID(host); + clients[client].host = host; + clients[client].port = port; + clients[client].active = 0; + clients[client].socket = std::make_unique(host, port, callback); + clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; + for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { + const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); + PreSetController(identifier); + } +} + +PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const { + const std::size_t client = pad_index / PADS_PER_CLIENT; + return { + .guid = clients[client].uuid, + .port = static_cast(clients[client].port), + .pad = pad_index, + }; +} + +Common::UUID UDPClient::GetHostUUID(const std::string& host) const { + const auto ip = boost::asio::ip::make_address_v4(host); + const auto hex_host = fmt::format("00000000-0000-0000-0000-0000{:06x}", ip.to_uint()); + return Common::UUID{hex_host}; +} + +void UDPClient::Reset() { + for (auto& client : clients) { + if (client.thread.joinable()) { + client.active = -1; + client.socket->Stop(); + client.thread.join(); + } + } +} + +std::vector UDPClient::GetInputDevices() const { + std::vector devices; + if (!Settings::values.enable_udp_controller) { + return devices; + } + for (std::size_t client = 0; client < clients.size(); client++) { + if (clients[client].active != 1) { + continue; + } + for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { + const std::size_t pad_index = client * PADS_PER_CLIENT + index; + if (!pads[pad_index].connected) { + continue; + } + const auto pad_identifier = GetPadIdentifier(pad_index); + Common::ParamPackage identifier{}; + identifier.Set("engine", GetEngineName()); + identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad)); + identifier.Set("guid", pad_identifier.guid.RawString()); + identifier.Set("port", static_cast(pad_identifier.port)); + identifier.Set("pad", static_cast(pad_identifier.pad)); + devices.emplace_back(identifier); + } + } + return devices; +} + +ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) { + // This list excludes any button that can't be really mapped + static constexpr std::array, 20> + switch_to_dsu_button = { + std::pair{Settings::NativeButton::A, PadButton::Circle}, + {Settings::NativeButton::B, PadButton::Cross}, + {Settings::NativeButton::X, PadButton::Triangle}, + {Settings::NativeButton::Y, PadButton::Square}, + {Settings::NativeButton::Start, PadButton::Options}, + {Settings::NativeButton::Select, PadButton::Share}, + {Settings::NativeButton::DLeft, PadButton::Left}, + {Settings::NativeButton::DUp, PadButton::Up}, + {Settings::NativeButton::DRight, PadButton::Right}, + {Settings::NativeButton::DDown, PadButton::Down}, + {Settings::NativeButton::L, PadButton::L1}, + {Settings::NativeButton::R, PadButton::R1}, + {Settings::NativeButton::ZL, PadButton::L2}, + {Settings::NativeButton::ZR, PadButton::R2}, + {Settings::NativeButton::ZL, PadButton::L2}, + {Settings::NativeButton::ZR, PadButton::R2}, + {Settings::NativeButton::Home, PadButton::Home}, + }; + if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { + return {}; + } + + ButtonMapping mapping{}; + for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) { + Common::ParamPackage button_params{}; + button_params.Set("engine", GetEngineName()); + button_params.Set("guid", params.Get("guid", "")); + button_params.Set("port", params.Get("port", 0)); + button_params.Set("pad", params.Get("pad", 0)); + button_params.Set("button", static_cast(dsu_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + return mapping; +} + +AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { + return {}; + } + + AnalogMapping mapping = {}; + Common::ParamPackage left_analog_params; + left_analog_params.Set("engine", GetEngineName()); + left_analog_params.Set("guid", params.Get("guid", "")); + left_analog_params.Set("port", params.Get("port", 0)); + left_analog_params.Set("pad", params.Get("pad", 0)); + left_analog_params.Set("axis_x", static_cast(PadAxes::LeftStickX)); + left_analog_params.Set("axis_y", static_cast(PadAxes::LeftStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", GetEngineName()); + right_analog_params.Set("guid", params.Get("guid", "")); + right_analog_params.Set("port", params.Get("port", 0)); + right_analog_params.Set("pad", params.Get("pad", 0)); + right_analog_params.Set("axis_x", static_cast(PadAxes::RightStickX)); + right_analog_params.Set("axis_y", static_cast(PadAxes::RightStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { + return {}; + } + + MotionMapping mapping = {}; + Common::ParamPackage left_motion_params; + left_motion_params.Set("engine", GetEngineName()); + left_motion_params.Set("guid", params.Get("guid", "")); + left_motion_params.Set("port", params.Get("port", 0)); + left_motion_params.Set("pad", params.Get("pad", 0)); + left_motion_params.Set("motion", 0); + + Common::ParamPackage right_motion_params; + right_motion_params.Set("engine", GetEngineName()); + right_motion_params.Set("guid", params.Get("guid", "")); + right_motion_params.Set("port", params.Get("port", 0)); + right_motion_params.Set("pad", params.Get("pad", 0)); + right_motion_params.Set("motion", 0); + + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params)); + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_motion_params)); + return mapping; +} + +Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const { + PadButton button = static_cast(params.Get("button", 0)); + switch (button) { + case PadButton::Left: + return Common::Input::ButtonNames::ButtonLeft; + case PadButton::Right: + return Common::Input::ButtonNames::ButtonRight; + case PadButton::Down: + return Common::Input::ButtonNames::ButtonDown; + case PadButton::Up: + return Common::Input::ButtonNames::ButtonUp; + case PadButton::L1: + return Common::Input::ButtonNames::L1; + case PadButton::L2: + return Common::Input::ButtonNames::L2; + case PadButton::L3: + return Common::Input::ButtonNames::L3; + case PadButton::R1: + return Common::Input::ButtonNames::R1; + case PadButton::R2: + return Common::Input::ButtonNames::R2; + case PadButton::R3: + return Common::Input::ButtonNames::R3; + case PadButton::Circle: + return Common::Input::ButtonNames::Circle; + case PadButton::Cross: + return Common::Input::ButtonNames::Cross; + case PadButton::Square: + return Common::Input::ButtonNames::Square; + case PadButton::Triangle: + return Common::Input::ButtonNames::Triangle; + case PadButton::Share: + return Common::Input::ButtonNames::Share; + case PadButton::Options: + return Common::Input::ButtonNames::Options; + case PadButton::Home: + return Common::Input::ButtonNames::Home; + case PadButton::Touch1: + case PadButton::Touch2: + case PadButton::TouchHardPress: + return Common::Input::ButtonNames::Touch; + default: + return Common::Input::ButtonNames::Undefined; + } +} + +Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return GetUIButtonName(params); + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("motion")) { + return Common::Input::ButtonNames::Engine; + } + + return Common::Input::ButtonNames::Invalid; +} + +bool UDPClient::IsStickInverted(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) { + return false; + } + + const auto x_axis = static_cast(params.Get("axis_x", 0)); + const auto y_axis = static_cast(params.Get("axis_y", 0)); + if (x_axis != PadAxes::LeftStickY && x_axis != PadAxes::RightStickY) { + return false; + } + if (y_axis != PadAxes::LeftStickX && y_axis != PadAxes::RightStickX) { + return false; + } + return true; +} + +void TestCommunication(const std::string& host, u16 port, + const std::function& success_callback, + const std::function& failure_callback) { + std::thread([=] { + Common::Event success_event; + SocketCallback callback{ + .version = [](Response::Version) {}, + .port_info = [](Response::PortInfo) {}, + .pad_data = [&](Response::PadData) { success_event.Set(); }, + }; + Socket socket{host, port, std::move(callback)}; + std::thread worker_thread{SocketLoop, &socket}; + const bool result = + success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10)); + socket.Stop(); + worker_thread.join(); + if (result) { + success_callback(); + } else { + failure_callback(); + } + }).detach(); +} + +CalibrationConfigurationJob::CalibrationConfigurationJob( + const std::string& host, u16 port, std::function status_callback, + std::function data_callback) { + + std::thread([=, this] { + u16 min_x{UINT16_MAX}; + u16 min_y{UINT16_MAX}; + u16 max_x{}; + u16 max_y{}; + + Status current_status{Status::Initialized}; + SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {}, + [&](Response::PadData data) { + constexpr u16 CALIBRATION_THRESHOLD = 100; + + if (current_status == Status::Initialized) { + // Receiving data means the communication is ready now + current_status = Status::Ready; + status_callback(current_status); + } + if (data.touch[0].is_active == 0) { + return; + } + LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x, + data.touch[0].y); + min_x = std::min(min_x, static_cast(data.touch[0].x)); + min_y = std::min(min_y, static_cast(data.touch[0].y)); + if (current_status == Status::Ready) { + // First touch - min data (min_x/min_y) + current_status = Status::Stage1Completed; + status_callback(current_status); + } + if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD && + data.touch[0].y - min_y > CALIBRATION_THRESHOLD) { + // Set the current position as max value and finishes + // configuration + max_x = data.touch[0].x; + max_y = data.touch[0].y; + current_status = Status::Completed; + data_callback(min_x, min_y, max_x, max_y); + status_callback(current_status); + + complete_event.Set(); + } + }}; + Socket socket{host, port, std::move(callback)}; + std::thread worker_thread{SocketLoop, &socket}; + complete_event.Wait(); + socket.Stop(); + worker_thread.join(); + }).detach(); +} + +CalibrationConfigurationJob::~CalibrationConfigurationJob() { + Stop(); +} + +void CalibrationConfigurationJob::Stop() { + complete_event.Set(); +} + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h new file mode 100644 index 000000000..cea9f579a --- /dev/null +++ b/src/input_common/drivers/udp_client.h @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "common/thread.h" +#include "input_common/input_engine.h" + +namespace InputCommon::CemuhookUDP { + +class Socket; + +namespace Response { +enum class Battery : u8; +struct PadData; +struct PortInfo; +struct TouchPad; +struct Version; +} // namespace Response + +enum class PadTouch { + Click, + Undefined, +}; + +struct UDPPadStatus { + std::string host{"127.0.0.1"}; + u16 port{26760}; + std::size_t pad_index{}; +}; + +struct DeviceStatus { + std::mutex update_mutex; + + // calibration data for scaling the device's touch area to 3ds + struct CalibrationData { + u16 min_x{}; + u16 min_y{}; + u16 max_x{}; + u16 max_y{}; + }; + std::optional touch_calibration; +}; + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class UDPClient final : public InputEngine { +public: + explicit UDPClient(std::string input_engine_); + ~UDPClient() override; + + void ReloadSockets(); + + /// Used for automapping features + std::vector GetInputDevices() const override; + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + + bool IsStickInverted(const Common::ParamPackage& params) override; + +private: + enum class PadButton { + Undefined = 0x0000, + Share = 0x0001, + L3 = 0x0002, + R3 = 0x0004, + Options = 0x0008, + Up = 0x0010, + Right = 0x0020, + Down = 0x0040, + Left = 0x0080, + L2 = 0x0100, + R2 = 0x0200, + L1 = 0x0400, + R1 = 0x0800, + Triangle = 0x1000, + Circle = 0x2000, + Cross = 0x4000, + Square = 0x8000, + Touch1 = 0x10000, + Touch2 = 0x20000, + Home = 0x40000, + TouchHardPress = 0x80000, + }; + + enum class PadAxes : u8 { + LeftStickX, + LeftStickY, + RightStickX, + RightStickY, + AnalogLeft, + AnalogDown, + AnalogRight, + AnalogUp, + AnalogSquare, + AnalogCross, + AnalogCircle, + AnalogTriangle, + AnalogR1, + AnalogL1, + AnalogR2, + AnalogL3, + AnalogR3, + Touch1X, + Touch1Y, + Touch2X, + Touch2Y, + Undefined, + }; + + struct PadData { + std::size_t pad_index{}; + bool connected{}; + DeviceStatus status; + u64 packet_sequence{}; + + std::chrono::time_point last_update; + }; + + struct ClientConnection { + ClientConnection(); + ~ClientConnection(); + Common::UUID uuid{"00000000-0000-0000-0000-00007F000001"}; + std::string host{"127.0.0.1"}; + u16 port{26760}; + s8 active{-1}; + std::unique_ptr socket; + std::thread thread; + }; + + // For shutting down, clear all data, join all threads, release usb + void Reset(); + + // Translates configuration to client number + std::size_t GetClientNumber(std::string_view host, u16 port) const; + + // Translates UDP battery level to input engine battery level + Common::Input::BatteryLevel GetBatteryLevel(Response::Battery battery) const; + + void OnVersion(Response::Version); + void OnPortInfo(Response::PortInfo); + void OnPadData(Response::PadData, std::size_t client); + void StartCommunication(std::size_t client, const std::string& host, u16 port); + PadIdentifier GetPadIdentifier(std::size_t pad_index) const; + Common::UUID GetHostUUID(const std::string& host) const; + + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + + // Allocate clients for 8 udp servers + static constexpr std::size_t MAX_UDP_CLIENTS = 8; + static constexpr std::size_t PADS_PER_CLIENT = 4; + std::array pads{}; + std::array clients{}; +}; + +/// An async job allowing configuration of the touchpad calibration. +class CalibrationConfigurationJob { +public: + enum class Status { + Initialized, + Ready, + Stage1Completed, + Completed, + }; + /** + * Constructs and starts the job with the specified parameter. + * + * @param status_callback Callback for job status updates + * @param data_callback Called when calibration data is ready + */ + explicit CalibrationConfigurationJob(const std::string& host, u16 port, + std::function status_callback, + std::function data_callback); + ~CalibrationConfigurationJob(); + void Stop(); + +private: + Common::Event complete_event; +}; + +void TestCommunication(const std::string& host, u16 port, + const std::function& success_callback, + const std::function& failure_callback); + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/drivers/virtual_gamepad.cpp b/src/input_common/drivers/virtual_gamepad.cpp new file mode 100644 index 000000000..34588bd69 --- /dev/null +++ b/src/input_common/drivers/virtual_gamepad.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "input_common/drivers/virtual_gamepad.h" + +namespace InputCommon { +constexpr std::size_t PlayerIndexCount = 10; + +VirtualGamepad::VirtualGamepad(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + for (std::size_t i = 0; i < PlayerIndexCount; i++) { + PreSetController(GetIdentifier(i)); + } +} + +void VirtualGamepad::SetButtonState(std::size_t player_index, int button_id, bool value) { + if (player_index > PlayerIndexCount) { + return; + } + const auto identifier = GetIdentifier(player_index); + SetButton(identifier, button_id, value); +} + +void VirtualGamepad::SetButtonState(std::size_t player_index, VirtualButton button_id, bool value) { + SetButtonState(player_index, static_cast(button_id), value); +} + +void VirtualGamepad::SetStickPosition(std::size_t player_index, int axis_id, float x_value, + float y_value) { + if (player_index > PlayerIndexCount) { + return; + } + const auto identifier = GetIdentifier(player_index); + SetAxis(identifier, axis_id * 2, x_value); + SetAxis(identifier, (axis_id * 2) + 1, y_value); +} + +void VirtualGamepad::SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value, + float y_value) { + SetStickPosition(player_index, static_cast(axis_id), x_value, y_value); +} + +void VirtualGamepad::ResetControllers() { + for (std::size_t i = 0; i < PlayerIndexCount; i++) { + SetStickPosition(i, VirtualStick::Left, 0.0f, 0.0f); + SetStickPosition(i, VirtualStick::Right, 0.0f, 0.0f); + + SetButtonState(i, VirtualButton::ButtonA, false); + SetButtonState(i, VirtualButton::ButtonB, false); + SetButtonState(i, VirtualButton::ButtonX, false); + SetButtonState(i, VirtualButton::ButtonY, false); + SetButtonState(i, VirtualButton::StickL, false); + SetButtonState(i, VirtualButton::StickR, false); + SetButtonState(i, VirtualButton::TriggerL, false); + SetButtonState(i, VirtualButton::TriggerR, false); + SetButtonState(i, VirtualButton::TriggerZL, false); + SetButtonState(i, VirtualButton::TriggerZR, false); + SetButtonState(i, VirtualButton::ButtonStart, false); + SetButtonState(i, VirtualButton::ButtonSelect, false); + SetButtonState(i, VirtualButton::ButtonLeft, false); + SetButtonState(i, VirtualButton::ButtonUp, false); + SetButtonState(i, VirtualButton::ButtonRight, false); + SetButtonState(i, VirtualButton::ButtonDown, false); + SetButtonState(i, VirtualButton::ButtonHome, false); + } +} + +PadIdentifier VirtualGamepad::GetIdentifier(std::size_t player_index) const { + return { + .guid = Common::UUID{}, + .port = player_index, + .pad = 0, + }; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/virtual_gamepad.h b/src/input_common/drivers/virtual_gamepad.h new file mode 100644 index 000000000..3487f67d0 --- /dev/null +++ b/src/input_common/drivers/virtual_gamepad.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A virtual controller that is always assigned to the game input + */ +class VirtualGamepad final : public InputEngine { +public: + enum class VirtualButton { + ButtonA, + ButtonB, + ButtonX, + ButtonY, + StickL, + StickR, + TriggerL, + TriggerR, + TriggerZL, + TriggerZR, + ButtonStart, + ButtonSelect, + ButtonLeft, + ButtonUp, + ButtonRight, + ButtonDown, + ButtonHome, + }; + + enum class VirtualStick { + Left = 0, + Right = 1, + }; + + explicit VirtualGamepad(std::string input_engine_); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param player_index the player number that will take this action + * @param button_id the id of the button + * @param value indicates if the button is pressed or not + */ + void SetButtonState(std::size_t player_index, int button_id, bool value); + void SetButtonState(std::size_t player_index, VirtualButton button_id, bool value); + + /** + * Sets the status of all buttons bound with the key to released + * @param player_index the player number that will take this action + * @param axis_id the id of the axis to move + * @param x_value the position of the stick in the x axis + * @param y_value the position of the stick in the y axis + */ + void SetStickPosition(std::size_t player_index, int axis_id, float x_value, float y_value); + void SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value, + float y_value); + + /// Restores all inputs into the neutral position + void ResetControllers(); + +private: + /// Returns the correct identifier corresponding to the player index + PadIdentifier GetIdentifier(std::size_t player_index) const; +}; + +} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp deleted file mode 100644 index 29e32e3fe..000000000 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright 2014 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include -#include -#include - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union -#endif -#include -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#include "common/logging/log.h" -#include "common/param_package.h" -#include "input_common/gcadapter/gc_adapter.h" - -// Workaround for older libusb versions not having libusb_init_context. -// libusb_init is deprecated and causes a compile error in newer versions. -#if !defined(LIBUSB_API_VERSION) || (LIBUSB_API_VERSION < 0x0100010A) -#define libusb_init_context(a, b, c) libusb_init(a) -#endif - -namespace GCAdapter { - -Adapter::Adapter() { - if (usb_adapter_handle != nullptr) { - return; - } - const int init_res = libusb_init_context(&libusb_ctx, nullptr, 0); - if (init_res == LIBUSB_SUCCESS) { - adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); - } else { - LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); - } -} - -Adapter::~Adapter() { - JoinThreads(); - ClearLibusbHandle(); - ResetDevices(); - - if (libusb_ctx) { - libusb_exit(libusb_ctx); - } -} - -void Adapter::AdapterInputThread() { - LOG_DEBUG(Input, "GC Adapter input thread started"); - s32 payload_size{}; - AdapterPayload adapter_payload{}; - - if (adapter_scan_thread.joinable()) { - adapter_scan_thread.join(); - } - - while (adapter_input_thread_running) { - libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), - static_cast(adapter_payload.size()), &payload_size, 16); - if (IsPayloadCorrect(adapter_payload, payload_size)) { - UpdateControllers(adapter_payload); - } - std::this_thread::yield(); - } - - if (restart_scan_thread) { - adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); - restart_scan_thread = false; - } -} - -bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { - if (payload_size != static_cast(adapter_payload.size()) || - adapter_payload[0] != LIBUSB_DT_HID) { - LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, - adapter_payload[0]); - if (++input_error_counter > 20) { - LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); - adapter_input_thread_running = false; - restart_scan_thread = true; - } - return false; - } - - input_error_counter = 0; - return true; -} - -void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { - for (std::size_t port = 0; port < pads.size(); ++port) { - const std::size_t offset = 1 + (9 * port); - const auto type = static_cast(adapter_payload[offset] >> 4); - UpdatePadType(port, type); - if (DeviceConnected(port)) { - const u8 b1 = adapter_payload[offset + 1]; - const u8 b2 = adapter_payload[offset + 2]; - UpdateStateButtons(port, b1, b2); - UpdateStateAxes(port, adapter_payload); - if (configuring) { - UpdateSettings(port); - } - } - } -} - -void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { - if (pads[port].type == pad_type) { - return; - } - // Device changed reset device and set new type - ResetDevice(port); - pads[port].type = pad_type; -} - -void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { - if (port >= pads.size()) { - return; - } - - static constexpr std::array b1_buttons{ - PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY, - PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, - }; - - static constexpr std::array b2_buttons{ - PadButton::ButtonStart, - PadButton::TriggerZ, - PadButton::TriggerR, - PadButton::TriggerL, - }; - pads[port].buttons = 0; - for (std::size_t i = 0; i < b1_buttons.size(); ++i) { - if ((b1 & (1U << i)) != 0) { - pads[port].buttons = - static_cast(pads[port].buttons | static_cast(b1_buttons[i])); - pads[port].last_button = b1_buttons[i]; - } - } - - for (std::size_t j = 0; j < b2_buttons.size(); ++j) { - if ((b2 & (1U << j)) != 0) { - pads[port].buttons = - static_cast(pads[port].buttons | static_cast(b2_buttons[j])); - pads[port].last_button = b2_buttons[j]; - } - } -} - -void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { - if (port >= pads.size()) { - return; - } - - const std::size_t offset = 1 + (9 * port); - static constexpr std::array axes{ - PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX, - PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, - }; - - for (const PadAxes axis : axes) { - const auto index = static_cast(axis); - const u8 axis_value = adapter_payload[offset + 3 + index]; - if (pads[port].axis_origin[index] == 255) { - pads[port].axis_origin[index] = axis_value; - } - pads[port].axis_values[index] = - static_cast(axis_value - pads[port].axis_origin[index]); - } -} - -void Adapter::UpdateSettings(std::size_t port) { - if (port >= pads.size()) { - return; - } - - constexpr u8 axis_threshold = 50; - GCPadStatus pad_status = {port}; - - if (pads[port].buttons != 0) { - pad_status.button = pads[port].last_button; - pad_queue.Push(pad_status); - } - - // Accounting for a threshold here to ensure an intentional press - for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) { - const s16 value = pads[port].axis_values[i]; - - if (value > axis_threshold || value < -axis_threshold) { - pad_status.axis = static_cast(i); - pad_status.axis_value = value; - pad_status.axis_threshold = axis_threshold; - pad_queue.Push(pad_status); - } - } -} - -void Adapter::AdapterScanThread() { - adapter_scan_thread_running = true; - adapter_input_thread_running = false; - if (adapter_input_thread.joinable()) { - adapter_input_thread.join(); - } - ClearLibusbHandle(); - ResetDevices(); - while (adapter_scan_thread_running && !adapter_input_thread_running) { - Setup(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } -} - -void Adapter::Setup() { - usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); - - if (usb_adapter_handle == NULL) { - return; - } - if (!CheckDeviceAccess()) { - ClearLibusbHandle(); - return; - } - - libusb_device* device = libusb_get_device(usb_adapter_handle); - - LOG_INFO(Input, "GC adapter is now connected"); - // GC Adapter found and accessible, registering it - if (GetGCEndpoint(device)) { - adapter_scan_thread_running = false; - adapter_input_thread_running = true; - input_error_counter = 0; - adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this); - } -} - -bool Adapter::CheckDeviceAccess() { - // This fixes payload problems from offbrand GCAdapters - const s32 control_transfer_error = - libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); - if (control_transfer_error < 0) { - LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); - } - - s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); - if (kernel_driver_error == 1) { - kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); - if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { - LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", - kernel_driver_error); - } - } - - if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { - libusb_close(usb_adapter_handle); - usb_adapter_handle = nullptr; - return false; - } - - const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); - if (interface_claim_error) { - LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); - libusb_close(usb_adapter_handle); - usb_adapter_handle = nullptr; - return false; - } - - return true; -} - -bool Adapter::GetGCEndpoint(libusb_device* device) { - libusb_config_descriptor* config = nullptr; - const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); - if (config_descriptor_return != LIBUSB_SUCCESS) { - LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}", - config_descriptor_return); - return false; - } - - for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { - const libusb_interface* interfaceContainer = &config->interface[ic]; - for (int i = 0; i < interfaceContainer->num_altsetting; i++) { - const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; - for (u8 e = 0; e < interface->bNumEndpoints; e++) { - const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; - if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) { - input_endpoint = endpoint->bEndpointAddress; - } else { - output_endpoint = endpoint->bEndpointAddress; - } - } - } - } - // This transfer seems to be responsible for clearing the state of the adapter - // Used to clear the "busy" state of when the device is unexpectedly unplugged - unsigned char clear_payload = 0x13; - libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, - sizeof(clear_payload), nullptr, 16); - return true; -} - -void Adapter::JoinThreads() { - restart_scan_thread = false; - adapter_input_thread_running = false; - adapter_scan_thread_running = false; - - if (adapter_scan_thread.joinable()) { - adapter_scan_thread.join(); - } - - if (adapter_input_thread.joinable()) { - adapter_input_thread.join(); - } -} - -void Adapter::ClearLibusbHandle() { - if (usb_adapter_handle) { - libusb_release_interface(usb_adapter_handle, 1); - libusb_close(usb_adapter_handle); - usb_adapter_handle = nullptr; - } -} - -void Adapter::ResetDevices() { - for (std::size_t i = 0; i < pads.size(); ++i) { - ResetDevice(i); - } -} - -void Adapter::ResetDevice(std::size_t port) { - pads[port].type = ControllerTypes::None; - pads[port].buttons = 0; - pads[port].last_button = PadButton::Undefined; - pads[port].axis_values.fill(0); - pads[port].axis_origin.fill(255); -} - -std::vector Adapter::GetInputDevices() const { - std::vector devices; - for (std::size_t port = 0; port < pads.size(); ++port) { - if (!DeviceConnected(port)) { - continue; - } - std::string name = fmt::format("Gamecube Controller {}", port + 1); - devices.emplace_back(Common::ParamPackage{ - {"class", "gcpad"}, - {"display", std::move(name)}, - {"port", std::to_string(port)}, - }); - } - return devices; -} - -bool Adapter::DeviceConnected(std::size_t port) const { - return pads[port].type != ControllerTypes::None; -} - -void Adapter::BeginConfiguration() { - pad_queue.Clear(); - configuring = true; -} - -void Adapter::EndConfiguration() { - pad_queue.Clear(); - configuring = false; -} - -Common::SPSCQueue& Adapter::GetPadQueue() { - return pad_queue; -} - -const Common::SPSCQueue& Adapter::GetPadQueue() const { - return pad_queue; -} - -GCController& Adapter::GetPadState(std::size_t port) { - return pads.at(port); -} - -const GCController& Adapter::GetPadState(std::size_t port) const { - return pads.at(port); -} - -} // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h deleted file mode 100644 index d7d18eaaa..000000000 --- a/src/input_common/gcadapter/gc_adapter.h +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2014 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "common/threadsafe_queue.h" - -struct libusb_context; -struct libusb_device; -struct libusb_device_handle; - -namespace Common { -class ParamPackage; -} - -namespace GCAdapter { - -enum class PadButton { - Undefined = 0x0000, - ButtonLeft = 0x0001, - ButtonRight = 0x0002, - ButtonDown = 0x0004, - ButtonUp = 0x0008, - TriggerZ = 0x0010, - TriggerR = 0x0020, - TriggerL = 0x0040, - ButtonA = 0x0100, - ButtonB = 0x0200, - ButtonX = 0x0400, - ButtonY = 0x0800, - ButtonStart = 0x1000, - // Below is for compatibility with "AxisButton" type - Stick = 0x2000, -}; - -enum class PadAxes : u8 { - StickX, - StickY, - SubstickX, - SubstickY, - TriggerLeft, - TriggerRight, - Undefined, -}; - -enum class ControllerTypes { - None, - Wired, - Wireless, -}; - -struct GCPadStatus { - std::size_t port{}; - - PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits - - PadAxes axis{PadAxes::Undefined}; - s16 axis_value{}; - u8 axis_threshold{50}; -}; - -struct GCController { - ControllerTypes type{}; - u16 buttons{}; - PadButton last_button{}; - std::array axis_values{}; - std::array axis_origin{}; -}; - -class Adapter { -public: - Adapter(); - ~Adapter(); - - /// Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - Common::SPSCQueue& GetPadQueue(); - const Common::SPSCQueue& GetPadQueue() const; - - GCController& GetPadState(std::size_t port); - const GCController& GetPadState(std::size_t port) const; - - /// Returns true if there is a device connected to port - bool DeviceConnected(std::size_t port) const; - - std::vector GetInputDevices() const; - -private: - using AdapterPayload = std::array; - - void UpdatePadType(std::size_t port, ControllerTypes pad_type); - void UpdateControllers(const AdapterPayload& adapter_payload); - void UpdateSettings(std::size_t port); - void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); - void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); - - void AdapterInputThread(); - - void AdapterScanThread(); - - bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); - - /// For use in initialization, querying devices to find the adapter - void Setup(); - - /// Resets status of all GC controller devices to a disconnected state - void ResetDevices(); - - /// Resets status of device connected to a disconnected state - void ResetDevice(std::size_t port); - - /// Returns true if we successfully gain access to GC Adapter - bool CheckDeviceAccess(); - - /// Captures GC Adapter endpoint address - /// Returns true if the endpoint was set correctly - bool GetGCEndpoint(libusb_device* device); - - // Join all threads - void JoinThreads(); - - // Release usb handles - void ClearLibusbHandle(); - - libusb_device_handle* usb_adapter_handle = nullptr; - std::array pads; - Common::SPSCQueue pad_queue; - - std::thread adapter_input_thread; - std::thread adapter_scan_thread; - bool adapter_input_thread_running; - bool adapter_scan_thread_running; - bool restart_scan_thread; - - libusb_context* libusb_ctx; - - u8 input_endpoint{0}; - u8 output_endpoint{0}; - u8 input_error_counter{0}; - - bool configuring{false}; -}; -} // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp deleted file mode 100644 index e0345cb55..000000000 --- a/src/input_common/gcadapter/gc_poller.cpp +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include "common/assert.h" -#include "common/threadsafe_queue.h" -#include "input_common/gcadapter/gc_adapter.h" -#include "input_common/gcadapter/gc_poller.h" - -namespace InputCommon { -namespace { -constexpr std::array gc_to_3ds_mapping{{ - GCAdapter::PadButton::ButtonA, - GCAdapter::PadButton::ButtonB, - GCAdapter::PadButton::ButtonX, - GCAdapter::PadButton::ButtonY, - GCAdapter::PadButton::ButtonUp, - GCAdapter::PadButton::ButtonDown, - GCAdapter::PadButton::ButtonLeft, - GCAdapter::PadButton::ButtonRight, - GCAdapter::PadButton::TriggerL, - GCAdapter::PadButton::TriggerR, - GCAdapter::PadButton::ButtonStart, - GCAdapter::PadButton::TriggerZ, - GCAdapter::PadButton::Undefined, - GCAdapter::PadButton::Undefined, - GCAdapter::PadButton::Undefined, - GCAdapter::PadButton::Undefined, - GCAdapter::PadButton::Undefined, -}}; -} -class GCButton final : public Input::ButtonDevice { -public: - explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) - : port(port_), button(button_), gcadapter(adapter) {} - - ~GCButton() override; - - bool GetStatus() const override { - if (gcadapter->DeviceConnected(port)) { - return (gcadapter->GetPadState(port).buttons & button) != 0; - } - return false; - } - -private: - const int port; - const int button; - GCAdapter::Adapter* gcadapter; -}; - -class GCAxisButton final : public Input::ButtonDevice { -public: - explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, - GCAdapter::Adapter* adapter) - : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), - gcadapter(adapter) {} - - bool GetStatus() const override { - if (gcadapter->DeviceConnected(port)) { - const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis); - const float axis_value = current_axis_value / 128.0f; - if (trigger_if_greater) { - return axis_value > threshold; - } - return axis_value < -threshold; - } - return false; - } - -private: - const u32 port; - const u32 axis; - float threshold; - bool trigger_if_greater; - const GCAdapter::Adapter* gcadapter; -}; - -GCButtonFactory::GCButtonFactory(std::shared_ptr adapter_) - : adapter(std::move(adapter_)) {} - -GCButton::~GCButton() = default; - -std::unique_ptr GCButtonFactory::Create(const Common::ParamPackage& params) { - const int button_id = params.Get("button", 0); - const int port = params.Get("port", 0); - - constexpr s32 PAD_STICK_ID = static_cast(GCAdapter::PadButton::Stick); - - // button is not an axis/stick button - if (button_id != PAD_STICK_ID) { - return std::make_unique(port, button_id, adapter.get()); - } - - // For Axis buttons, used by the binary sticks. - if (button_id == PAD_STICK_ID) { - const int axis = params.Get("axis", 0); - const float threshold = params.Get("threshold", 0.25f); - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction {}", direction_name); - } - return std::make_unique(port, axis, threshold, trigger_if_greater, - adapter.get()); - } - - UNREACHABLE(); - return nullptr; -} - -Common::ParamPackage GCButtonFactory::GetNextInput() { - Common::ParamPackage params; - GCAdapter::GCPadStatus pad; - auto& queue = adapter->GetPadQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - params.Set("engine", "gcpad"); - params.Set("port", static_cast(pad.port)); - if (pad.button != GCAdapter::PadButton::Undefined) { - params.Set("button", static_cast(pad.button)); - } - - // For Axis button implementation - if (pad.axis != GCAdapter::PadAxes::Undefined) { - params.Set("axis", static_cast(pad.axis)); - params.Set("button", static_cast(GCAdapter::PadButton::Stick)); - params.Set("threshold", "0.25"); - if (pad.axis_value > 0) { - params.Set("direction", "+"); - } else { - params.Set("direction", "-"); - } - break; - } - } - return params; -} - -Common::ParamPackage GCButtonFactory::GetGcTo3DSMappedButton( - int port, Settings::NativeButton::Values button) { - Common::ParamPackage params({{"engine", "gcpad"}}); - params.Set("port", port); - auto mapped_button = gc_to_3ds_mapping[static_cast(button)]; - if (mapped_button != GCAdapter::PadButton::Undefined) { - params.Set("button", static_cast(mapped_button)); - } - return params; -} - -void GCButtonFactory::Start() { - polling = true; - adapter->BeginConfiguration(); -} - -void GCButtonFactory::Stop() { - polling = false; - adapter->EndConfiguration(); -} - -class GCAnalog final : public Input::AnalogDevice { -public: - explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, float deadzone_, - const GCAdapter::Adapter* adapter) - : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {} - - float GetAxis(u32 axis) const { - if (gcadapter->DeviceConnected(port)) { - std::lock_guard lock{mutex}; - const auto axis_value = - static_cast(gcadapter->GetPadState(port).axis_values.at(axis)); - return (axis_value) / 50.0f; - } - return 0.0f; - } - - std::pair GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { - float x = GetAxis(analog_axis_x); - float y = GetAxis(analog_axis_y); - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return {x, y}; - } - - std::tuple GetStatus() const override { - const auto [x, y] = GetAnalog(axis_x, axis_y); - const float r = std::sqrt((x * x) + (y * y)); - if (r > deadzone) { - return {x / r * (r - deadzone) / (1 - deadzone), - y / r * (r - deadzone) / (1 - deadzone)}; - } - return {0.0f, 0.0f}; - } - -private: - const u32 port; - const u32 axis_x; - const u32 axis_y; - const float deadzone; - const GCAdapter::Adapter* gcadapter; - mutable std::mutex mutex; -}; - -/// An analog device factory that creates analog devices from GC Adapter -GCAnalogFactory::GCAnalogFactory(std::shared_ptr adapter_) - : adapter(std::move(adapter_)) {} - -/** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ -std::unique_ptr GCAnalogFactory::Create(const Common::ParamPackage& params) { - const auto port = static_cast(params.Get("port", 0)); - const auto axis_x = static_cast(params.Get("axis_x", 0)); - const auto axis_y = static_cast(params.Get("axis_y", 1)); - const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - - return std::make_unique(port, axis_x, axis_y, deadzone, adapter.get()); -} - -void GCAnalogFactory::Start() { - polling = true; - adapter->BeginConfiguration(); -} - -void GCAnalogFactory::Stop() { - polling = false; - adapter->EndConfiguration(); -} - -Common::ParamPackage GCAnalogFactory::GetNextInput() { - GCAdapter::GCPadStatus pad; - Common::ParamPackage params; - auto& queue = adapter->GetPadQueue(); - while (queue.Pop(pad)) { - if (pad.button != GCAdapter::PadButton::Undefined) { - params.Set("engine", "gcpad"); - params.Set("port", static_cast(pad.port)); - params.Set("button", static_cast(pad.button)); - return params; - } - if (pad.axis == GCAdapter::PadAxes::Undefined || - std::abs(static_cast(pad.axis_value) / 128.0f) < 0.1f) { - continue; - } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second input event. The axes also must be from the same joystick. - const u8 axis = static_cast(pad.axis); - if (axis == 0 || axis == 1) { - analog_x_axis = 0; - analog_y_axis = 1; - controller_number = static_cast(pad.port); - break; - } - if (axis == 2 || axis == 3) { - analog_x_axis = 2; - analog_y_axis = 3; - controller_number = static_cast(pad.port); - break; - } - - if (analog_x_axis == -1) { - analog_x_axis = axis; - controller_number = static_cast(pad.port); - } else if (analog_y_axis == -1 && analog_x_axis != axis && - controller_number == static_cast(pad.port)) { - analog_y_axis = axis; - break; - } - } - if (analog_x_axis != -1 && analog_y_axis != -1) { - params.Set("engine", "gcpad"); - params.Set("port", controller_number); - params.Set("axis_x", analog_x_axis); - params.Set("axis_y", analog_y_axis); - analog_x_axis = -1; - analog_y_axis = -1; - controller_number = -1; - return params; - } - return params; -} - -Common::ParamPackage GCAnalogFactory::GetGcTo3DSMappedAnalog( - int port, Settings::NativeAnalog::Values analog) { - int x_axis, y_axis; - Common::ParamPackage params({{"engine", "gcpad"}}); - params.Set("port", port); - if (analog == Settings::NativeAnalog::Values::CirclePad) { - x_axis = static_cast(GCAdapter::PadAxes::StickX); - y_axis = static_cast(GCAdapter::PadAxes::StickY); - } else if (analog == Settings::NativeAnalog::Values::CStick) { - x_axis = static_cast(GCAdapter::PadAxes::SubstickX); - y_axis = static_cast(GCAdapter::PadAxes::SubstickY); - } else { - LOG_WARNING(Input, "analog value out of range {}", analog); - return {{}}; - } - params.Set("axis_x", x_axis); - params.Set("axis_y", y_axis); - return params; -} - -} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h deleted file mode 100644 index 297749c9f..000000000 --- a/src/input_common/gcadapter/gc_poller.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "common/settings.h" -#include "core/frontend/input.h" -#include "input_common/gcadapter/gc_adapter.h" -#include "input_common/main.h" - -namespace InputCommon { - -/** - * A button device factory representing a gcpad. It receives gcpad events and forward them - * to all button devices it created. - */ -class GCButtonFactory final : public Input::Factory, - public Polling::DevicePoller { -public: -public: - explicit GCButtonFactory(std::shared_ptr adapter_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() override; - Common::ParamPackage GetGcTo3DSMappedButton(int port, Settings::NativeButton::Values button); - - /// For device input configuration/polling - void Start() override; - void Stop() override; - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr adapter; - bool polling{false}; -}; - -/// An analog device factory that creates analog devices from GC Adapter -class GCAnalogFactory final : public Input::Factory, - public Polling::DevicePoller { -public: - explicit GCAnalogFactory(std::shared_ptr adapter_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() override; - Common::ParamPackage GetGcTo3DSMappedAnalog(int port, Settings::NativeAnalog::Values analog); - - /// For device input configuration/polling - void Start() override; - void Stop() override; - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr adapter; - int analog_x_axis{-1}; - int analog_y_axis{-1}; - int controller_number{-1}; - bool polling{false}; -}; - -} // namespace InputCommon diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp new file mode 100644 index 000000000..3711d75ed --- /dev/null +++ b/src/input_common/helpers/stick_from_buttons.cpp @@ -0,0 +1,334 @@ +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/math_util.h" +#include "common/settings.h" +#include "input_common/helpers/stick_from_buttons.h" + +namespace InputCommon { + +class Stick final : public Common::Input::InputDevice { +public: + using Button = std::unique_ptr; + + Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, + float modifier_scale_, float modifier_angle_) + : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), + right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), + modifier_angle(modifier_angle_) { + up->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateUpButtonStatus(callback_); + }, + }); + down->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateDownButtonStatus(callback_); + }, + }); + left->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateLeftButtonStatus(callback_); + }, + }); + right->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateRightButtonStatus(callback_); + }, + }); + modifier->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateModButtonStatus(callback_); + }, + }); + last_x_axis_value = 0.0f; + last_y_axis_value = 0.0f; + } + + bool IsAngleGreater(float old_angle, float new_angle) const { + constexpr float TAU = Common::PI * 2.0f; + // Use wider angle to ease the transition. + constexpr float aperture = TAU * 0.15f; + const float top_limit = new_angle + aperture; + return (old_angle > new_angle && old_angle <= top_limit) || + (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); + } + + bool IsAngleSmaller(float old_angle, float new_angle) const { + constexpr float TAU = Common::PI * 2.0f; + // Use wider angle to ease the transition. + constexpr float aperture = TAU * 0.15f; + const float bottom_limit = new_angle - aperture; + return (old_angle >= bottom_limit && old_angle < new_angle) || + (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); + } + + float GetAngle(std::chrono::time_point now) const { + constexpr float TAU = Common::PI * 2.0f; + float new_angle = angle; + + auto time_difference = static_cast( + std::chrono::duration_cast(now - last_update).count()); + time_difference /= 1000.0f * 1000.0f; + if (time_difference > 0.5f) { + time_difference = 0.5f; + } + + if (IsAngleGreater(new_angle, goal_angle)) { + new_angle -= modifier_angle * time_difference; + if (new_angle < 0) { + new_angle += TAU; + } + if (!IsAngleGreater(new_angle, goal_angle)) { + return goal_angle; + } + } else if (IsAngleSmaller(new_angle, goal_angle)) { + new_angle += modifier_angle * time_difference; + if (new_angle >= TAU) { + new_angle -= TAU; + } + if (!IsAngleSmaller(new_angle, goal_angle)) { + return goal_angle; + } + } else { + return goal_angle; + } + return new_angle; + } + + void SetGoalAngle(bool r, bool l, bool u, bool d) { + // Move to the right + if (r && !u && !d) { + goal_angle = 0.0f; + } + + // Move to the upper right + if (r && u && !d) { + goal_angle = Common::PI * 0.25f; + } + + // Move up + if (u && !l && !r) { + goal_angle = Common::PI * 0.5f; + } + + // Move to the upper left + if (l && u && !d) { + goal_angle = Common::PI * 0.75f; + } + + // Move to the left + if (l && !u && !d) { + goal_angle = Common::PI; + } + + // Move to the bottom left + if (l && !u && d) { + goal_angle = Common::PI * 1.25f; + } + + // Move down + if (d && !l && !r) { + goal_angle = Common::PI * 1.5f; + } + + // Move to the bottom right + if (r && !u && d) { + goal_angle = Common::PI * 1.75f; + } + } + + void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) { + up_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateDownButtonStatus(const Common::Input::CallbackStatus& button_callback) { + down_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateLeftButtonStatus(const Common::Input::CallbackStatus& button_callback) { + left_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateRightButtonStatus(const Common::Input::CallbackStatus& button_callback) { + right_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateModButtonStatus(const Common::Input::CallbackStatus& button_callback) { + const auto& new_status = button_callback.button_status; + const bool new_button_value = new_status.inverted ? !new_status.value : new_status.value; + modifier_status.toggle = new_status.toggle; + + // Update button status with current + if (!modifier_status.toggle) { + modifier_status.locked = false; + if (modifier_status.value != new_button_value) { + modifier_status.value = new_button_value; + } + } else { + // Toggle button and lock status + if (new_button_value && !modifier_status.locked) { + modifier_status.locked = true; + modifier_status.value = !modifier_status.value; + } + + // Unlock button ready for next press + if (!new_button_value && modifier_status.locked) { + modifier_status.locked = false; + } + } + + UpdateStatus(); + } + + void UpdateStatus() { + const float coef = modifier_status.value ? modifier_scale : 1.0f; + + bool r = right_status; + bool l = left_status; + bool u = up_status; + bool d = down_status; + + // Eliminate contradictory movements + if (r && l) { + r = false; + l = false; + } + if (u && d) { + u = false; + d = false; + } + + // Move if a key is pressed + if (r || l || u || d) { + amplitude = coef; + } else { + amplitude = 0; + } + + const auto now = std::chrono::steady_clock::now(); + const auto time_difference = static_cast( + std::chrono::duration_cast(now - last_update).count()); + + if (time_difference < 10) { + // Disable analog mode if inputs are too fast + SetGoalAngle(r, l, u, d); + angle = goal_angle; + } else { + angle = GetAngle(now); + SetGoalAngle(r, l, u, d); + } + + last_update = now; + Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + last_x_axis_value = status.stick_status.x.raw_value; + last_y_axis_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + void ForceUpdate() override { + up->ForceUpdate(); + down->ForceUpdate(); + left->ForceUpdate(); + right->ForceUpdate(); + modifier->ForceUpdate(); + } + + void SoftUpdate() override { + Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + if (last_x_axis_value == status.stick_status.x.raw_value && + last_y_axis_value == status.stick_status.y.raw_value) { + return; + } + last_x_axis_value = status.stick_status.x.raw_value; + last_y_axis_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + Common::Input::StickStatus GetStatus() const { + Common::Input::StickStatus status{}; + status.x.properties = properties; + status.y.properties = properties; + constexpr float SQRT_HALF = 0.707106781f; + int x = 0, y = 0; + if (right_status) { + ++x; + } + if (left_status) { + --x; + } + if (up_status) { + ++y; + } + if (down_status) { + --y; + } + const float coef = modifier_status.value ? modifier_scale : 1.0f; + status.x.raw_value = static_cast(x) * coef * (y == 0 ? 1.0f : SQRT_HALF); + status.y.raw_value = static_cast(y) * coef * (x == 0 ? 1.0f : SQRT_HALF); + return status; + } + +private: + static constexpr Common::Input::AnalogProperties properties{ + .deadzone = 0.0f, + .range = 1.0f, + .threshold = 0.5f, + .offset = 0.0f, + .inverted = false, + .toggle = false, + }; + + Button up; + Button down; + Button left; + Button right; + Button modifier; + float modifier_scale{}; + float modifier_angle{}; + float angle{}; + float goal_angle{}; + float amplitude{}; + bool up_status{}; + bool down_status{}; + bool left_status{}; + bool right_status{}; + float last_x_axis_value{}; + float last_y_axis_value{}; + Common::Input::ButtonStatus modifier_status{}; + std::chrono::time_point last_update; +}; + +std::unique_ptr StickFromButton::Create( + const Common::ParamPackage& params) { + const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); + auto up = Common::Input::CreateInputDeviceFromString(params.Get("up", null_engine)); + auto down = Common::Input::CreateInputDeviceFromString(params.Get("down", null_engine)); + auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine)); + auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine)); + auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine)); + auto modifier_scale = params.Get("modifier_scale", 0.5f); + auto modifier_angle = params.Get("modifier_angle", 5.5f); + return std::make_unique(std::move(up), std::move(down), std::move(left), + std::move(right), std::move(modifier), modifier_scale, + modifier_angle); +} + +} // namespace InputCommon diff --git a/src/input_common/analog_from_button.h b/src/input_common/helpers/stick_from_buttons.h old mode 100755 new mode 100644 similarity index 72% rename from src/input_common/analog_from_button.h rename to src/input_common/helpers/stick_from_buttons.h index bbd583dd9..e8d865743 --- a/src/input_common/analog_from_button.h +++ b/src/input_common/helpers/stick_from_buttons.h @@ -1,11 +1,9 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include -#include "core/frontend/input.h" +#include "common/input.h" namespace InputCommon { @@ -13,7 +11,7 @@ namespace InputCommon { * An analog device factory that takes direction button devices and combines them into a analog * device. */ -class AnalogFromButton final : public Input::Factory { +class StickFromButton final : public Common::Input::Factory { public: /** * Creates an analog device from direction button devices @@ -25,7 +23,7 @@ public: * - "modifier": a serialized ParamPackage for creating a button device as the modifier * - "modifier_scale": a float for the multiplier the modifier gives to the position */ - std::unique_ptr Create(const Common::ParamPackage& params) override; + std::unique_ptr Create(const Common::ParamPackage& params) override; }; } // namespace InputCommon diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp new file mode 100644 index 000000000..e064b13d9 --- /dev/null +++ b/src/input_common/helpers/touch_from_buttons.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2020 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/settings.h" +#include "input_common/helpers/touch_from_buttons.h" + +namespace InputCommon { + +class TouchFromButtonDevice final : public Common::Input::InputDevice { +public: + using Button = std::unique_ptr; + TouchFromButtonDevice(Button button_, float x_, float y_) + : button(std::move(button_)), x(x_), y(y_) { + last_button_value = false; + button->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateButtonStatus(callback_); + }, + }); + button->ForceUpdate(); + } + + void ForceUpdate() override { + button->ForceUpdate(); + } + + Common::Input::TouchStatus GetStatus(bool pressed) const { + const Common::Input::ButtonStatus button_status{ + .value = pressed, + }; + Common::Input::TouchStatus status{ + .pressed = button_status, + .x = {}, + .y = {}, + }; + status.x.properties = properties; + status.y.properties = properties; + + if (!pressed) { + return status; + } + + status.x.raw_value = x; + status.y.raw_value = y; + return status; + } + + void UpdateButtonStatus(const Common::Input::CallbackStatus& button_callback) { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Touch, + .touch_status = GetStatus(button_callback.button_status.value), + }; + if (last_button_value != button_callback.button_status.value) { + last_button_value = button_callback.button_status.value; + TriggerOnChange(status); + } + } + +private: + static constexpr Common::Input::AnalogProperties properties{ + .deadzone = 0.0f, + .range = 1.0f, + .threshold = 0.5f, + .offset = 0.0f, + .inverted = false, + .toggle = false, + }; + + Button button; + bool last_button_value; + const float x; + const float y; +}; + +std::unique_ptr TouchFromButton::Create( + const Common::ParamPackage& params) { + const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); + auto button = Common::Input::CreateInputDeviceFromString(params.Get("button", null_engine)); + const float x = params.Get("x", 0.0f) / 1280.0f; + const float y = params.Get("y", 0.0f) / 720.0f; + return std::make_unique(std::move(button), x, y); +} + +} // namespace InputCommon diff --git a/src/input_common/helpers/touch_from_buttons.h b/src/input_common/helpers/touch_from_buttons.h new file mode 100644 index 000000000..c6cb3ab3c --- /dev/null +++ b/src/input_common/helpers/touch_from_buttons.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2020 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/input.h" + +namespace InputCommon { + +/** + * A touch device factory that takes a list of button devices and combines them into a touch device. + */ +class TouchFromButton final : public Common::Input::Factory { +public: + /** + * Creates a touch device from a list of button devices + */ + std::unique_ptr Create(const Common::ParamPackage& params) override; +}; + +} // namespace InputCommon diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/helpers/udp_protocol.cpp similarity index 93% rename from src/input_common/udp/protocol.cpp rename to src/input_common/helpers/udp_protocol.cpp index 5e50bd612..994380d21 100644 --- a/src/input_common/udp/protocol.cpp +++ b/src/input_common/helpers/udp_protocol.cpp @@ -1,11 +1,10 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include #include #include "common/logging/log.h" -#include "input_common/udp/protocol.h" +#include "input_common/helpers/udp_protocol.h" namespace InputCommon::CemuhookUDP { diff --git a/src/input_common/udp/protocol.h b/src/input_common/helpers/udp_protocol.h similarity index 78% rename from src/input_common/udp/protocol.h rename to src/input_common/helpers/udp_protocol.h index 3ba4d1fc8..d9643ffe0 100644 --- a/src/input_common/udp/protocol.h +++ b/src/input_common/helpers/udp_protocol.h @@ -1,14 +1,23 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used +#endif + #include -#include "common/bit_field.h" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include "common/swap.h" namespace InputCommon::CemuhookUDP { @@ -52,12 +61,30 @@ struct Message { template constexpr Type GetMessageType(); +template +Message CreateMessage(const u32 magic, const T data, const u32 sender_id) { + boost::crc_32_type crc; + Header header{ + magic, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, sender_id, GetMessageType(), + }; + Message message{header, data}; + crc.process_bytes(&message, sizeof(Message)); + message.header.crc = crc.checksum(); + return message; +} + namespace Request { +enum RegisterFlags : u8 { + AllPads, + PadID, + PadMACAdddress, +}; + struct Version {}; /** * Requests the server to send information about what controllers are plugged into the ports - * In citra's case, we only have one controller, so for simplicity's sake, we can just send a + * In yuzu's case, we only have one controller, so for simplicity's sake, we can just send a * request explicitly for the first controller port and leave it at that. In the future it would be * nice to make this configurable */ @@ -75,13 +102,8 @@ static_assert(std::is_trivially_copyable_v, * timeout seems to be 5 seconds. */ struct PadData { - enum class Flags : u8 { - AllPorts, - Id, - Mac, - }; /// Determines which method will be used as a look up for the controller - Flags flags{}; + RegisterFlags flags{}; /// Index of the port of the controller to retrieve data about u8 port_id{}; /// Mac address of the controller to retrieve data about @@ -93,24 +115,47 @@ static_assert(std::is_trivially_copyable_v, /** * Creates a message with the proper header data that can be sent to the server. - * @param T data Request body to send + * @param data Request body to send * @param client_id ID of the udp client (usually not checked on the server) */ template Message Create(const T data, const u32 client_id = 0) { - boost::crc_32_type crc; - Header header{ - CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType(), - }; - Message message{header, data}; - crc.process_bytes(&message, sizeof(Message)); - message.header.crc = crc.checksum(); - return message; + return CreateMessage(CLIENT_MAGIC, data, client_id); } } // namespace Request namespace Response { +enum class ConnectionType : u8 { + None, + Usb, + Bluetooth, +}; + +enum class State : u8 { + Disconnected, + Reserved, + Connected, +}; + +enum class Model : u8 { + None, + PartialGyro, + FullGyro, + Generic, +}; + +enum class Battery : u8 { + None = 0x00, + Dying = 0x01, + Low = 0x02, + Medium = 0x03, + High = 0x04, + Full = 0x05, + Charging = 0xEE, + Charged = 0xEF, +}; + struct Version { u16_le version{}; }; @@ -120,17 +165,25 @@ static_assert(std::is_trivially_copyable_v, struct PortInfo { u8 id{}; - u8 state{}; - u8 model{}; - u8 connection_type{}; + State state{}; + Model model{}; + ConnectionType connection_type{}; MacAddress mac; - u8 battery{}; + Battery battery{}; u8 is_pad_active{}; }; static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); static_assert(std::is_trivially_copyable_v, "UDP Response PortInfo is not trivially copyable"); +struct TouchPad { + u8 is_active{}; + u8 id{}; + u16_le x{}; + u16_le y{}; +}; +static_assert(sizeof(TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); + #pragma pack(push, 1) struct PadData { PortInfo info{}; @@ -167,26 +220,21 @@ struct PadData { u8 right_stick_y{}; struct AnalogButton { - u8 button_8{}; - u8 button_7{}; - u8 button_6{}; - u8 button_5{}; - u8 button_12{}; - u8 button_11{}; - u8 button_10{}; - u8 button_9{}; - u8 button_16{}; - u8 button_15{}; - u8 button_14{}; - u8 button_13{}; + u8 button_dpad_left_analog{}; + u8 button_dpad_down_analog{}; + u8 button_dpad_right_analog{}; + u8 button_dpad_up_analog{}; + u8 button_square_analog{}; + u8 button_cross_analog{}; + u8 button_circle_analog{}; + u8 button_triangle_analog{}; + u8 button_r1_analog{}; + u8 button_l1_analog{}; + u8 trigger_r2{}; + u8 trigger_l2{}; } analog_button; - struct TouchPad { - u8 is_active{}; - u8 id{}; - u16_le x{}; - u16_le y{}; - } touch_1, touch_2; + std::array touch; u64_le motion_timestamp; @@ -213,7 +261,6 @@ static_assert(sizeof(Message) == MAX_PACKET_SIZE, static_assert(sizeof(PadData::AnalogButton) == 12, "UDP Response AnalogButton struct has wrong size "); -static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); static_assert(sizeof(PadData::Accelerometer) == 12, "UDP Response Accelerometer struct has wrong size "); static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size "); diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp new file mode 100644 index 000000000..4ba074183 --- /dev/null +++ b/src/input_common/input_engine.cpp @@ -0,0 +1,331 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/input_engine.h" + +namespace InputCommon { + +void InputEngine::PreSetController(const PadIdentifier& identifier) { + std::scoped_lock lock{mutex}; + controller_list.try_emplace(identifier); +} + +void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + controller.buttons.try_emplace(button, false); +} + +void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + controller.hat_buttons.try_emplace(button, u8{0}); +} + +void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + controller.axes.try_emplace(axis, 0.0f); +} + +void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + controller.motions.try_emplace(motion); +} + +void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) { + { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.buttons.insert_or_assign(button, value); + } + } + TriggerOnButtonChange(identifier, button, value); +} + +void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) { + { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.hat_buttons.insert_or_assign(button, value); + } + } + TriggerOnHatButtonChange(identifier, button, value); +} + +void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) { + { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.axes.insert_or_assign(axis, value); + } + } + TriggerOnAxisChange(identifier, axis, value); +} + +void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) { + { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.motions.insert_or_assign(motion, value); + } + } + TriggerOnMotionChange(identifier, motion, value); +} + +bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const { + std::scoped_lock lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), + identifier.pad, identifier.port); + return false; + } + const ControllerData& controller = controller_iter->second; + const auto button_iter = controller.buttons.find(button); + if (button_iter == controller.buttons.cend()) { + LOG_ERROR(Input, "Invalid button {}", button); + return false; + } + return button_iter->second; +} + +bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const { + std::scoped_lock lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), + identifier.pad, identifier.port); + return false; + } + const ControllerData& controller = controller_iter->second; + const auto hat_iter = controller.hat_buttons.find(button); + if (hat_iter == controller.hat_buttons.cend()) { + LOG_ERROR(Input, "Invalid hat button {}", button); + return false; + } + return (hat_iter->second & direction) != 0; +} + +f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const { + std::scoped_lock lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), + identifier.pad, identifier.port); + return 0.0f; + } + const ControllerData& controller = controller_iter->second; + const auto axis_iter = controller.axes.find(axis); + if (axis_iter == controller.axes.cend()) { + LOG_ERROR(Input, "Invalid axis {}", axis); + return 0.0f; + } + return axis_iter->second; +} + +BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const { + std::scoped_lock lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), + identifier.pad, identifier.port); + return {}; + } + const ControllerData& controller = controller_iter->second; + return controller.motions.at(motion); +} + +void InputEngine::ResetButtonState() { + for (const auto& controller : controller_list) { + for (const auto& button : controller.second.buttons) { + SetButton(controller.first, button.first, false); + } + for (const auto& button : controller.second.hat_buttons) { + SetHatButton(controller.first, button.first, 0); + } + } +} + +void InputEngine::ResetAnalogState() { + for (const auto& controller : controller_list) { + for (const auto& axis : controller.second.axes) { + SetAxis(controller.first, axis.first, 0.0); + } + } +} + +void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) { + std::scoped_lock lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + + PreSetButton(identifier, button); + if (value == GetButton(identifier, button)) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Button, + .index = button, + .button_value = value, + }); +} + +void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) { + std::scoped_lock lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + for (std::size_t index = 1; index < 0xff; index <<= 1) { + bool button_value = (value & index) != 0; + if (button_value == GetHatButton(identifier, button, static_cast(index))) { + continue; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::HatButton, + .index = button, + .hat_name = GetHatButtonName(static_cast(index)), + }); + } +} + +void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) { + std::scoped_lock lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Analog, + .index = axis, + .axis_value = value, + }); +} + +void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion, + const BasicMotion& value) { + std::scoped_lock lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + bool is_active = false; + if (std::abs(value.accel_x) > 1.5f || std::abs(value.accel_y) > 1.5f || + std::abs(value.accel_z) > 1.5f) { + is_active = true; + } + if (std::abs(value.gyro_x) > 0.6f || std::abs(value.gyro_y) > 0.6f || + std::abs(value.gyro_z) > 0.6f) { + is_active = true; + } + if (!is_active) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Motion, + .index = motion, + .motion_value = value, + }); +} + +bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier, + const PadIdentifier& identifier, EngineInputType type, + int index) const { + if (input_identifier.type != type) { + return false; + } + if (input_identifier.index != index) { + return false; + } + if (input_identifier.identifier != identifier) { + return false; + } + return true; +} + +void InputEngine::BeginConfiguration() { + configuring = true; +} + +void InputEngine::EndConfiguration() { + configuring = false; +} + +const std::string& InputEngine::GetEngineName() const { + return input_engine; +} + +int InputEngine::SetCallback(InputIdentifier input_identifier) { + std::scoped_lock lock{mutex_callback}; + callback_list.insert_or_assign(last_callback_key, std::move(input_identifier)); + return last_callback_key++; +} + +void InputEngine::SetMappingCallback(MappingCallback callback) { + std::scoped_lock lock{mutex_callback}; + mapping_callback = std::move(callback); +} + +void InputEngine::DeleteCallback(int key) { + std::scoped_lock lock{mutex_callback}; + 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 InputCommon diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h new file mode 100644 index 000000000..2ece9c7ca --- /dev/null +++ b/src/input_common/input_engine.h @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/common_types.h" +#include "common/input.h" +#include "common/param_package.h" +#include "common/uuid.h" +#include "input_common/main.h" + +// Pad Identifier of data source +struct PadIdentifier { + Common::UUID guid{}; + std::size_t port{}; + std::size_t pad{}; + + friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default; +}; + +// Basic motion data containing data from the sensors and a timestamp in microseconds +struct BasicMotion { + float gyro_x{}; + float gyro_y{}; + float gyro_z{}; + float accel_x{}; + float accel_y{}; + float accel_z{}; + u64 delta_timestamp{}; +}; + +// Types of input that are stored in the engine +enum class EngineInputType { + None, + Analog, + Button, + HatButton, + Motion, +}; + +namespace std { +// Hash used to create lists from PadIdentifier data +template <> +struct hash { + size_t operator()(const PadIdentifier& pad_id) const noexcept { + u64 hash_value = pad_id.guid.Hash(); + hash_value ^= (static_cast(pad_id.port) << 32); + hash_value ^= static_cast(pad_id.pad); + return static_cast(hash_value); + } +}; + +} // namespace std + +namespace InputCommon { + +// Data from the engine and device needed for creating a ParamPackage +struct MappingData { + std::string engine{}; + PadIdentifier pad{}; + EngineInputType type{}; + int index{}; + bool button_value{}; + std::string hat_name{}; + f32 axis_value{}; + BasicMotion motion_value{}; +}; + +// Triggered if data changed on the controller +struct UpdateCallback { + std::function on_change; +}; + +// Triggered if data changed on the controller and the engine is on configuring mode +struct MappingCallback { + std::function on_data; +}; + +// Input Identifier of data source +struct InputIdentifier { + PadIdentifier identifier; + EngineInputType type; + int index; + UpdateCallback callback; +}; + +class InputEngine { +public: + explicit InputEngine(std::string input_engine_) : input_engine{std::move(input_engine_)} {} + + virtual ~InputEngine() = default; + + // Enable configuring mode for mapping + void BeginConfiguration(); + + // Disable configuring mode for mapping + void EndConfiguration(); + + // Sets polling mode to a controller + virtual Common::Input::PollingError SetPollingMode( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::PollingMode polling_mode) { + return Common::Input::PollingError::NotSupported; + } + + // Returns the engine name + [[nodiscard]] const std::string& GetEngineName() const; + + /// Used for automapping features + virtual std::vector GetInputDevices() const { + return {}; + } + + /// Retrieves the button mappings for the given device + virtual ButtonMapping GetButtonMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + } + + /// Retrieves the analog mappings for the given device + virtual AnalogMapping GetAnalogMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + } + + /// Retrieves the motion mappings for the given device + virtual MotionMapping GetMotionMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + } + + /// Retrieves the name of the given input. + virtual Common::Input::ButtonNames GetUIName( + [[maybe_unused]] const Common::ParamPackage& params) const { + return Common::Input::ButtonNames::Engine; + } + + /// Retrieves the index number of the given hat button direction + virtual u8 GetHatButtonId([[maybe_unused]] const std::string& direction_name) const { + return 0; + } + + /// Returns true if axis of a stick aren't mapped in the correct direction + virtual bool IsStickInverted([[maybe_unused]] const Common::ParamPackage& params) { + return false; + } + + void PreSetController(const PadIdentifier& identifier); + void PreSetButton(const PadIdentifier& identifier, int button); + void PreSetHatButton(const PadIdentifier& identifier, int button); + void PreSetAxis(const PadIdentifier& identifier, int axis); + void PreSetMotion(const PadIdentifier& identifier, int motion); + void ResetButtonState(); + void ResetAnalogState(); + + bool GetButton(const PadIdentifier& identifier, int button) const; + bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const; + f32 GetAxis(const PadIdentifier& identifier, int axis) const; + BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; + + int SetCallback(InputIdentifier input_identifier); + void SetMappingCallback(MappingCallback callback); + void DeleteCallback(int key); + +protected: + void SetButton(const PadIdentifier& identifier, int button, bool value); + void SetHatButton(const PadIdentifier& identifier, int button, u8 value); + void SetAxis(const PadIdentifier& identifier, int axis, f32 value); + void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); + + virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const { + return "Unknown"; + } + +private: + struct ControllerData { + std::unordered_map buttons; + std::unordered_map hat_buttons; + std::unordered_map axes; + std::unordered_map motions; + }; + + void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value); + void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value); + void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value); + void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, + const BasicMotion& value); + + bool IsInputIdentifierEqual(const InputIdentifier& input_identifier, + const PadIdentifier& identifier, EngineInputType type, + int index) const; + + mutable std::mutex mutex; + mutable std::mutex mutex_callback; + bool configuring{false}; + const std::string input_engine; + int last_callback_key = 0; + std::unordered_map controller_list; + std::unordered_map callback_list; + MappingCallback mapping_callback; +}; + +} // namespace InputCommon diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp new file mode 100644 index 000000000..edd5287c1 --- /dev/null +++ b/src/input_common/input_mapping.cpp @@ -0,0 +1,212 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/settings.h" +#include "input_common/input_engine.h" +#include "input_common/input_mapping.h" + +namespace InputCommon { + +MappingFactory::MappingFactory() = default; + +void MappingFactory::BeginMapping(Polling::InputType type) { + is_enabled = true; + input_type = type; + input_queue.Clear(); + first_axis = -1; + second_axis = -1; +} + +Common::ParamPackage MappingFactory::GetNextInput() { + Common::ParamPackage input; + input_queue.Pop(input); + return input; +} + +void MappingFactory::RegisterInput(const MappingData& data) { + if (!is_enabled) { + return; + } + if (!IsDriverValid(data)) { + return; + } + + switch (input_type) { + case Polling::InputType::Button: + RegisterButton(data); + return; + case Polling::InputType::Stick: + RegisterStick(data); + return; + case Polling::InputType::Motion: + RegisterMotion(data); + return; + default: + return; + } +} + +void MappingFactory::StopMapping() { + is_enabled = false; + input_type = Polling::InputType::None; + input_queue.Clear(); +} + +void MappingFactory::RegisterButton(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid.IsValid()) { + new_input.Set("guid", data.pad.guid.RawString()); + } + new_input.Set("port", static_cast(data.pad.port)); + new_input.Set("pad", static_cast(data.pad.pad)); + + switch (data.type) { + case EngineInputType::Button: + // Workaround for old compatibility + if (data.engine == "keyboard") { + new_input.Set("code", data.index); + break; + } + new_input.Set("button", data.index); + break; + case EngineInputType::HatButton: + new_input.Set("hat", data.index); + new_input.Set("direction", data.hat_name); + break; + case EngineInputType::Analog: + // Ignore mouse axis when mapping buttons + if (data.engine == "mouse") { + return; + } + new_input.Set("axis", data.index); + new_input.Set("threshold", 0.5f); + break; + default: + return; + } + input_queue.Push(new_input); +} + +void MappingFactory::RegisterStick(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid.IsValid()) { + new_input.Set("guid", data.pad.guid.RawString()); + } + new_input.Set("port", static_cast(data.pad.port)); + new_input.Set("pad", static_cast(data.pad.pad)); + + // If engine is mouse map the mouse position as a joystick + if (data.engine == "mouse") { + new_input.Set("axis_x", 0); + new_input.Set("axis_y", 1); + new_input.Set("threshold", 0.5f); + new_input.Set("range", 1.0f); + new_input.Set("deadzone", 0.0f); + input_queue.Push(new_input); + return; + } + + switch (data.type) { + case EngineInputType::Button: + case EngineInputType::HatButton: + RegisterButton(data); + return; + case EngineInputType::Analog: + if (first_axis == data.index) { + return; + } + if (first_axis == -1) { + first_axis = data.index; + return; + } + new_input.Set("axis_x", first_axis); + new_input.Set("axis_y", data.index); + new_input.Set("threshold", 0.5f); + new_input.Set("range", 0.95f); + new_input.Set("deadzone", 0.15f); + break; + default: + return; + } + input_queue.Push(new_input); +} + +void MappingFactory::RegisterMotion(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid.IsValid()) { + new_input.Set("guid", data.pad.guid.RawString()); + } + new_input.Set("port", static_cast(data.pad.port)); + new_input.Set("pad", static_cast(data.pad.pad)); + + // If engine is mouse map the mouse position as 3 axis motion + if (data.engine == "mouse") { + new_input.Set("axis_x", 1); + new_input.Set("invert_x", "-"); + new_input.Set("axis_y", 0); + new_input.Set("axis_z", 4); + new_input.Set("range", 1.0f); + new_input.Set("deadzone", 0.0f); + input_queue.Push(new_input); + return; + } + + switch (data.type) { + case EngineInputType::Button: + case EngineInputType::HatButton: + RegisterButton(data); + return; + case EngineInputType::Analog: + if (first_axis == data.index) { + return; + } + if (second_axis == data.index) { + return; + } + if (first_axis == -1) { + first_axis = data.index; + return; + } + if (second_axis == -1) { + second_axis = data.index; + return; + } + new_input.Set("axis_x", first_axis); + new_input.Set("axis_y", second_axis); + new_input.Set("axis_z", data.index); + new_input.Set("range", 1.0f); + new_input.Set("deadzone", 0.20f); + break; + case EngineInputType::Motion: + new_input.Set("motion", data.index); + break; + default: + return; + } + input_queue.Push(new_input); +} + +bool MappingFactory::IsDriverValid(const MappingData& data) const { + // Only port 0 can be mapped on the keyboard + if (data.engine == "keyboard" && data.pad.port != 0) { + return false; + } + // To prevent mapping with two devices we disable any UDP except motion + if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" && + data.type != EngineInputType::Motion) { + return false; + } + // The following drivers don't need to be mapped + if (data.engine == "touch_from_button") { + return false; + } + if (data.engine == "analog_from_button") { + return false; + } + return true; +} + +} // namespace InputCommon diff --git a/src/input_common/input_mapping.h b/src/input_common/input_mapping.h new file mode 100644 index 000000000..79bd083e0 --- /dev/null +++ b/src/input_common/input_mapping.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/param_package.h" +#include "common/threadsafe_queue.h" + +namespace InputCommon::Polling { +enum class InputType; +} + +namespace InputCommon { +class InputEngine; +struct MappingData; + +class MappingFactory { +public: + MappingFactory(); + + /** + * Resets all variables to begin the mapping process + * @param type type of input desired to be returned + */ + void BeginMapping(Polling::InputType type); + + /// Returns an input event with mapping information from the input_queue + [[nodiscard]] Common::ParamPackage GetNextInput(); + + /** + * Registers mapping input data from the driver + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterInput(const MappingData& data); + + /// Stop polling from all backends + void StopMapping(); + +private: + /** + * If provided data satisfies the requirements it will push an element to the input_queue + * Supported input: + * - Button: Creates a basic button ParamPackage + * - HatButton: Creates a basic hat button ParamPackage + * - Analog: Creates a basic analog ParamPackage + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterButton(const MappingData& data); + + /** + * If provided data satisfies the requirements it will push an element to the input_queue + * Supported input: + * - Button, HatButton: Pass the data to RegisterButton + * - Analog: Stores the first axis and on the second axis creates a basic stick ParamPackage + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterStick(const MappingData& data); + + /** + * If provided data satisfies the requirements it will push an element to the input_queue + * Supported input: + * - Button, HatButton: Pass the data to RegisterButton + * - Analog: Stores the first two axis and on the third axis creates a basic Motion + * ParamPackage + * - Motion: Creates a basic Motion ParamPackage + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterMotion(const MappingData& data); + + /** + * Returns true if driver can be mapped + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + bool IsDriverValid(const MappingData& data) const; + + Common::SPSCQueue input_queue; + Polling::InputType input_type{Polling::InputType::None}; + bool is_enabled{}; + int first_axis = -1; + int second_axis = -1; +}; + +} // namespace InputCommon diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp new file mode 100644 index 000000000..de8dce25b --- /dev/null +++ b/src/input_common/input_poller.cpp @@ -0,0 +1,790 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/common_types.h" +#include "common/input.h" + +#include "input_common/input_engine.h" +#include "input_common/input_poller.h" + +namespace InputCommon { + +class DummyInput final : public Common::Input::InputDevice { +public: + explicit DummyInput() = default; +}; + +class InputFromButton final : public Common::Input::InputDevice { +public: + explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, + InputEngine* input_engine_) + : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Button, + .index = button, + .callback = engine_callback, + }; + last_button_value = false; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromButton() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::ButtonStatus GetStatus() const { + return { + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + if (status.button_status.value != last_button_value) { + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const bool toggle; + const bool inverted; + int callback_key; + bool last_button_value; + InputEngine* input_engine; +}; + +class InputFromHatButton final : public Common::Input::InputDevice { +public: + explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_, + bool inverted_, InputEngine* input_engine_) + : identifier(identifier_), button(button_), direction(direction_), toggle(toggle_), + inverted(inverted_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::HatButton, + .index = button, + .callback = engine_callback, + }; + last_button_value = false; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromHatButton() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::ButtonStatus GetStatus() const { + return { + .value = input_engine->GetHatButton(identifier, button, direction), + .inverted = inverted, + .toggle = toggle, + }; + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + if (status.button_status.value != last_button_value) { + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const u8 direction; + const bool toggle; + const bool inverted; + int callback_key; + bool last_button_value; + InputEngine* input_engine; +}; + +class InputFromStick final : public Common::Input::InputDevice { +public: + explicit InputFromStick(PadIdentifier identifier_, int axis_x_, int axis_y_, + Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + InputEngine* input_engine_) + : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_), + properties_y(properties_y_), + input_engine(input_engine_), invert_axis_y{input_engine_->GetEngineName() == "sdl"} { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + } + + ~InputFromStick() override { + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + } + + Common::Input::StickStatus GetStatus() const { + Common::Input::StickStatus status; + status.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + // This is a workaround to keep compatibility with old yuzu versions. Vertical axis is + // inverted on SDL compared to Nintendo + if (invert_axis_y) { + status.y.raw_value = -status.y.raw_value; + } + return status; + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + + last_axis_x_value = status.stick_status.x.raw_value; + last_axis_y_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + + if (status.stick_status.x.raw_value != last_axis_x_value || + status.stick_status.y.raw_value != last_axis_y_value) { + last_axis_x_value = status.stick_status.x.raw_value; + last_axis_y_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis_x; + const int axis_y; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + int callback_key_x; + int callback_key_y; + float last_axis_x_value; + float last_axis_y_value; + InputEngine* input_engine; + const bool invert_axis_y; +}; + +class InputFromTouch final : public Common::Input::InputDevice { +public: + explicit InputFromTouch(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, + int axis_x_, int axis_y_, Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + InputEngine* input_engine_) + : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), + axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_), + properties_y(properties_y_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier button_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Button, + .index = button, + .callback = engine_callback, + }; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + last_button_value = false; + callback_key_button = input_engine->SetCallback(button_input_identifier); + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + } + + ~InputFromTouch() override { + input_engine->DeleteCallback(callback_key_button); + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + } + + Common::Input::TouchStatus GetStatus() const { + Common::Input::TouchStatus status{}; + status.pressed = { + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + status.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + return status; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Touch, + .touch_status = GetStatus(), + }; + + if (status.touch_status.x.raw_value != last_axis_x_value || + status.touch_status.y.raw_value != last_axis_y_value || + status.touch_status.pressed.value != last_button_value) { + last_axis_x_value = status.touch_status.x.raw_value; + last_axis_y_value = status.touch_status.y.raw_value; + last_button_value = status.touch_status.pressed.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const bool toggle; + const bool inverted; + const int axis_x; + const int axis_y; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + int callback_key_button; + int callback_key_x; + int callback_key_y; + bool last_button_value; + float last_axis_x_value; + float last_axis_y_value; + InputEngine* input_engine; +}; + +class InputFromAnalog final : public Common::Input::InputDevice { +public: + explicit InputFromAnalog(PadIdentifier identifier_, int axis_, + Common::Input::AnalogProperties properties_, + InputEngine* input_engine_) + : identifier(identifier_), axis(axis_), properties(properties_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis, + .callback = engine_callback, + }; + last_axis_value = 0.0f; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromAnalog() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::AnalogStatus GetStatus() const { + return { + .raw_value = input_engine->GetAxis(identifier, axis), + .properties = properties, + }; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Analog, + .analog_status = GetStatus(), + }; + + if (status.analog_status.raw_value != last_axis_value) { + last_axis_value = status.analog_status.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis; + const Common::Input::AnalogProperties properties; + int callback_key; + float last_axis_value; + InputEngine* input_engine; +}; + +class InputFromMotion final : public Common::Input::InputDevice { +public: + explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_, + InputEngine* input_engine_) + : identifier(identifier_), motion_sensor(motion_sensor_), gyro_threshold(gyro_threshold_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Motion, + .index = motion_sensor, + .callback = engine_callback, + }; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromMotion() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::MotionStatus GetStatus() const { + const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor); + Common::Input::MotionStatus status{}; + const Common::Input::AnalogProperties properties = { + .deadzone = 0.0f, + .range = 1.0f, + .threshold = gyro_threshold, + .offset = 0.0f, + }; + status.accel.x = {.raw_value = basic_motion.accel_x, .properties = properties}; + status.accel.y = {.raw_value = basic_motion.accel_y, .properties = properties}; + status.accel.z = {.raw_value = basic_motion.accel_z, .properties = properties}; + status.gyro.x = {.raw_value = basic_motion.gyro_x, .properties = properties}; + status.gyro.y = {.raw_value = basic_motion.gyro_y, .properties = properties}; + status.gyro.z = {.raw_value = basic_motion.gyro_z, .properties = properties}; + status.delta_timestamp = basic_motion.delta_timestamp; + return status; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + TriggerOnChange(status); + } + +private: + const PadIdentifier identifier; + const int motion_sensor; + const float gyro_threshold; + int callback_key; + InputEngine* input_engine; +}; + +class InputFromAxisMotion final : public Common::Input::InputDevice { +public: + explicit InputFromAxisMotion(PadIdentifier identifier_, int axis_x_, int axis_y_, int axis_z_, + Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + Common::Input::AnalogProperties properties_z_, + InputEngine* input_engine_) + : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), axis_z(axis_z_), + properties_x(properties_x_), properties_y(properties_y_), properties_z(properties_z_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + const InputIdentifier z_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_z, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + last_axis_z_value = 0.0f; + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + callback_key_z = input_engine->SetCallback(z_input_identifier); + } + + ~InputFromAxisMotion() override { + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + input_engine->DeleteCallback(callback_key_z); + } + + Common::Input::MotionStatus GetStatus() const { + Common::Input::MotionStatus status{}; + status.gyro.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.gyro.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + status.gyro.z = { + .raw_value = input_engine->GetAxis(identifier, axis_z), + .properties = properties_z, + }; + status.delta_timestamp = 5000; + status.force_update = true; + return status; + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + last_axis_x_value = status.motion_status.gyro.x.raw_value; + last_axis_y_value = status.motion_status.gyro.y.raw_value; + last_axis_z_value = status.motion_status.gyro.z.raw_value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + if (status.motion_status.gyro.x.raw_value != last_axis_x_value || + status.motion_status.gyro.y.raw_value != last_axis_y_value || + status.motion_status.gyro.z.raw_value != last_axis_z_value) { + last_axis_x_value = status.motion_status.gyro.x.raw_value; + last_axis_y_value = status.motion_status.gyro.y.raw_value; + last_axis_z_value = status.motion_status.gyro.z.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis_x; + const int axis_y; + const int axis_z; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + const Common::Input::AnalogProperties properties_z; + int callback_key_x; + int callback_key_y; + int callback_key_z; + float last_axis_x_value; + float last_axis_y_value; + float last_axis_z_value; + InputEngine* input_engine; +}; + +class OutputFromIdentifier final : public Common::Input::OutputDevice { +public: + explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) {} + + Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override { + return input_engine->SetPollingMode(identifier, polling_mode); + } + +private: + const PadIdentifier identifier; + InputEngine* input_engine; +}; + +std::unique_ptr InputFactory::CreateButtonDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto button_id = params.Get("button", 0); + const auto keyboard_key = params.Get("code", 0); + const auto toggle = params.Get("toggle", false) != 0; + const auto inverted = params.Get("inverted", false) != 0; + input_engine->PreSetController(identifier); + input_engine->PreSetButton(identifier, button_id); + input_engine->PreSetButton(identifier, keyboard_key); + if (keyboard_key != 0) { + return std::make_unique(identifier, keyboard_key, toggle, inverted, + input_engine.get()); + } + return std::make_unique(identifier, button_id, toggle, inverted, + input_engine.get()); +} + +std::unique_ptr InputFactory::CreateHatButtonDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto button_id = params.Get("hat", 0); + const auto direction = input_engine->GetHatButtonId(params.Get("direction", "")); + const auto toggle = params.Get("toggle", false) != 0; + const auto inverted = params.Get("inverted", false) != 0; + + input_engine->PreSetController(identifier); + input_engine->PreSetHatButton(identifier, button_id); + return std::make_unique(identifier, button_id, direction, toggle, inverted, + input_engine.get()); +} + +std::unique_ptr InputFactory::CreateStickDevice( + const Common::ParamPackage& params) { + const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 0.95f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", "+") != "+", + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + return std::make_unique(identifier, axis_x, axis_y, properties_x, properties_y, + input_engine.get()); +} + +std::unique_ptr InputFactory::CreateAnalogDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto axis = params.Get("axis", 0); + const Common::Input::AnalogProperties properties = { + .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f), + .range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f), + .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), + .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert", "+") == "-", + .toggle = params.Get("toggle", false) != 0, + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis); + return std::make_unique(identifier, axis, properties, input_engine.get()); +} + +std::unique_ptr InputFactory::CreateTouchDevice( + const Common::ParamPackage& params) { + const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + const auto button = params.Get("button", 0); + const auto toggle = params.Get("toggle", false) != 0; + const auto inverted = params.Get("inverted", false) != 0; + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", false) != 0, + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + input_engine->PreSetButton(identifier, button); + return std::make_unique(identifier, button, toggle, inverted, axis_x, axis_y, + properties_x, properties_y, input_engine.get()); +} + +std::unique_ptr InputFactory::CreateMotionDevice( + Common::ParamPackage params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + if (params.Has("motion")) { + const auto motion_sensor = params.Get("motion", 0); + const auto gyro_threshold = params.Get("threshold", 0.007f); + input_engine->PreSetController(identifier); + input_engine->PreSetMotion(identifier, motion_sensor); + return std::make_unique(identifier, motion_sensor, gyro_threshold, + input_engine.get()); + } + + const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", "+") != "+", + }; + + const auto axis_z = params.Get("axis_z", 1); + const Common::Input::AnalogProperties properties_z = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_z", "+") != "+", + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + input_engine->PreSetAxis(identifier, axis_z); + return std::make_unique(identifier, axis_x, axis_y, axis_z, properties_x, + properties_y, properties_z, input_engine.get()); +} + +InputFactory::InputFactory(std::shared_ptr input_engine_) + : input_engine(std::move(input_engine_)) {} + +std::unique_ptr InputFactory::Create( + const Common::ParamPackage& params) { + if (params.Has("button") && params.Has("axis_x") && params.Has("axis_y")) { + return CreateTouchDevice(params); + } + if (params.Has("button") || params.Has("code")) { + return CreateButtonDevice(params); + } + if (params.Has("hat")) { + return CreateHatButtonDevice(params); + } + if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { + return CreateMotionDevice(params); + } + if (params.Has("motion")) { + return CreateMotionDevice(params); + } + if (params.Has("axis_x") && params.Has("axis_y")) { + return CreateStickDevice(params); + } + if (params.Has("axis")) { + return CreateAnalogDevice(params); + } + LOG_ERROR(Input, "Invalid parameters given"); + return std::make_unique(); +} + +OutputFactory::OutputFactory(std::shared_ptr input_engine_) + : input_engine(std::move(input_engine_)) {} + +std::unique_ptr OutputFactory::Create( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique(identifier, input_engine.get()); +} + +} // namespace InputCommon diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h new file mode 100644 index 000000000..1e0112bbc --- /dev/null +++ b/src/input_common/input_poller.h @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Input { +class InputDevice; + +template +class Factory; +}; // namespace Input + +namespace InputCommon { +class InputEngine; + +class OutputFactory final : public Common::Input::Factory { +public: + explicit OutputFactory(std::shared_ptr input_engine_); + + /** + * Creates an output device from the parameters given. + * @param params contains parameters for creating the device: + * - "guid" text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique output device with the parameters specified + */ + std::unique_ptr Create( + const Common::ParamPackage& params) override; + +private: + std::shared_ptr input_engine; +}; + +/** + * An Input factory. It receives input events and forward them to all input devices it created. + */ +class InputFactory final : public Common::Input::Factory { +public: + explicit InputFactory(std::shared_ptr input_engine_); + + /** + * Creates an input device from the parameters given. Identifies the type of input to be + * returned if it contains the following parameters: + * - button: Contains "button" or "code" + * - hat_button: Contains "hat" + * - analog: Contains "axis" + * - stick: Contains "axis_x" and "axis_y" + * - motion: Contains "axis_x", "axis_y" and "axis_z" + * - motion: Contains "motion" + * - touch: Contains "button", "axis_x" and "axis_y" + * - output: Contains "output" + * @param params contains parameters for creating the device: + * - "code": the code of the keyboard key to bind with the input + * - "button": same as "code" but for controller buttons + * - "hat": similar as "button" but it's a group of hat buttons from SDL + * - "axis": the axis number of the axis to bind with the input + * - "motion": the motion number of the motion to bind with the input + * - "axis_x": same as axis but specifying horizontal direction + * - "axis_y": same as axis but specifying vertical direction + * - "axis_z": same as axis but specifying forward direction + * @returns a unique input device with the parameters specified + */ + std::unique_ptr Create(const Common::ParamPackage& params) override; + +private: + /** + * Creates a button device from the parameters given. + * @param params contains parameters for creating the device: + * - "code": the code of the keyboard key to bind with the input + * - "button": same as "code" but for controller buttons + * - "toggle": press once to enable, press again to disable + * - "inverted": inverts the output of the button + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr CreateButtonDevice( + const Common::ParamPackage& params); + + /** + * Creates a hat button device from the parameters given. + * @param params contains parameters for creating the device: + * - "button": the controller hat id to bind with the input + * - "direction": the direction id to be detected + * - "toggle": press once to enable, press again to disable + * - "inverted": inverts the output of the button + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr CreateHatButtonDevice( + const Common::ParamPackage& params); + + /** + * Creates a stick device from the parameters given. + * @param params contains parameters for creating the device: + * - "axis_x": the controller horizontal axis id to bind with the input + * - "axis_y": the controller vertical axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "threshold": the minimum required value to considered pressed + * - "offset_x": the amount of offset in the x axis + * - "offset_y": the amount of offset in the y axis + * - "invert_x": inverts the sign of the horizontal axis + * - "invert_y": inverts the sign of the vertical axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr CreateStickDevice( + const Common::ParamPackage& params); + + /** + * Creates an analog device from the parameters given. + * @param params contains parameters for creating the device: + * - "axis": the controller axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "threshold": the minimum required value to considered pressed + * - "offset": the amount of offset in the axis + * - "invert": inverts the sign of the axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr CreateAnalogDevice( + const Common::ParamPackage& params); + + /** + * Creates a touch device from the parameters given. + * @param params contains parameters for creating the device: + * - "button": the controller hat id to bind with the input + * - "direction": the direction id to be detected + * - "toggle": press once to enable, press again to disable + * - "inverted": inverts the output of the button + * - "axis_x": the controller horizontal axis id to bind with the input + * - "axis_y": the controller vertical axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "threshold": the minimum required value to considered pressed + * - "offset_x": the amount of offset in the x axis + * - "offset_y": the amount of offset in the y axis + * - "invert_x": inverts the sign of the horizontal axis + * - "invert_y": inverts the sign of the vertical axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr CreateTouchDevice( + const Common::ParamPackage& params); + + /** + * Creates a motion device from the parameters given. + * @param params contains parameters for creating the device: + * - "axis_x": the controller horizontal axis id to bind with the input + * - "axis_y": the controller vertical axis id to bind with the input + * - "axis_z": the controller forward axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "offset_x": the amount of offset in the x axis + * - "offset_y": the amount of offset in the y axis + * - "offset_z": the amount of offset in the z axis + * - "invert_x": inverts the sign of the horizontal axis + * - "invert_y": inverts the sign of the vertical axis + * - "invert_z": inverts the sign of the forward axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr CreateMotionDevice(Common::ParamPackage params); + + std::shared_ptr input_engine; +}; +} // namespace InputCommon diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp deleted file mode 100644 index 98d4f30a2..000000000 --- a/src/input_common/keyboard.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include "input_common/keyboard.h" - -namespace InputCommon { - -class KeyButton final : public Input::ButtonDevice { -public: - explicit KeyButton(std::atomic& status_) : status(status_) {} - - ~KeyButton() override = default; - - bool GetStatus() const override { - return status.load(); - } - - friend class KeyButtonList; - -private: - std::atomic& status; -}; - -struct KeyButtonPair { - explicit KeyButtonPair(int key_code_) : key_code(key_code_) {} - int key_code; - std::atomic status{false}; -}; - -class KeyButtonList { -public: - KeyButtonPair& AddKeyButton(int key_code) { - std::lock_guard guard{mutex}; - auto it = std::find_if(list.begin(), list.end(), [key_code](const KeyButtonPair& pair) { - return pair.key_code == key_code; - }); - if (it == list.end()) { - return list.emplace_back(key_code); - } - return *it; - } - - void ChangeKeyStatus(int key_code, bool pressed) { - std::lock_guard guard{mutex}; - for (KeyButtonPair& pair : list) { - if (pair.key_code == key_code) - pair.status.store(pressed); - } - } - - void ChangeAllKeyStatus(bool pressed) { - std::lock_guard guard{mutex}; - for (KeyButtonPair& pair : list) { - pair.status.store(pressed); - } - } - -private: - std::mutex mutex; - std::list list; -}; - -Keyboard::Keyboard() : key_button_list{std::make_shared()} {} - -std::unique_ptr Keyboard::Create(const Common::ParamPackage& params) { - int key_code = params.Get("code", 0); - auto& pair = key_button_list->AddKeyButton(key_code); - return std::make_unique(pair.status); -} - -void Keyboard::PressKey(int key_code) { - key_button_list->ChangeKeyStatus(key_code, true); -} - -void Keyboard::ReleaseKey(int key_code) { - key_button_list->ChangeKeyStatus(key_code, false); -} - -void Keyboard::ReleaseAllKeys() { - key_button_list->ChangeAllKeyStatus(false); -} - -} // namespace InputCommon diff --git a/src/input_common/keyboard.h b/src/input_common/keyboard.h deleted file mode 100644 index 861950472..000000000 --- a/src/input_common/keyboard.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/frontend/input.h" - -namespace InputCommon { - -class KeyButtonList; - -/** - * A button device factory representing a keyboard. It receives keyboard events and forward them - * to all button devices it created. - */ -class Keyboard final : public Input::Factory { -public: - Keyboard(); - - /** - * Creates a button device from a keyboard key - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; - - /** - * Sets the status of all buttons bound with the key to pressed - * @param key_code the code of the key to press - */ - void PressKey(int key_code); - - /** - * Sets the status of all buttons bound with the key to released - * @param key_code the code of the key to release - */ - void ReleaseKey(int key_code); - - void ReleaseAllKeys(); - -private: - std::shared_ptr key_button_list; -}; - -} // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index bcff11d20..ba56727c8 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -1,165 +1,521 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include -#include +#include "common/input.h" #include "common/param_package.h" +<<<<<<< HEAD #include "input_common/analog_from_button.h" #ifdef ENABLE_GCADAPTER #include "input_common/gcadapter/gc_adapter.h" #include "input_common/gcadapter/gc_poller.h" #endif #include "input_common/keyboard.h" -#include "input_common/main.h" -#include "input_common/motion_emu.h" -#include "input_common/sdl/sdl.h" -#include "input_common/sdl/sdl_impl.h" -#include "input_common/touch_from_button.h" -#include "input_common/udp/udp.h" + == == == = +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" +#include "input_common/drivers/touch_screen.h" +#include "input_common/drivers/udp_client.h" +#include "input_common/drivers/virtual_gamepad.h" +#include "input_common/helpers/stick_from_buttons.h" +#include "input_common/helpers/touch_from_buttons.h" +#include "input_common/input_engine.h" +#include "input_common/input_mapping.h" +#include "input_common/input_poller.h" +>>>>>>> 6e5fec9fe (add input common changes) + #include "input_common/main.h" -namespace InputCommon { - -#ifdef ENABLE_GCADAPTER -std::shared_ptr gcbuttons; -std::shared_ptr gcanalog; -std::shared_ptr gcadapter; +#ifdef HAVE_LIBUSB +#include "input_common/drivers/gc_adapter.h" #endif -static std::shared_ptr keyboard; -static std::shared_ptr motion_emu; -static std::unique_ptr udp; -static std::unique_ptr sdl; - -void Init() { -#ifdef ENABLE_GCADAPTER - gcadapter = std::make_shared(); - gcbuttons = std::make_shared(gcadapter); - Input::RegisterFactory("gcpad", gcbuttons); - gcanalog = std::make_shared(gcadapter); - Input::RegisterFactory("gcpad", gcanalog); +#ifdef HAVE_SDL2 +#include "input_common/drivers/sdl_driver.h" #endif - keyboard = std::make_shared(); - Input::RegisterFactory("keyboard", keyboard); - Input::RegisterFactory("analog_from_button", - std::make_shared()); - motion_emu = std::make_shared(); - Input::RegisterFactory("motion_emu", motion_emu); - Input::RegisterFactory("touch_from_button", - std::make_shared()); - sdl = SDL::Init(); + namespace InputCommon { - udp = CemuhookUDP::Init(); -} - -void Shutdown() { -#ifdef ENABLE_GCADAPTER - Input::UnregisterFactory("gcpad"); - Input::UnregisterFactory("gcpad"); - gcbuttons.reset(); - gcanalog.reset(); +<<<<<<< HEAD +#ifdef ENABLE_GCADAPTER std::shared_ptr < GCButtonFactory> gcbuttons; + std::shared_ptr gcanalog; + std::shared_ptr gcadapter; #endif - Input::UnregisterFactory("keyboard"); - keyboard.reset(); - Input::UnregisterFactory("analog_from_button"); - Input::UnregisterFactory("motion_emu"); - motion_emu.reset(); - Input::UnregisterFactory("emu_window"); - Input::UnregisterFactory("touch_from_button"); - sdl.reset(); - udp.reset(); -} + static std::shared_ptr keyboard; + static std::shared_ptr motion_emu; + static std::unique_ptr udp; + static std::unique_ptr sdl; -Keyboard* GetKeyboard() { - return keyboard.get(); -} - -MotionEmu* GetMotionEmu() { - return motion_emu.get(); -} - -std::string GenerateKeyboardParam(int key_code) { - Common::ParamPackage param{ - {"engine", "keyboard"}, - {"code", std::to_string(key_code)}, - }; - return param.Serialize(); -} - -std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, - int key_modifier, float modifier_scale) { - Common::ParamPackage circle_pad_param{ - {"engine", "analog_from_button"}, - {"up", GenerateKeyboardParam(key_up)}, - {"down", GenerateKeyboardParam(key_down)}, - {"left", GenerateKeyboardParam(key_left)}, - {"right", GenerateKeyboardParam(key_right)}, - {"modifier", GenerateKeyboardParam(key_modifier)}, - {"modifier_scale", std::to_string(modifier_scale)}, - }; - return circle_pad_param.Serialize(); -} - -Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button) { - const auto native_button{static_cast(button)}; - const auto engine{params.Get("engine", "")}; - if (engine == "sdl") { - return dynamic_cast(sdl.get())->GetSDLControllerButtonBindByGUID( - params.Get("guid", "0"), params.Get("port", 0), native_button); - } + void Init() { #ifdef ENABLE_GCADAPTER - if (engine == "gcpad") { - return gcbuttons->GetGcTo3DSMappedButton(params.Get("port", 0), native_button); - } + gcadapter = std::make_shared(); + gcbuttons = std::make_shared(gcadapter); + Input::RegisterFactory("gcpad", gcbuttons); + gcanalog = std::make_shared(gcadapter); + Input::RegisterFactory("gcpad", gcanalog); #endif - return {}; -} + keyboard = std::make_shared(); + Input::RegisterFactory("keyboard", keyboard); + Input::RegisterFactory("analog_from_button", + std::make_shared()); + motion_emu = std::make_shared(); + Input::RegisterFactory("motion_emu", motion_emu); + Input::RegisterFactory("touch_from_button", + std::make_shared()); + == == == = struct InputSubsystem::Impl { + template + void RegisterEngine(std::string name, std::shared_ptr& engine) { + MappingCallback mapping_callback{ + [this](const MappingData& data) { RegisterInput(data); }}; -Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog) { - const auto native_analog{static_cast(analog)}; - const auto engine{params.Get("engine", "")}; - if (engine == "sdl") { - return dynamic_cast(sdl.get())->GetSDLControllerAnalogBindByGUID( - params.Get("guid", "0"), params.Get("port", 0), native_analog); - } -#ifdef ENABLE_GCADAPTER - if (engine == "gcpad") { - return gcanalog->GetGcTo3DSMappedAnalog(params.Get("port", 0), native_analog); - } + engine = std::make_shared(name); + engine->SetMappingCallback(mapping_callback); +>>>>>>> 6e5fec9fe (add input common changes) + + std::shared_ptr input_factory = + std::make_shared(engine); + std::shared_ptr output_factory = + std::make_shared(engine); + Common::Input::RegisterInputFactory(engine->GetEngineName(), + std::move(input_factory)); + Common::Input::RegisterOutputFactory(engine->GetEngineName(), + std::move(output_factory)); + } + + void Initialize() { + mapping_factory = std::make_shared(); + + RegisterEngine("keyboard", keyboard); + RegisterEngine("mouse", mouse); + RegisterEngine("touch", touch_screen); +#ifdef HAVE_LIBUSB + RegisterEngine("gcpad", gcadapter); +#endif + RegisterEngine("cemuhookudp", udp_client); + RegisterEngine("virtual_gamepad", virtual_gamepad); +#ifdef HAVE_SDL2 + RegisterEngine("sdl", sdl); #endif - return {}; -} -void ReloadInputDevices() { - if (!udp) { - return; - } - udp->ReloadUDPClient(); -} + Common::Input::RegisterInputFactory("touch_from_button", + std::make_shared()); + Common::Input::RegisterInputFactory("analog_from_button", + std::make_shared()); + } -namespace Polling { + template + void UnregisterEngine(std::shared_ptr& engine) { + Common::Input::UnregisterInputFactory(engine->GetEngineName()); + Common::Input::UnregisterOutputFactory(engine->GetEngineName()); + engine.reset(); + } -std::vector> GetPollers(DeviceType type) { - std::vector> pollers; + void Shutdown() { + UnregisterEngine(keyboard); + UnregisterEngine(mouse); + UnregisterEngine(touch_screen); +#ifdef HAVE_LIBUSB + UnregisterEngine(gcadapter); +#endif + UnregisterEngine(udp_client); + UnregisterEngine(virtual_gamepad); +#ifdef HAVE_SDL2 + UnregisterEngine(sdl); +#endif + + Common::Input::UnregisterInputFactory("touch_from_button"); + Common::Input::UnregisterInputFactory("analog_from_button"); + } + + [[nodiscard]] std::vector GetInputDevices() const { + std::vector devices = { + Common::ParamPackage{{"display", "Any"}, {"engine", "any"}}, + }; + + auto keyboard_devices = keyboard->GetInputDevices(); + devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end()); + auto mouse_devices = mouse->GetInputDevices(); + devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end()); +#ifdef HAVE_LIBUSB + auto gcadapter_devices = gcadapter->GetInputDevices(); + devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end()); +#endif + auto udp_devices = udp_client->GetInputDevices(); + devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); +#ifdef HAVE_SDL2 + auto sdl_devices = sdl->GetInputDevices(); + devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); +#endif + + return devices; + } + + [[nodiscard]] std::shared_ptr GetInputEngine( + const Common::ParamPackage& params) const { + if (!params.Has("engine") || params.Get("engine", "") == "any") { + return nullptr; + } + const std::string engine = params.Get("engine", ""); + if (engine == keyboard->GetEngineName()) { + return keyboard; + } + if (engine == mouse->GetEngineName()) { + return mouse; + } +#ifdef HAVE_LIBUSB + if (engine == gcadapter->GetEngineName()) { + return gcadapter; + } +#endif + if (engine == udp_client->GetEngineName()) { + return udp_client; + } +#ifdef HAVE_SDL2 + if (engine == sdl->GetEngineName()) { + return sdl; + } +#endif + return nullptr; + } + + [[nodiscard]] AnalogMapping GetAnalogMappingForDevice( + const Common::ParamPackage& params) const { + const auto input_engine = GetInputEngine(params); + + if (input_engine == nullptr) { + return {}; + } + + return input_engine->GetAnalogMappingForDevice(params); + } + + [[nodiscard]] ButtonMapping GetButtonMappingForDevice( + const Common::ParamPackage& params) const { + const auto input_engine = GetInputEngine(params); + + if (input_engine == nullptr) { + return {}; + } + + return input_engine->GetButtonMappingForDevice(params); + } + + [[nodiscard]] MotionMapping GetMotionMappingForDevice( + const Common::ParamPackage& params) const { + const auto input_engine = GetInputEngine(params); + + if (input_engine == nullptr) { + return {}; + } + + return input_engine->GetMotionMappingForDevice(params); + } + + Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const { + if (!params.Has("engine") || params.Get("engine", "") == "any") { + return Common::Input::ButtonNames::Undefined; + } + const auto input_engine = GetInputEngine(params); + + if (input_engine == nullptr) { + return Common::Input::ButtonNames::Invalid; + } + + return input_engine->GetUIName(params); + } + + bool IsStickInverted(const Common::ParamPackage& params) { + const auto input_engine = GetInputEngine(params); + + if (input_engine == nullptr) { + return false; + } + + return input_engine->IsStickInverted(params); + } + + bool IsController(const Common::ParamPackage& params) { + const std::string engine = params.Get("engine", ""); + if (engine == mouse->GetEngineName()) { + return true; + } +#ifdef HAVE_LIBUSB + if (engine == gcadapter->GetEngineName()) { + return true; + } +#endif + if (engine == udp_client->GetEngineName()) { + return true; + } + if (engine == virtual_gamepad->GetEngineName()) { + return true; + } +#ifdef HAVE_SDL2 + if (engine == sdl->GetEngineName()) { + return true; + } +#endif + return false; + } + + void BeginConfiguration() { + keyboard->BeginConfiguration(); + mouse->BeginConfiguration(); +#ifdef HAVE_LIBUSB + gcadapter->BeginConfiguration(); +#endif + udp_client->BeginConfiguration(); +#ifdef HAVE_SDL2 + sdl->BeginConfiguration(); +#endif + } + + void EndConfiguration() { + keyboard->EndConfiguration(); + mouse->EndConfiguration(); +#ifdef HAVE_LIBUSB + gcadapter->EndConfiguration(); +#endif + udp_client->EndConfiguration(); +#ifdef HAVE_SDL2 + sdl->EndConfiguration(); +#endif + } + + void PumpEvents() const { +#ifdef HAVE_SDL2 + sdl->PumpEvents(); +#endif + } + + void RegisterInput(const MappingData& data) { + mapping_factory->RegisterInput(data); + } + + std::shared_ptr mapping_factory; + + std::shared_ptr keyboard; + std::shared_ptr mouse; + std::shared_ptr touch_screen; + std::shared_ptr udp_client; + std::shared_ptr virtual_gamepad; + +#ifdef HAVE_LIBUSB + std::shared_ptr gcadapter; +#endif #ifdef HAVE_SDL2 - pollers = sdl->GetPollers(type); + std::shared_ptr sdl; +#endif + }; + + InputSubsystem::InputSubsystem() : impl{std::make_unique()} {} + + InputSubsystem::~InputSubsystem() = default; + + void InputSubsystem::Initialize() { + impl->Initialize(); + } + +<<<<<<< HEAD + void Shutdown() { +#ifdef ENABLE_GCADAPTER + Input::UnregisterFactory("gcpad"); + Input::UnregisterFactory("gcpad"); + gcbuttons.reset(); + gcanalog.reset(); +#endif + Input::UnregisterFactory("keyboard"); + keyboard.reset(); + Input::UnregisterFactory("analog_from_button"); + Input::UnregisterFactory("motion_emu"); + motion_emu.reset(); + Input::UnregisterFactory("emu_window"); + Input::UnregisterFactory("touch_from_button"); + sdl.reset(); + udp.reset(); + == == == = void InputSubsystem::Shutdown() { + impl->Shutdown(); +>>>>>>> 6e5fec9fe (add input common changes) + } + + Keyboard* InputSubsystem::GetKeyboard() { + return impl->keyboard.get(); + } + + const Keyboard* InputSubsystem::GetKeyboard() const { + return impl->keyboard.get(); + } + + Mouse* InputSubsystem::GetMouse() { + return impl->mouse.get(); + } + + const Mouse* InputSubsystem::GetMouse() const { + return impl->mouse.get(); + } + + TouchScreen* InputSubsystem::GetTouchScreen() { + return impl->touch_screen.get(); + } + + const TouchScreen* InputSubsystem::GetTouchScreen() const { + return impl->touch_screen.get(); + } + + VirtualGamepad* InputSubsystem::GetVirtualGamepad() { + return impl->virtual_gamepad.get(); + } + + const VirtualGamepad* InputSubsystem::GetVirtualGamepad() const { + return impl->virtual_gamepad.get(); + } + + std::vector InputSubsystem::GetInputDevices() const { + return impl->GetInputDevices(); + } + + AnalogMapping InputSubsystem::GetAnalogMappingForDevice( + const Common::ParamPackage& device) const { + return impl->GetAnalogMappingForDevice(device); + } + + ButtonMapping InputSubsystem::GetButtonMappingForDevice( + const Common::ParamPackage& device) const { + return impl->GetButtonMappingForDevice(device); + } + + MotionMapping InputSubsystem::GetMotionMappingForDevice( + const Common::ParamPackage& device) const { + return impl->GetMotionMappingForDevice(device); + } + + Common::Input::ButtonNames InputSubsystem::GetButtonName( + const Common::ParamPackage& params) const { + return impl->GetButtonName(params); + } + + bool InputSubsystem::IsController(const Common::ParamPackage& params) const { + return impl->IsController(params); + } + + bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const { + if (params.Has("axis_x") && params.Has("axis_y")) { + return impl->IsStickInverted(params); + } + return false; + } + + void InputSubsystem::ReloadInputDevices() { + impl->udp_client.get()->ReloadSockets(); + } + + void InputSubsystem::BeginMapping(Polling::InputType type) { + impl->BeginConfiguration(); + impl->mapping_factory->BeginMapping(type); + } + + Common::ParamPackage InputSubsystem::GetNextInput() const { + return impl->mapping_factory->GetNextInput(); + } + + void InputSubsystem::StopMapping() const { + impl->EndConfiguration(); + impl->mapping_factory->StopMapping(); + } + + void InputSubsystem::PumpEvents() const { + impl->PumpEvents(); + } + + std::string GenerateKeyboardParam(int key_code) { + Common::ParamPackage param; + param.Set("engine", "keyboard"); + param.Set("code", key_code); + param.Set("toggle", false); + return param.Serialize(); + } + + std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, + int key_right, int key_modifier, + float modifier_scale) { + Common::ParamPackage circle_pad_param{ + {"engine", "analog_from_button"}, + {"up", GenerateKeyboardParam(key_up)}, + {"down", GenerateKeyboardParam(key_down)}, + {"left", GenerateKeyboardParam(key_left)}, + {"right", GenerateKeyboardParam(key_right)}, + {"modifier", GenerateKeyboardParam(key_modifier)}, + {"modifier_scale", std::to_string(modifier_scale)}, + }; + return circle_pad_param.Serialize(); + } +<<<<<<< HEAD + + Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, + int button) { + const auto native_button{static_cast(button)}; + const auto engine{params.Get("engine", "")}; + if (engine == "sdl") { + return dynamic_cast(sdl.get()) + ->GetSDLControllerButtonBindByGUID(params.Get("guid", "0"), + params.Get("port", 0), native_button); + } +#ifdef ENABLE_GCADAPTER + if (engine == "gcpad") { + return gcbuttons->GetGcTo3DSMappedButton(params.Get("port", 0), native_button); + } +#endif + return {}; + } + + Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, + int analog) { + const auto native_analog{static_cast(analog)}; + const auto engine{params.Get("engine", "")}; + if (engine == "sdl") { + return dynamic_cast(sdl.get()) + ->GetSDLControllerAnalogBindByGUID(params.Get("guid", "0"), + params.Get("port", 0), native_analog); + } +#ifdef ENABLE_GCADAPTER + if (engine == "gcpad") { + return gcanalog->GetGcTo3DSMappedAnalog(params.Get("port", 0), native_analog); + } +#endif + return {}; + } + + void ReloadInputDevices() { + if (!udp) { + return; + } + udp->ReloadUDPClient(); + } + + namespace Polling { + + std::vector> GetPollers(DeviceType type) { + std::vector> pollers; + +#ifdef HAVE_SDL2 + pollers = sdl->GetPollers(type); #endif #ifdef ENABLE_GCADAPTER - switch (type) { - case DeviceType::Analog: - pollers.push_back(std::make_unique(*gcanalog)); - break; - case DeviceType::Button: - pollers.push_back(std::make_unique(*gcbuttons)); - break; - default: - break; - } + switch (type) { + case DeviceType::Analog: + pollers.push_back(std::make_unique(*gcanalog)); + break; + case DeviceType::Button: + pollers.push_back(std::make_unique(*gcbuttons)); + break; + default: + break; + } #endif - return pollers; -} + return pollers; + } -} // namespace Polling -} // namespace InputCommon + } // namespace Polling + == == == = +>>>>>>> 6e5fec9fe (add input common changes) + } // namespace InputCommon diff --git a/src/input_common/main.h b/src/input_common/main.h index 48ecd26ba..9cf1e6ca6 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -1,72 +1,147 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include +#include #include namespace Common { class ParamPackage; } +namespace Common::Input { +enum class ButtonNames; +} + +namespace Settings::NativeAnalog { +enum Values : int; +} + +namespace Settings::NativeButton { +enum Values : int; +} + +namespace Settings::NativeMotion { +enum Values : int; +} + namespace InputCommon { - -/// Initializes and registers all built-in input device factories. -void Init(); - -/// Deregisters all built-in input device factories and shuts them down. -void Shutdown(); - class Keyboard; +class Mouse; +class TouchScreen; +class VirtualGamepad; +struct MappingData; +} // namespace InputCommon -/// Gets the keyboard button device factory. -Keyboard* GetKeyboard(); - -class MotionEmu; - -/// Gets the motion emulation factory. -MotionEmu* GetMotionEmu(); - -/// Generates a serialized param package for creating a keyboard button device -std::string GenerateKeyboardParam(int key_code); - -/// Generates a serialized param package for creating an analog device taking input from keyboard -std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, - int key_modifier, float modifier_scale); - -Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button); -Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog); - -/// Reloads the input devices -void ReloadInputDevices(); - +namespace InputCommon { namespace Polling { - -enum class DeviceType { Button, Analog }; +/// Type of input desired for mapping purposes +enum class InputType { None, Button, Stick, Motion, Touch }; +} // namespace Polling /** - * A class that can be used to get inputs from an input device like controllers without having to - * poll the device's status yourself + * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default + * mapping for the device. */ -class DevicePoller { +using AnalogMapping = std::unordered_map; +using ButtonMapping = std::unordered_map; +using MotionMapping = std::unordered_map; + +class InputSubsystem { public: - virtual ~DevicePoller() = default; - /// Setup and start polling for inputs, should be called before GetNextInput - virtual void Start() = 0; - /// Stop polling - virtual void Stop() = 0; + explicit InputSubsystem(); + ~InputSubsystem(); + + InputSubsystem(const InputSubsystem&) = delete; + InputSubsystem& operator=(const InputSubsystem&) = delete; + + InputSubsystem(InputSubsystem&&) = delete; + InputSubsystem& operator=(InputSubsystem&&) = delete; + + /// Initializes and registers all built-in input device factories. + void Initialize(); + + /// Unregisters all built-in input device factories and shuts them down. + void Shutdown(); + + /// Retrieves the underlying keyboard device. + [[nodiscard]] Keyboard* GetKeyboard(); + + /// Retrieves the underlying keyboard device. + [[nodiscard]] const Keyboard* GetKeyboard() const; + + /// Retrieves the underlying mouse device. + [[nodiscard]] Mouse* GetMouse(); + + /// Retrieves the underlying mouse device. + [[nodiscard]] const Mouse* GetMouse() const; + + /// Retrieves the underlying touch screen device. + [[nodiscard]] TouchScreen* GetTouchScreen(); + + /// Retrieves the underlying touch screen device. + [[nodiscard]] const TouchScreen* GetTouchScreen() const; + + /// Retrieves the underlying virtual gamepad input device. + [[nodiscard]] VirtualGamepad* GetVirtualGamepad(); + + /// Retrieves the underlying virtual gamepad input device. + [[nodiscard]] const VirtualGamepad* GetVirtualGamepad() const; + /** - * Every call to this function returns the next input recorded since calling Start - * @return A ParamPackage of the recorded input, which can be used to create an InputDevice. - * If there has been no input, the package is empty + * Returns all available input devices that this Factory can create a new device with. + * Each returned ParamPackage should have a `display` field used for display, a `engine` field + * for backends to determine if this backend is meant to service the request and any other + * information needed to identify this in the backend later. */ - virtual Common::ParamPackage GetNextInput() = 0; + [[nodiscard]] std::vector GetInputDevices() const; + + /// Retrieves the analog mappings for the given device. + [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const; + + /// Retrieves the button mappings for the given device. + [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const; + + /// Retrieves the motion mappings for the given device. + [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const; + + /// Returns an enum contaning the name to be displayed from the input engine. + [[nodiscard]] Common::Input::ButtonNames GetButtonName( + const Common::ParamPackage& params) const; + + /// Returns true if device is a controller. + [[nodiscard]] bool IsController(const Common::ParamPackage& params) const; + + /// Returns true if axis of a stick aren't mapped in the correct direction + [[nodiscard]] bool IsStickInverted(const Common::ParamPackage& device) const; + + /// Reloads the input devices. + void ReloadInputDevices(); + + /// Start polling from all backends for a desired input type. + void BeginMapping(Polling::InputType type); + + /// Returns an input event with mapping information. + [[nodiscard]] Common::ParamPackage GetNextInput() const; + + /// Stop polling from all backends. + void StopMapping() const; + + /// Signals SDL driver for new input events + void PumpEvents() const; + +private: + struct Impl; + std::unique_ptr impl; }; -// Get all DevicePoller from all backends for a specific device type -std::vector> GetPollers(DeviceType type); -} // namespace Polling +/// Generates a serialized param package for creating a keyboard button device. +std::string GenerateKeyboardParam(int key_code); + +/// Generates a serialized param package for creating an analog device taking input from keyboard. +std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, + int key_modifier, float modifier_scale); } // namespace InputCommon diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp deleted file mode 100644 index db36b6276..000000000 --- a/src/input_common/motion_emu.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include "common/math_util.h" -#include "common/quaternion.h" -#include "common/thread.h" -#include "common/vector_math.h" -#include "input_common/motion_emu.h" - -namespace InputCommon { - -// Implementation class of the motion emulation device -class MotionEmuDevice { -public: - MotionEmuDevice(int update_millisecond, float sensitivity, float tilt_clamp) - : update_millisecond(update_millisecond), - update_duration(std::chrono::duration_cast( - std::chrono::milliseconds(update_millisecond))), - sensitivity(sensitivity), tilt_clamp(tilt_clamp), - motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {} - - ~MotionEmuDevice() { - if (motion_emu_thread.joinable()) { - shutdown_event.Set(); - motion_emu_thread.join(); - } - } - - void BeginTilt(int x, int y) { - mouse_origin = Common::MakeVec(x, y); - is_tilting = true; - } - - void Tilt(int x, int y) { - auto mouse_move = Common::MakeVec(x, y) - mouse_origin; - if (is_tilting) { - std::lock_guard guard{tilt_mutex}; - if (mouse_move.x == 0 && mouse_move.y == 0) { - tilt_angle = 0; - } else { - tilt_direction = mouse_move.Cast(); - tilt_angle = std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, - Common::PI * this->tilt_clamp / 180.0f); - } - } - } - - void EndTilt() { - std::lock_guard guard{tilt_mutex}; - tilt_angle = 0; - is_tilting = false; - } - - std::tuple, Common::Vec3> GetStatus() { - std::lock_guard guard{status_mutex}; - return status; - } - -private: - const int update_millisecond; - const std::chrono::steady_clock::duration update_duration; - const float sensitivity; - - Common::Vec2 mouse_origin; - - std::mutex tilt_mutex; - Common::Vec2 tilt_direction; - float tilt_angle = 0; - float tilt_clamp = 90; - - bool is_tilting = false; - - Common::Event shutdown_event; - - std::tuple, Common::Vec3> status; - std::mutex status_mutex; - - // Note: always keep the thread declaration at the end so that other objects are initialized - // before this! - std::thread motion_emu_thread; - - void MotionEmuThread() { - auto update_time = std::chrono::steady_clock::now(); - Common::Quaternion q = Common::MakeQuaternion(Common::Vec3(), 0); - Common::Quaternion old_q; - - while (!shutdown_event.WaitUntil(update_time)) { - update_time += update_duration; - old_q = q; - - { - std::lock_guard guard{tilt_mutex}; - - // Find the quaternion describing current 3DS tilting - q = Common::MakeQuaternion( - Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle); - } - - auto inv_q = q.Inverse(); - - // Set the gravity vector in world space - auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f); - - // Find the angular rate vector in world space - auto angular_rate = ((q - old_q) * inv_q).xyz * 2; - angular_rate *= 1000 / update_millisecond / Common::PI * 180; - - // Transform the two vectors from world space to 3DS space - gravity = QuaternionRotate(inv_q, gravity); - angular_rate = QuaternionRotate(inv_q, angular_rate); - - // Update the sensor state - { - std::lock_guard guard{status_mutex}; - status = std::make_tuple(gravity, angular_rate); - } - } - } -}; - -// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as -// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory -// can forward all the inputs to the implementation only when it is valid. -class MotionEmuDeviceWrapper : public Input::MotionDevice { -public: - MotionEmuDeviceWrapper(int update_millisecond, float sensitivity, float tilt_clamp) { - device = std::make_shared(update_millisecond, sensitivity, tilt_clamp); - } - - std::tuple, Common::Vec3> GetStatus() const override { - return device->GetStatus(); - } - - std::shared_ptr device; -}; - -std::unique_ptr MotionEmu::Create(const Common::ParamPackage& params) { - int update_period = params.Get("update_period", 100); - float sensitivity = params.Get("sensitivity", 0.01f); - float tilt_clamp = params.Get("tilt_clamp", 90.0f); - auto device_wrapper = - std::make_unique(update_period, sensitivity, tilt_clamp); - // Previously created device is disconnected here. Having two motion devices for 3DS is not - // expected. - current_device = device_wrapper->device; - return std::move(device_wrapper); -} - -void MotionEmu::BeginTilt(int x, int y) { - if (auto ptr = current_device.lock()) { - ptr->BeginTilt(x, y); - } -} - -void MotionEmu::Tilt(int x, int y) { - if (auto ptr = current_device.lock()) { - ptr->Tilt(x, y); - } -} - -void MotionEmu::EndTilt() { - if (auto ptr = current_device.lock()) { - ptr->EndTilt(); - } -} - -} // namespace InputCommon diff --git a/src/input_common/motion_emu.h b/src/input_common/motion_emu.h deleted file mode 100644 index 7a7e22467..000000000 --- a/src/input_common/motion_emu.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/frontend/input.h" - -namespace InputCommon { - -class MotionEmuDevice; - -class MotionEmu : public Input::Factory { -public: - /** - * Creates a motion device emulated from mouse input - * @param params contains parameters for creating the device: - * - "update_period": update period in milliseconds - * - "sensitivity": the coefficient converting mouse movement to tilting angle - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; - - /** - * Signals that a motion sensor tilt has begun. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - */ - void BeginTilt(int x, int y); - - /** - * Signals that a motion sensor tilt is occurring. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - */ - void Tilt(int x, int y); - - /** - * Signals that a motion sensor tilt has ended. - */ - void EndTilt(); - -private: - std::weak_ptr current_device; -}; - -} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp deleted file mode 100644 index 644db3448..000000000 --- a/src/input_common/sdl/sdl.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "input_common/sdl/sdl.h" -#ifdef HAVE_SDL2 -#include "input_common/sdl/sdl_impl.h" -#endif - -namespace InputCommon::SDL { - -std::unique_ptr Init() { -#ifdef HAVE_SDL2 - return std::make_unique(); -#else - return std::make_unique(); -#endif -} -} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h deleted file mode 100644 index d7f24c68a..000000000 --- a/src/input_common/sdl/sdl.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include "core/frontend/input.h" -#include "input_common/main.h" - -union SDL_Event; - -namespace Common { -class ParamPackage; -} // namespace Common - -namespace InputCommon::Polling { -class DevicePoller; -enum class DeviceType; -} // namespace InputCommon::Polling - -namespace InputCommon::SDL { - -class State { -public: - using Pollers = std::vector>; - - /// Unregisters SDL device factories and shut them down. - virtual ~State() = default; - - virtual Pollers GetPollers(Polling::DeviceType type) = 0; -}; - -class NullState : public State { -public: - Pollers GetPollers(Polling::DeviceType type) override { - return {}; - } -}; - -std::unique_ptr Init(); - -} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp deleted file mode 100644 index 2e2589b35..000000000 --- a/src/input_common/sdl/sdl_impl.cpp +++ /dev/null @@ -1,1135 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/math_util.h" -#include "common/param_package.h" -#include "common/threadsafe_queue.h" -#include "core/frontend/input.h" -#include "input_common/sdl/sdl_impl.h" - -// These structures are not actually defined in the headers, so we need to define them here to use -// them. -typedef struct { - SDL_GameControllerBindType inputType; - union { - int button; - - struct { - int axis; - int axis_min; - int axis_max; - } axis; - - struct { - int hat; - int hat_mask; - } hat; - - } input; - - SDL_GameControllerBindType outputType; - union { - SDL_GameControllerButton button; - - struct { - SDL_GameControllerAxis axis; - int axis_min; - int axis_max; - } axis; - - } output; - -} SDL_ExtendedGameControllerBind; - -#if SDL_VERSION_ATLEAST(2, 26, 0) -/* our hard coded list of mapping support */ -typedef enum { - SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT, - SDL_CONTROLLER_MAPPING_PRIORITY_API, - SDL_CONTROLLER_MAPPING_PRIORITY_USER, -} SDL_ControllerMappingPriority; - -typedef struct _ControllerMapping_t { - SDL_JoystickGUID guid; - char* name; - char* mapping; - SDL_ControllerMappingPriority priority; - struct _ControllerMapping_t* next; -} ControllerMapping_t; -#endif - -struct _SDL_GameController { -#if SDL_VERSION_ATLEAST(2, 26, 0) - const void* magic; -#endif - - SDL_Joystick* joystick; /* underlying joystick device */ - int ref_count; - - const char* name; -#if SDL_VERSION_ATLEAST(2, 26, 0) - ControllerMapping_t* mapping; -#endif - int num_bindings; - SDL_ExtendedGameControllerBind* bindings; - SDL_ExtendedGameControllerBind** last_match_axis; - Uint8* last_hat_mask; - Uint32 guide_button_down; - - struct _SDL_GameController* next; /* pointer to next game controller we have allocated */ -}; - -namespace InputCommon { - -namespace SDL { - -static std::string GetGUID(SDL_Joystick* joystick) { - SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); - char guid_str[33]; - SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); - return guid_str; -} - -/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); - -static int SDLEventWatcher(void* userdata, SDL_Event* event) { - SDLState* sdl_state = reinterpret_cast(userdata); - // Don't handle the event if we are configuring - if (sdl_state->polling) { - sdl_state->event_queue.Push(*event); - } else { - sdl_state->HandleGameControllerEvent(*event); - } - return 0; -} - -constexpr std::array - xinput_to_3ds_mapping = {{ - SDL_CONTROLLER_BUTTON_B, - SDL_CONTROLLER_BUTTON_A, - SDL_CONTROLLER_BUTTON_Y, - SDL_CONTROLLER_BUTTON_X, - SDL_CONTROLLER_BUTTON_DPAD_UP, - SDL_CONTROLLER_BUTTON_DPAD_DOWN, - SDL_CONTROLLER_BUTTON_DPAD_LEFT, - SDL_CONTROLLER_BUTTON_DPAD_RIGHT, - SDL_CONTROLLER_BUTTON_LEFTSHOULDER, - SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, - SDL_CONTROLLER_BUTTON_START, - SDL_CONTROLLER_BUTTON_BACK, - SDL_CONTROLLER_BUTTON_INVALID, - SDL_CONTROLLER_BUTTON_INVALID, - SDL_CONTROLLER_BUTTON_INVALID, - SDL_CONTROLLER_BUTTON_INVALID, - SDL_CONTROLLER_BUTTON_GUIDE, - }}; - -struct SDLJoystickDeleter { - void operator()(SDL_Joystick* object) { - SDL_JoystickClose(object); - } -}; -class SDLJoystick { -public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick} {} - - void SetButton(int button, bool value) { - std::lock_guard lock{mutex}; - state.buttons[button] = value; - } - - bool GetButton(int button) const { - std::lock_guard lock{mutex}; - return state.buttons.at(button); - } - - void SetAxis(int axis, Sint16 value) { - std::lock_guard lock{mutex}; - state.axes[axis] = value; - } - - float GetAxis(int axis) const { - std::lock_guard lock{mutex}; - return state.axes.at(axis) / 32767.0f; - } - - std::tuple GetAnalog(int axis_x, int axis_y) const { - float x = GetAxis(axis_x); - float y = GetAxis(axis_y); - y = -y; // 3DS uses an y-axis inverse from SDL - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return std::make_tuple(x, y); - } - - void SetHat(int hat, Uint8 direction) { - std::lock_guard lock{mutex}; - state.hats[hat] = direction; - } - - bool GetHatDirection(int hat, Uint8 direction) const { - std::lock_guard lock{mutex}; - return (state.hats.at(hat) & direction) != 0; - } - - void SetAccel(const float x, const float y, const float z) { - std::lock_guard lock{mutex}; - state.accel.x = x; - state.accel.y = y; - state.accel.z = z; - } - void SetGyro(const float pitch, const float yaw, const float roll) { - std::lock_guard lock{mutex}; - state.gyro.x = pitch; - state.gyro.y = yaw; - state.gyro.z = roll; - } - std::tuple, Common::Vec3> GetMotion() const { - std::lock_guard lock{mutex}; - return std::make_tuple(state.accel, state.gyro); - } - - /** - * The guid of the joystick - */ - const std::string& GetGUID() const { - return guid; - } - - /** - * The number of joystick from the same type that were connected before this joystick - */ - int GetPort() const { - return port; - } - - SDL_Joystick* GetSDLJoystick() const { - return sdl_joystick.get(); - } - - void SetSDLJoystick(SDL_Joystick* joystick) { - sdl_joystick = std::unique_ptr(joystick); - } - - SDL_GameController* GetGameController() const { - return SDL_GameControllerFromInstanceID(SDL_JoystickInstanceID(sdl_joystick.get())); - } - -private: - struct State { - std::unordered_map buttons; - std::unordered_map axes; - std::unordered_map hats; - Common::Vec3 accel; - Common::Vec3 gyro; - } state; - std::string guid; - int port; - std::unique_ptr sdl_joystick; - mutable std::mutex mutex; -}; - -struct SDLGameControllerDeleter { - void operator()(SDL_GameController* object) { - SDL_GameControllerClose(object); - } -}; -class SDLGameController { -public: - SDLGameController(std::string guid_, int port_, SDL_GameController* controller) - : guid{std::move(guid_)}, port{port_}, sdl_controller{controller} {} - - /** - * The guid of the joystick/controller - */ - const std::string& GetGUID() const { - return guid; - } - - /** - * The number of joystick from the same type that were connected before this joystick - */ - int GetPort() const { - return port; - } - - SDL_GameController* GetSDLGameController() const { - return sdl_controller.get(); - } - - void SetSDLGameController(SDL_GameController* controller) { - sdl_controller = std::unique_ptr(controller); - } - -private: - std::string guid; - int port; - std::unique_ptr sdl_controller; -}; - -/** - * Get the nth joystick with the corresponding GUID - */ -std::shared_ptr SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { - std::lock_guard lock{joystick_map_mutex}; - const auto it = joystick_map.find(guid); - if (it != joystick_map.end()) { - while (it->second.size() <= static_cast(port)) { - auto joystick = - std::make_shared(guid, static_cast(it->second.size()), nullptr); - it->second.emplace_back(std::move(joystick)); - } - return it->second[port]; - } - auto joystick = std::make_shared(guid, 0, nullptr); - return joystick_map[guid].emplace_back(std::move(joystick)); -} - -std::shared_ptr SDLState::GetSDLGameControllerByGUID(const std::string& guid, - int port) { - std::lock_guard lock{controller_map_mutex}; - const auto it = controller_map.find(guid); - if (it != controller_map.end()) { - while (it->second.size() <= static_cast(port)) { - auto controller = std::make_shared( - guid, static_cast(it->second.size()), nullptr); - it->second.emplace_back(std::move(controller)); - } - return it->second[port]; - } - auto controller = std::make_shared(guid, 0, nullptr); - return controller_map[guid].emplace_back(std::move(controller)); -} - -/** - * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie - * it to a SDLJoystick with the same guid and that port - */ -std::shared_ptr SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); - const std::string guid = GetGUID(sdl_joystick); - - std::lock_guard lock{joystick_map_mutex}; - auto map_it = joystick_map.find(guid); - if (map_it != joystick_map.end()) { - auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [&sdl_joystick](const std::shared_ptr& joystick) { - return sdl_joystick == joystick->GetSDLJoystick(); - }); - if (vec_it != map_it->second.end()) { - // This is the common case: There is already an existing SDL_Joystick maped to a - // SDLJoystick. return the SDLJoystick - return *vec_it; - } - // Search for a SDLJoystick without a mapped SDL_Joystick... - auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [](const std::shared_ptr& joystick) { - return !joystick->GetSDLJoystick(); - }); - if (nullptr_it != map_it->second.end()) { - // ... and map it - (*nullptr_it)->SetSDLJoystick(sdl_joystick); - return *nullptr_it; - } - // There is no SDLJoystick without a mapped SDL_Joystick - // Create a new SDLJoystick - auto joystick = std::make_shared(guid, static_cast(map_it->second.size()), - sdl_joystick); - return map_it->second.emplace_back(std::move(joystick)); - } - auto joystick = std::make_shared(guid, 0, sdl_joystick); - return joystick_map[guid].emplace_back(std::move(joystick)); -} - -Common::ParamPackage SDLState::GetSDLControllerButtonBindByGUID( - const std::string& guid, int port, Settings::NativeButton::Values button) { - Common::ParamPackage params({{"engine", "sdl"}}); - params.Set("guid", guid); - params.Set("port", port); - SDL_GameController* controller = GetSDLGameControllerByGUID(guid, port)->GetSDLGameController(); - SDL_GameControllerButtonBind button_bind; - - if (!controller) { - LOG_WARNING(Input, "failed to open controller {}", guid); - return {{}}; - } - - auto mapped_button = xinput_to_3ds_mapping[static_cast(button)]; - if (mapped_button == SDL_CONTROLLER_BUTTON_INVALID) { - if (button == Settings::NativeButton::Values::ZL) { - button_bind = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); - } else if (button == Settings::NativeButton::Values::ZR) { - button_bind = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); - } else { - return {{}}; - } - } else { - button_bind = SDL_GameControllerGetBindForButton(controller, mapped_button); - } - - switch (button_bind.bindType) { - case SDL_CONTROLLER_BINDTYPE_BUTTON: - params.Set("button", button_bind.value.button); - break; - case SDL_CONTROLLER_BINDTYPE_HAT: - params.Set("hat", button_bind.value.hat.hat); - switch (button_bind.value.hat.hat_mask) { - case SDL_HAT_UP: - params.Set("direction", "up"); - break; - case SDL_HAT_DOWN: - params.Set("direction", "down"); - break; - case SDL_HAT_LEFT: - params.Set("direction", "left"); - break; - case SDL_HAT_RIGHT: - params.Set("direction", "right"); - break; - default: - return {{}}; - } - break; - case SDL_CONTROLLER_BINDTYPE_AXIS: - params.Set("axis", button_bind.value.axis); - -#if SDL_VERSION_ATLEAST(2, 0, 6) - { - const SDL_ExtendedGameControllerBind extended_bind = - controller->bindings[mapped_button]; - if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) { - params.Set("direction", "-"); - } else { - params.Set("direction", "+"); - } - params.Set( - "threshold", - (extended_bind.input.axis.axis_min + - (extended_bind.input.axis.axis_max - extended_bind.input.axis.axis_min) / 2.0f) / - SDL_JOYSTICK_AXIS_MAX); - } -#else - params.Set("direction", "+"); // lacks extended_bind, so just a guess -#endif - break; - case SDL_CONTROLLER_BINDTYPE_NONE: - LOG_WARNING(Input, "Button not bound: {}", Settings::NativeButton::mapping[button]); - return {{}}; - default: - LOG_WARNING(Input, "unknown SDL bind type {}", button_bind.bindType); - return {{}}; - } - - return params; -} - -Common::ParamPackage SDLState::GetSDLControllerAnalogBindByGUID( - const std::string& guid, int port, Settings::NativeAnalog::Values analog) { - Common::ParamPackage params({{"engine", "sdl"}}); - params.Set("guid", guid); - params.Set("port", port); - SDL_GameController* controller = GetSDLGameControllerByGUID(guid, port)->GetSDLGameController(); - SDL_GameControllerButtonBind button_bind_x; - SDL_GameControllerButtonBind button_bind_y; - - if (!controller) { - LOG_WARNING(Input, "failed to open controller {}", guid); - return {{}}; - } - - if (analog == Settings::NativeAnalog::Values::CirclePad) { - button_bind_x = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - button_bind_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); - } else if (analog == Settings::NativeAnalog::Values::CStick) { - button_bind_x = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - button_bind_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); - } else { - LOG_WARNING(Input, "analog value out of range {}", analog); - return {{}}; - } - - if (button_bind_x.bindType != SDL_CONTROLLER_BINDTYPE_AXIS || - button_bind_y.bindType != SDL_CONTROLLER_BINDTYPE_AXIS) { - return {{}}; - } - params.Set("axis_x", button_bind_x.value.axis); - params.Set("axis_y", button_bind_y.value.axis); - return params; -} - -void SDLState::InitJoystick(int joystick_index) { - SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); - if (!sdl_joystick) { - LOG_ERROR(Input, "failed to open joystick {}, with error: {}", joystick_index, - SDL_GetError()); - return; - } - const std::string guid = GetGUID(sdl_joystick); - - std::lock_guard lock{joystick_map_mutex}; - if (joystick_map.find(guid) == joystick_map.end()) { - auto joystick = std::make_shared(guid, 0, sdl_joystick); - joystick_map[guid].emplace_back(std::move(joystick)); - return; - } - auto& joystick_guid_list = joystick_map[guid]; - const auto it = std::find_if( - joystick_guid_list.begin(), joystick_guid_list.end(), - [](const std::shared_ptr& joystick) { return !joystick->GetSDLJoystick(); }); - if (it != joystick_guid_list.end()) { - (*it)->SetSDLJoystick(sdl_joystick); - return; - } - auto joystick = std::make_shared(guid, static_cast(joystick_guid_list.size()), - sdl_joystick); - joystick_guid_list.emplace_back(std::move(joystick)); -} - -void SDLState::InitGameController(int controller_index) { - SDL_GameController* sdl_controller = SDL_GameControllerOpen(controller_index); - if (!sdl_controller) { - LOG_WARNING(Input, "failed to open joystick {} as controller", controller_index); - return; - } -#if SDL_VERSION_ATLEAST(2, 0, 14) - if (SDL_GameControllerHasSensor(sdl_controller, SDL_SENSOR_ACCEL)) { - SDL_GameControllerSetSensorEnabled(sdl_controller, SDL_SENSOR_ACCEL, SDL_TRUE); - } - if (SDL_GameControllerHasSensor(sdl_controller, SDL_SENSOR_GYRO)) { - SDL_GameControllerSetSensorEnabled(sdl_controller, SDL_SENSOR_GYRO, SDL_TRUE); - } -#endif - const std::string guid = GetGUID(SDL_GameControllerGetJoystick(sdl_controller)); - - LOG_INFO(Input, "opened joystick {} as controller", controller_index); - std::lock_guard lock{controller_map_mutex}; - if (controller_map.find(guid) == controller_map.end()) { - auto controller = std::make_shared(guid, 0, sdl_controller); - controller_map[guid].emplace_back(std::move(controller)); - return; - } - auto& controller_guid_list = controller_map[guid]; - const auto it = std::find_if(controller_guid_list.begin(), controller_guid_list.end(), - [](const std::shared_ptr& controller) { - return !controller->GetSDLGameController(); - }); - if (it != controller_guid_list.end()) { - (*it)->SetSDLGameController(sdl_controller); - return; - } - auto controller = std::make_shared( - guid, static_cast(controller_guid_list.size()), sdl_controller); - controller_guid_list.emplace_back(std::move(controller)); -} - -void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { - std::string guid = GetGUID(sdl_joystick); - std::shared_ptr joystick; - { - std::lock_guard lock{joystick_map_mutex}; - // This call to guid is safe since the joystick is guaranteed to be in the map - auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = - std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [&sdl_joystick](const std::shared_ptr& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - joystick = *joystick_it; - } - // Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback - // which locks the mutex again - joystick->SetSDLJoystick(nullptr); -} - -void SDLState::CloseGameController(SDL_GameController* sdl_controller) { - std::string guid = GetGUID(SDL_GameControllerGetJoystick(sdl_controller)); - std::shared_ptr controller; - { - std::lock_guard lock{controller_map_mutex}; - auto& controller_guid_list = controller_map[guid]; - const auto controller_it = - std::find_if(controller_guid_list.begin(), controller_guid_list.end(), - [&sdl_controller](const std::shared_ptr& controller) { - return controller->GetSDLGameController() == sdl_controller; - }); - controller = *controller_it; - } - controller->SetSDLGameController(nullptr); -} - -void SDLState::HandleGameControllerEvent(const SDL_Event& event) { - switch (event.type) { - case SDL_JOYBUTTONUP: { - if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { - joystick->SetButton(event.jbutton.button, false); - } - break; - } - case SDL_JOYBUTTONDOWN: { - if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { - joystick->SetButton(event.jbutton.button, true); - } - break; - } - case SDL_JOYHATMOTION: { - if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { - joystick->SetHat(event.jhat.hat, event.jhat.value); - } - break; - } - case SDL_JOYAXISMOTION: { - if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { - joystick->SetAxis(event.jaxis.axis, event.jaxis.value); - } - break; - } -#if SDL_VERSION_ATLEAST(2, 0, 14) - case SDL_CONTROLLERSENSORUPDATE: { - if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { - switch (event.csensor.sensor) { - case SDL_SENSOR_ACCEL: - joystick->SetAccel(event.csensor.data[0] / SDL_STANDARD_GRAVITY, - -event.csensor.data[1] / SDL_STANDARD_GRAVITY, - event.csensor.data[2] / SDL_STANDARD_GRAVITY); - break; - case SDL_SENSOR_GYRO: - joystick->SetGyro(-event.csensor.data[0] * (180.0f / Common::PI), - event.csensor.data[1] * (180.0f / Common::PI), - -event.csensor.data[2] * (180.0f / Common::PI)); - break; - } - } - break; - } -#endif - case SDL_JOYDEVICEREMOVED: - LOG_DEBUG(Input, "Joystick removed with Instance_ID {}", event.jdevice.which); - CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); - break; - case SDL_JOYDEVICEADDED: - LOG_DEBUG(Input, "Joystick connected with device index {}", event.jdevice.which); - InitJoystick(event.jdevice.which); - break; - case SDL_CONTROLLERDEVICEREMOVED: - LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.cdevice.which); - CloseGameController(SDL_GameControllerFromInstanceID(event.cdevice.which)); - break; - case SDL_CONTROLLERDEVICEADDED: - LOG_DEBUG(Input, "Controller connected with device index {}", event.cdevice.which); - InitGameController(event.cdevice.which); - break; - } -} - -void SDLState::CloseJoysticks() { - std::lock_guard lock{joystick_map_mutex}; - joystick_map.clear(); -} - -void SDLState::CloseGameControllers() { - std::lock_guard lock{controller_map_mutex}; - controller_map.clear(); -} - -class SDLButton final : public Input::ButtonDevice { -public: - explicit SDLButton(std::shared_ptr joystick_, int button_) - : joystick(std::move(joystick_)), button(button_) {} - - bool GetStatus() const override { - return joystick->GetButton(button); - } - -private: - std::shared_ptr joystick; - int button; -}; - -class SDLDirectionButton final : public Input::ButtonDevice { -public: - explicit SDLDirectionButton(std::shared_ptr joystick_, int hat_, Uint8 direction_) - : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} - - bool GetStatus() const override { - return joystick->GetHatDirection(hat, direction); - } - -private: - std::shared_ptr joystick; - int hat; - Uint8 direction; -}; - -class SDLAxisButton final : public Input::ButtonDevice { -public: - explicit SDLAxisButton(std::shared_ptr joystick_, int axis_, float threshold_, - bool trigger_if_greater_) - : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), - trigger_if_greater(trigger_if_greater_) {} - - bool GetStatus() const override { - float axis_value = joystick->GetAxis(axis); - if (trigger_if_greater) - return axis_value > threshold; - return axis_value < threshold; - } - -private: - std::shared_ptr joystick; - int axis; - float threshold; - bool trigger_if_greater; -}; - -class SDLAnalog final : public Input::AnalogDevice { -public: - SDLAnalog(std::shared_ptr joystick_, int axis_x_, int axis_y_, float deadzone_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} - - std::tuple GetStatus() const override { - const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); - const float r = std::sqrt((x * x) + (y * y)); - if (r > deadzone) { - return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), - y / r * (r - deadzone) / (1 - deadzone)); - } - return std::make_tuple(0.0f, 0.0f); - } - -private: - std::shared_ptr joystick; - const int axis_x; - const int axis_y; - const float deadzone; -}; - -class SDLMotion final : public Input::MotionDevice { -public: - explicit SDLMotion(std::shared_ptr joystick_) : joystick(std::move(joystick_)) {} - - std::tuple, Common::Vec3> GetStatus() const override { - return joystick->GetMotion(); - } - -private: - std::shared_ptr joystick; -}; - -/// A button device factory that creates button devices from SDL joystick -class SDLButtonFactory final : public Input::Factory { -public: - explicit SDLButtonFactory(SDLState& state_) : state(state_) {} - - /** - * Creates a button device from a joystick button - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type to bind - * - "button"(optional): the index of the button to bind - * - "hat"(optional): the index of the hat to bind as direction buttons - * - "axis"(optional): the index of the axis to bind - * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", - * "down", "left" or "right" - * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is - * triggered if the axis value crosses - * - "direction"(only used for axis): "+" means the button is triggered when the axis - * value is greater than the threshold; "-" means the button is triggered when the axis - * value is smaller than the threshold - */ - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - if (params.Has("hat")) { - const int hat = params.Get("hat", 0); - const std::string direction_name = params.Get("direction", ""); - Uint8 direction; - if (direction_name == "up") { - direction = SDL_HAT_UP; - } else if (direction_name == "down") { - direction = SDL_HAT_DOWN; - } else if (direction_name == "left") { - direction = SDL_HAT_LEFT; - } else if (direction_name == "right") { - direction = SDL_HAT_RIGHT; - } else { - direction = 0; - } - // This is necessary so accessing GetHat with hat won't crash - joystick->SetHat(hat, SDL_HAT_CENTERED); - return std::make_unique(joystick, hat, direction); - } - - if (params.Has("axis")) { - const int axis = params.Get("axis", 0); - const float threshold = params.Get("threshold", 0.5f); - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction {}", direction_name); - } - // This is necessary so accessing GetAxis with axis won't crash - joystick->SetAxis(axis, 0); - return std::make_unique(joystick, axis, threshold, trigger_if_greater); - } - - const int button = params.Get("button", 0); - // This is necessary so accessing GetButton with button won't crash - joystick->SetButton(button, false); - return std::make_unique(joystick, button); - } - -private: - SDLState& state; -}; - -/// An analog device factory that creates analog devices from SDL joystick -class SDLAnalogFactory final : public Input::Factory { -public: - explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} - /** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - const int axis_x = params.Get("axis_x", 0); - const int axis_y = params.Get("axis_y", 1); - float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); - - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - // This is necessary so accessing GetAxis with axis_x and axis_y won't crash - joystick->SetAxis(axis_x, 0); - joystick->SetAxis(axis_y, 0); - return std::make_unique(joystick, axis_x, axis_y, deadzone); - } - -private: - SDLState& state; -}; - -class SDLMotionFactory final : public Input::Factory { -public: - explicit SDLMotionFactory(SDLState& state_) : state(state_) {} - - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - return std::make_unique(joystick); - } - -private: - SDLState& state; -}; - -SDLState::SDLState() { - using namespace Input; - RegisterFactory("sdl", std::make_shared(*this)); - RegisterFactory("sdl", std::make_shared(*this)); - RegisterFactory("sdl", std::make_shared(*this)); - - // If the frontend is going to manage the event loop, then we dont start one here - start_thread = !SDL_WasInit(SDL_INIT_GAMECONTROLLER); - if (start_thread && SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_GAMECONTROLLER) failed with: {}", SDL_GetError()); - return; - } - if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { - LOG_ERROR(Input, "Failed to set Hint for background events: {}", SDL_GetError()); - } -// these hints are only defined on sdl2.0.9 or higher -#if SDL_VERSION_ATLEAST(2, 0, 9) -#if !SDL_VERSION_ATLEAST(2, 0, 12) - // There are also hints to toggle the individual drivers if needed. - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "0"); -#endif -#endif - - SDL_AddEventWatch(&SDLEventWatcher, this); - - initialized = true; - if (start_thread) { - poll_thread = std::thread([this] { - using namespace std::chrono_literals; - while (initialized) { - SDL_PumpEvents(); - std::this_thread::sleep_for(10ms); - } - }); - } - // Because the events for joystick connection happens before we have our event watcher added, we - // can just open all the joysticks right here - for (int i = 0; i < SDL_NumJoysticks(); ++i) { - if (SDL_IsGameController(i)) { - InitGameController(i); - } - InitJoystick(i); - } -} - -SDLState::~SDLState() { - using namespace Input; - UnregisterFactory("sdl"); - UnregisterFactory("sdl"); - UnregisterFactory("sdl"); - - CloseJoysticks(); - CloseGameControllers(); - SDL_DelEventWatch(&SDLEventWatcher, this); - - initialized = false; - if (start_thread) { - poll_thread.join(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); - } -} - -Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { - Common::ParamPackage params({{"engine", "sdl"}}); - - switch (event.type) { - case SDL_JOYAXISMOTION: { - auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis", event.jaxis.axis); - if (event.jaxis.value > 0) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } else { - params.Set("direction", "-"); - params.Set("threshold", "-0.5"); - } - break; - } - case SDL_JOYBUTTONUP: { - auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("button", event.jbutton.button); - break; - } - case SDL_JOYHATMOTION: { - auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("hat", event.jhat.hat); - switch (event.jhat.value) { - case SDL_HAT_UP: - params.Set("direction", "up"); - break; - case SDL_HAT_DOWN: - params.Set("direction", "down"); - break; - case SDL_HAT_LEFT: - params.Set("direction", "left"); - break; - case SDL_HAT_RIGHT: - params.Set("direction", "right"); - break; - default: - return {}; - } - break; - } - } - return params; -} - -namespace Polling { - -class SDLPoller : public InputCommon::Polling::DevicePoller { -public: - explicit SDLPoller(SDLState& state_) : state(state_) {} - - void Start() override { - state.event_queue.Clear(); - state.polling = true; - } - - void Stop() override { - state.polling = false; - } - -protected: - SDLState& state; -}; - -class SDLButtonPoller final : public SDLPoller { -public: - explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {} - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (state.event_queue.Pop(event)) { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (!axis_memory.count(event.jaxis.which) || - !axis_memory[event.jaxis.which].count(event.jaxis.axis)) { - axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value; - axis_event_count[event.jaxis.which][event.jaxis.axis] = 1; - break; - } else { - axis_event_count[event.jaxis.which][event.jaxis.axis]++; - // The joystick and axis exist in our map if we take this branch, so no checks - // needed - if (std::abs( - (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) / - 32767.0) < 0.5) { - break; - } else { - if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 && - IsAxisAtPole(event.jaxis.value) && - IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) { - // If we have exactly two events and both are near a pole, this is - // likely a digital input masquerading as an analog axis; Instead of - // trying to look at the direction the axis travelled, assume the first - // event was press and the second was release; This should handle most - // digital axes while deferring to the direction of travel for analog - // axes - event.jaxis.value = static_cast(std::copysign( - 32767, axis_memory[event.jaxis.which][event.jaxis.axis])); - } else { - // There are more than two events, so this is likely a true analog axis, - // check the direction it travelled - event.jaxis.value = static_cast(std::copysign( - 32767, event.jaxis.value - - axis_memory[event.jaxis.which][event.jaxis.axis])); - } - axis_memory.clear(); - axis_event_count.clear(); - } - } - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - return SDLEventToButtonParamPackage(state, event); - } - } - return {}; - } - -private: - // Determine whether an axis value is close to an extreme or center - // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per - // axis, which is why the center must be considered a pole - bool IsAxisAtPole(int16_t value) { - return std::abs(value) >= 32767 || std::abs(value) < 327; - } - std::unordered_map> axis_memory; - std::unordered_map> axis_event_count; -}; - -class SDLAnalogPoller final : public SDLPoller { -public: - explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} - - void Start() override { - SDLPoller::Start(); - - // Reset stored axes - analog_xaxis = -1; - analog_yaxis = -1; - analog_axes_joystick = -1; - } - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (state.event_queue.Pop(event)) { - if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { - continue; - } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second SDL event. The axes also must be from the same joystick. - int axis = event.jaxis.axis; - if (analog_xaxis == -1) { - analog_xaxis = axis; - analog_axes_joystick = event.jaxis.which; - } else if (analog_yaxis == -1 && analog_xaxis != axis && - analog_axes_joystick == event.jaxis.which) { - analog_yaxis = axis; - } - } - Common::ParamPackage params; - if (analog_xaxis != -1 && analog_yaxis != -1) { - auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("engine", "sdl"); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis_x", analog_xaxis); - params.Set("axis_y", analog_yaxis); - analog_xaxis = -1; - analog_yaxis = -1; - analog_axes_joystick = -1; - return params; - } - return params; - } - -private: - int analog_xaxis = -1; - int analog_yaxis = -1; - SDL_JoystickID analog_axes_joystick = -1; -}; -} // namespace Polling - -SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) { - Pollers pollers; - - switch (type) { - case InputCommon::Polling::DeviceType::Analog: - pollers.emplace_back(std::make_unique(*this)); - break; - case InputCommon::Polling::DeviceType::Button: - pollers.emplace_back(std::make_unique(*this)); - break; - } - - return pollers; -} - -} // namespace SDL -} // namespace InputCommon diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h deleted file mode 100644 index 0ce094b62..000000000 --- a/src/input_common/sdl/sdl_impl.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "common/settings.h" -#include "common/threadsafe_queue.h" -#include "input_common/sdl/sdl.h" - -union SDL_Event; -using SDL_Joystick = struct _SDL_Joystick; -using SDL_JoystickID = s32; -using SDL_GameController = struct _SDL_GameController; - -namespace InputCommon::SDL { - -class SDLJoystick; -class SDLGameController; -class SDLButtonFactory; -class SDLAnalogFactory; -class SDLMotionFactory; - -class SDLState : public State { -public: - /// Initializes and registers SDL device factories - SDLState(); - - /// Unregisters SDL device factories and shut them down. - ~SDLState() override; - - /// Handle SDL_Events for joysticks from SDL_PollEvent - void HandleGameControllerEvent(const SDL_Event& event); - - std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); - std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port); - - std::shared_ptr GetSDLGameControllerByGUID(const std::string& guid, - int port); - - Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port, - Settings::NativeButton::Values button); - Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port, - Settings::NativeAnalog::Values analog); - - /// Get all DevicePoller that use the SDL backend for a specific device type - Pollers GetPollers(Polling::DeviceType type) override; - - /// Used by the Pollers during config - std::atomic polling = false; - Common::SPSCQueue event_queue; - -private: - void InitJoystick(int joystick_index); - void CloseJoystick(SDL_Joystick* sdl_joystick); - - void InitGameController(int joystick_index); - void CloseGameController(SDL_GameController* sdl_controller); - - /// Needs to be called before SDL_QuitSubSystem. - void CloseJoysticks(); - void CloseGameControllers(); - - /// Map of GUID of a list of corresponding virtual Joysticks - std::unordered_map>> joystick_map; - std::mutex joystick_map_mutex; - - /// Map of GUID of a list of corresponding virtual Controllers - std::unordered_map>> controller_map; - std::mutex controller_map_mutex; - - std::shared_ptr button_factory; - std::shared_ptr analog_factory; - std::shared_ptr motion_factory; - - bool start_thread = false; - std::atomic initialized = false; - - std::thread poll_thread; -}; -} // namespace InputCommon::SDL diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp deleted file mode 100644 index 9cd6c0204..000000000 --- a/src/input_common/touch_from_button.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/settings.h" -#include "core/3ds.h" -#include "input_common/touch_from_button.h" - -namespace InputCommon { - -class TouchFromButtonDevice final : public Input::TouchDevice { -public: - TouchFromButtonDevice() { - for (const auto& config_entry : - Settings::values - .touch_from_button_maps[Settings::values.current_input_profile - .touch_from_button_map_index] - .buttons) { - - const Common::ParamPackage package{config_entry}; - map.emplace_back(Input::CreateDevice(config_entry), - std::clamp(package.Get("x", 0), 0, Core::kScreenBottomWidth), - std::clamp(package.Get("y", 0), 0, Core::kScreenBottomHeight)); - } - } - - std::tuple GetStatus() const override { - for (const auto& m : map) { - const bool state = std::get<0>(m)->GetStatus(); - if (state) { - const float x = static_cast(std::get<1>(m)) / Core::kScreenBottomWidth; - const float y = static_cast(std::get<2>(m)) / Core::kScreenBottomHeight; - return {x, y, true}; - } - } - return {}; - } - -private: - // A vector of the mapped button, its x and its y-coordinate - std::vector, int, int>> map; -}; - -std::unique_ptr TouchFromButtonFactory::Create( - const Common::ParamPackage& params) { - return std::make_unique(); -} - -} // namespace InputCommon diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h deleted file mode 100644 index 8b4d1aa96..000000000 --- a/src/input_common/touch_from_button.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/frontend/input.h" - -namespace InputCommon { - -/** - * A touch device factory that takes a list of button devices and combines them into a touch device. - */ -class TouchFromButtonFactory final : public Input::Factory { -public: - /** - * Creates a touch device from a list of button devices - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; -}; - -} // namespace InputCommon diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp deleted file mode 100644 index 7b14dbc76..000000000 --- a/src/input_common/udp/client.cpp +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include -#include "common/logging/log.h" -#include "input_common/udp/client.h" -#include "input_common/udp/protocol.h" - -using boost::asio::ip::udp; - -namespace InputCommon::CemuhookUDP { - -struct SocketCallback { - std::function version; - std::function port_info; - std::function pad_data; -}; - -class Socket { -public: - using clock = std::chrono::system_clock; - - explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, - SocketCallback callback) - : callback(std::move(callback)), timer(io_service), - socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id), - pad_index(pad_index) { - boost::system::error_code ec{}; - auto ipv4 = boost::asio::ip::make_address_v4(host, ec); - if (ec.value() != boost::system::errc::success) { - LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host); - ipv4 = boost::asio::ip::address_v4{}; - } - - send_endpoint = {udp::endpoint(ipv4, port)}; - } - - void Stop() { - io_service.stop(); - } - - void Loop() { - io_service.run(); - } - - void StartSend(const clock::time_point& from) { - timer.expires_at(from + std::chrono::seconds(3)); - timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); - } - - void StartReceive() { - socket.async_receive_from( - boost::asio::buffer(receive_buffer), receive_endpoint, - [this](const boost::system::error_code& error, std::size_t bytes_transferred) { - HandleReceive(error, bytes_transferred); - }); - } - -private: - void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) { - if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { - switch (*type) { - case Type::Version: { - Response::Version version; - std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); - callback.version(std::move(version)); - break; - } - case Type::PortInfo: { - Response::PortInfo port_info; - std::memcpy(&port_info, &receive_buffer[sizeof(Header)], - sizeof(Response::PortInfo)); - callback.port_info(std::move(port_info)); - break; - } - case Type::PadData: { - Response::PadData pad_data; - std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); - callback.pad_data(std::move(pad_data)); - break; - } - } - } - StartReceive(); - } - - void HandleSend(const boost::system::error_code& error) { - boost::system::error_code _ignored{}; - // Send a request for getting port info for the pad - Request::PortInfo port_info{1, {pad_index, 0, 0, 0}}; - const auto port_message = Request::Create(port_info, client_id); - std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); - socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); - - // Send a request for getting pad data for the pad - Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS}; - const auto pad_message = Request::Create(pad_data, client_id); - std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); - socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); - StartSend(timer.expiry()); - } - - SocketCallback callback; - boost::asio::io_service io_service; - boost::asio::basic_waitable_timer timer; - udp::socket socket; - - u32 client_id{}; - u8 pad_index{}; - - static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); - static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); - std::array send_buffer1; - std::array send_buffer2; - udp::endpoint send_endpoint; - - std::array receive_buffer; - udp::endpoint receive_endpoint; -}; - -static void SocketLoop(Socket* socket) { - socket->StartReceive(); - socket->StartSend(Socket::clock::now()); - socket->Loop(); -} - -Client::Client(std::shared_ptr status, const std::string& host, u16 port, - u8 pad_index, u32 client_id) - : status(std::move(status)) { - StartCommunication(host, port, pad_index, client_id); -} - -Client::~Client() { - socket->Stop(); - thread.join(); -} - -void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { - socket->Stop(); - thread.join(); - StartCommunication(host, port, pad_index, client_id); -} - -void Client::OnVersion(Response::Version data) { - LOG_TRACE(Input, "Version packet received: {}", data.version); -} - -void Client::OnPortInfo(Response::PortInfo data) { - LOG_TRACE(Input, "PortInfo packet received: {}", data.model); -} - -void Client::OnPadData(Response::PadData data) { - LOG_TRACE(Input, "PadData packet received"); - if (data.packet_counter <= packet_sequence) { - LOG_WARNING( - Input, - "PadData packet dropped because its stale info. Current count: {} Packet count: {}", - packet_sequence, data.packet_counter); - return; - } - packet_sequence = data.packet_counter; - // Due to differences between the 3ds and cemuhookudp motion directions, we need to invert - // accel.x and accel.z and also invert pitch and yaw. See - // https://github.com/citra-emu/citra/pull/4049 for more details on gyro/accel - Common::Vec3f accel = Common::MakeVec(-data.accel.x, data.accel.y, -data.accel.z); - Common::Vec3f gyro = Common::MakeVec(-data.gyro.pitch, -data.gyro.yaw, data.gyro.roll); - { - std::lock_guard guard(status->update_mutex); - - status->motion_status = {accel, gyro}; - - // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates - // between a simple "tap" and a hard press that causes the touch screen to click. - const bool is_active = data.touch_1.is_active != 0; - - float x = 0; - float y = 0; - - if (is_active && status->touch_calibration) { - const u16 min_x = status->touch_calibration->min_x; - const u16 max_x = status->touch_calibration->max_x; - const u16 min_y = status->touch_calibration->min_y; - const u16 max_y = status->touch_calibration->max_y; - - x = (std::clamp(static_cast(data.touch_1.x), min_x, max_x) - min_x) / - static_cast(max_x - min_x); - y = (std::clamp(static_cast(data.touch_1.y), min_y, max_y) - min_y) / - static_cast(max_y - min_y); - } - - status->touch_status = {x, y, is_active}; - } -} - -void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { - SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, - [this](Response::PortInfo info) { OnPortInfo(info); }, - [this](Response::PadData data) { OnPadData(data); }}; - LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); - socket = std::make_unique(host, port, pad_index, client_id, callback); - thread = std::thread{SocketLoop, this->socket.get()}; -} - -void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, - const std::function& success_callback, - const std::function& failure_callback) { - std::thread([=] { - Common::Event success_event; - SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, - [&](Response::PadData data) { success_event.Set(); }}; - Socket socket{host, port, pad_index, client_id, std::move(callback)}; - std::thread worker_thread{SocketLoop, &socket}; - bool result = success_event.WaitFor(std::chrono::seconds(8)); - socket.Stop(); - worker_thread.join(); - if (result) { - success_callback(); - } else { - failure_callback(); - } - }).detach(); -} - -CalibrationConfigurationJob::CalibrationConfigurationJob( - const std::string& host, u16 port, u8 pad_index, u32 client_id, - std::function status_callback, - std::function data_callback) { - - std::thread([=, this] { - u16 min_x{UINT16_MAX}; - u16 min_y{UINT16_MAX}; - u16 max_x{}; - u16 max_y{}; - - Status current_status{Status::Initialized}; - SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, - [&](Response::PadData data) { - constexpr u16 CALIBRATION_THRESHOLD = 100; - - if (current_status == Status::Initialized) { - // Receiving data means the communication is ready now - current_status = Status::Ready; - status_callback(current_status); - } - if (!data.touch_1.is_active) { - return; - } - LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x, - data.touch_1.y); - min_x = std::min(min_x, static_cast(data.touch_1.x)); - min_y = std::min(min_y, static_cast(data.touch_1.y)); - if (current_status == Status::Ready) { - // First touch - min data (min_x/min_y) - current_status = Status::Stage1Completed; - status_callback(current_status); - } - if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD && - data.touch_1.y - min_y > CALIBRATION_THRESHOLD) { - // Set the current position as max value and finishes - // configuration - max_x = data.touch_1.x; - max_y = data.touch_1.y; - current_status = Status::Completed; - data_callback(min_x, min_y, max_x, max_y); - status_callback(current_status); - - complete_event.Set(); - } - }}; - Socket socket{host, port, pad_index, client_id, std::move(callback)}; - std::thread worker_thread{SocketLoop, &socket}; - complete_event.Wait(); - socket.Stop(); - worker_thread.join(); - }).detach(); -} - -CalibrationConfigurationJob::~CalibrationConfigurationJob() { - Stop(); -} - -void CalibrationConfigurationJob::Stop() { - complete_event.Set(); -} - -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h deleted file mode 100644 index e049d4039..000000000 --- a/src/input_common/udp/client.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "common/thread.h" -#include "common/vector_math.h" - -namespace InputCommon::CemuhookUDP { - -constexpr u16 DEFAULT_PORT = 26760; -constexpr char DEFAULT_ADDR[] = "127.0.0.1"; - -class Socket; - -namespace Response { -struct PadData; -struct PortInfo; -struct Version; -} // namespace Response - -struct DeviceStatus { - std::mutex update_mutex; - std::tuple, Common::Vec3> motion_status; - std::tuple touch_status; - - // calibration data for scaling the device's touch area to 3ds - struct CalibrationData { - u16 min_x{}; - u16 min_y{}; - u16 max_x{}; - u16 max_y{}; - }; - std::optional touch_calibration; -}; - -class Client { -public: - explicit Client(std::shared_ptr status, const std::string& host = DEFAULT_ADDR, - u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); - ~Client(); - void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, - u32 client_id = 24872); - -private: - void OnVersion(Response::Version); - void OnPortInfo(Response::PortInfo); - void OnPadData(Response::PadData); - void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); - - std::unique_ptr socket; - std::shared_ptr status; - std::thread thread; - u64 packet_sequence = 0; -}; - -/// An async job allowing configuration of the touchpad calibration. -class CalibrationConfigurationJob { -public: - enum class Status { - Initialized, - Ready, - Stage1Completed, - Completed, - }; - /** - * Constructs and starts the job with the specified parameter. - * - * @param status_callback Callback for job status updates - * @param data_callback Called when calibration data is ready - */ - explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index, - u32 client_id, std::function status_callback, - std::function data_callback); - ~CalibrationConfigurationJob(); - void Stop(); - -private: - Common::Event complete_event; -}; - -void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, - const std::function& success_callback, - const std::function& failure_callback); - -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp deleted file mode 100644 index 89e114fee..000000000 --- a/src/input_common/udp/udp.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include "common/param_package.h" -#include "common/settings.h" -#include "core/frontend/input.h" -#include "input_common/udp/client.h" -#include "input_common/udp/udp.h" - -namespace InputCommon::CemuhookUDP { - -class UDPTouchDevice final : public Input::TouchDevice { -public: - explicit UDPTouchDevice(std::shared_ptr status_) : status(std::move(status_)) {} - std::tuple GetStatus() const override { - std::lock_guard guard(status->update_mutex); - return status->touch_status; - } - -private: - std::shared_ptr status; -}; - -class UDPMotionDevice final : public Input::MotionDevice { -public: - explicit UDPMotionDevice(std::shared_ptr status_) : status(std::move(status_)) {} - std::tuple, Common::Vec3> GetStatus() const override { - std::lock_guard guard(status->update_mutex); - return status->motion_status; - } - -private: - std::shared_ptr status; -}; - -class UDPTouchFactory final : public Input::Factory { -public: - explicit UDPTouchFactory(std::shared_ptr status_) : status(std::move(status_)) {} - - std::unique_ptr Create(const Common::ParamPackage& params) override { - { - std::lock_guard guard(status->update_mutex); - status->touch_calibration = DeviceStatus::CalibrationData{}; - // These default values work well for DS4 but probably not other touch inputs - status->touch_calibration->min_x = params.Get("min_x", 100); - status->touch_calibration->min_y = params.Get("min_y", 50); - status->touch_calibration->max_x = params.Get("max_x", 1800); - status->touch_calibration->max_y = params.Get("max_y", 850); - } - return std::make_unique(status); - } - -private: - std::shared_ptr status; -}; - -class UDPMotionFactory final : public Input::Factory { -public: - explicit UDPMotionFactory(std::shared_ptr status_) : status(std::move(status_)) {} - - std::unique_ptr Create(const Common::ParamPackage& params) override { - return std::make_unique(status); - } - -private: - std::shared_ptr status; -}; - -State::State() { - auto status = std::make_shared(); - client = - std::make_unique(status, Settings::values.current_input_profile.udp_input_address, - Settings::values.current_input_profile.udp_input_port, - Settings::values.current_input_profile.udp_pad_index); - - Input::RegisterFactory("cemuhookudp", - std::make_shared(status)); - Input::RegisterFactory("cemuhookudp", - std::make_shared(status)); -} - -State::~State() { - Input::UnregisterFactory("cemuhookudp"); - Input::UnregisterFactory("cemuhookudp"); -} - -void State::ReloadUDPClient() { - client->ReloadSocket(Settings::values.current_input_profile.udp_input_address, - Settings::values.current_input_profile.udp_input_port, - Settings::values.current_input_profile.udp_pad_index); -} - -std::unique_ptr Init() { - return std::make_unique(); -} -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h deleted file mode 100644 index 3eac8c7ea..000000000 --- a/src/input_common/udp/udp.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "input_common/udp/client.h" - -namespace InputCommon::CemuhookUDP { - -class State { -public: - State(); - ~State(); - void ReloadUDPClient(); - -private: - std::unique_ptr client; -}; - -std::unique_ptr Init(); - -} // namespace InputCommon::CemuhookUDP