diff options
Diffstat (limited to 'src/input_common/drivers')
-rw-r--r-- | src/input_common/drivers/camera.cpp | 4 | ||||
-rw-r--r-- | src/input_common/drivers/camera.h | 4 | ||||
-rw-r--r-- | src/input_common/drivers/gc_adapter.cpp | 12 | ||||
-rw-r--r-- | src/input_common/drivers/gc_adapter.h | 2 | ||||
-rw-r--r-- | src/input_common/drivers/joycon.cpp | 844 | ||||
-rw-r--r-- | src/input_common/drivers/joycon.h | 125 | ||||
-rw-r--r-- | src/input_common/drivers/keyboard.cpp | 2 | ||||
-rw-r--r-- | src/input_common/drivers/mouse.cpp | 231 | ||||
-rw-r--r-- | src/input_common/drivers/mouse.h | 43 | ||||
-rw-r--r-- | src/input_common/drivers/sdl_driver.cpp | 129 | ||||
-rw-r--r-- | src/input_common/drivers/sdl_driver.h | 2 | ||||
-rw-r--r-- | src/input_common/drivers/virtual_amiibo.cpp | 179 | ||||
-rw-r--r-- | src/input_common/drivers/virtual_amiibo.h | 23 | ||||
-rw-r--r-- | src/input_common/drivers/virtual_gamepad.cpp | 16 | ||||
-rw-r--r-- | src/input_common/drivers/virtual_gamepad.h | 12 |
15 files changed, 1487 insertions, 141 deletions
diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp index fad9177dc..04970f635 100644 --- a/src/input_common/drivers/camera.cpp +++ b/src/input_common/drivers/camera.cpp @@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const { } } -Common::Input::CameraError Camera::SetCameraFormat( +Common::Input::DriverResult Camera::SetCameraFormat( [[maybe_unused]] const PadIdentifier& identifier_, const Common::Input::CameraFormat camera_format) { status.format = camera_format; - return Common::Input::CameraError::None; + return Common::Input::DriverResult::Success; } } // namespace InputCommon diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h index ead3e0fde..24b27e325 100644 --- a/src/input_common/drivers/camera.h +++ b/src/input_common/drivers/camera.h @@ -22,8 +22,8 @@ public: std::size_t getImageWidth() const; std::size_t getImageHeight() const; - Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, - Common::Input::CameraFormat camera_format) override; + Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_, + Common::Input::CameraFormat camera_format) override; private: Common::Input::CameraStatus status{}; diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 826fa2109..3ad34884d 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -6,6 +6,7 @@ #include "common/logging/log.h" #include "common/param_package.h" +#include "common/polyfill_thread.h" #include "common/settings_input.h" #include "common/thread.h" #include "input_common/drivers/gc_adapter.h" @@ -217,8 +218,7 @@ 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)); + while (!Setup() && Common::StoppableTimedWait(stop_token, std::chrono::seconds{2})) { } } @@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) { return true; } -Common::Input::VibrationError GCAdapter::SetVibration( +Common::Input::DriverResult 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 = @@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration( pads[identifier.port].rumble_amplitude = processed_amplitude; if (!rumble_enabled) { - return Common::Input::VibrationError::Disabled; + return Common::Input::DriverResult::Disabled; } - return Common::Input::VibrationError::None; + return Common::Input::DriverResult::Success; } bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { @@ -344,7 +344,7 @@ bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identif void GCAdapter::UpdateVibrations() { // Use 8 states to keep the switching between on/off fast enough for - // a human to feel different vibration strenght + // a human to feel different vibration strength // More states == more rumble strengths == slower update time constexpr u8 vibration_states = 8; diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index b5270fd0b..3c2eb376d 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -25,7 +25,7 @@ public: explicit GCAdapter(std::string input_engine_); ~GCAdapter() override; - Common::Input::VibrationError SetVibration( + Common::Input::DriverResult SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; bool IsVibrationEnabled(const PadIdentifier& identifier) override; diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp new file mode 100644 index 000000000..52494e0d9 --- /dev/null +++ b/src/input_common/drivers/joycon.cpp @@ -0,0 +1,844 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <fmt/format.h> + +#include "common/param_package.h" +#include "common/polyfill_ranges.h" +#include "common/polyfill_thread.h" +#include "common/settings.h" +#include "common/thread.h" +#include "input_common/drivers/joycon.h" +#include "input_common/helpers/joycon_driver.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon { + +Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) { + // Avoid conflicting with SDL driver + if (!Settings::values.enable_joycon_driver && !Settings::values.enable_procon_driver) { + return; + } + LOG_INFO(Input, "Joycon driver Initialization started"); + const int init_res = SDL_hid_init(); + if (init_res == 0) { + Setup(); + } else { + LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res); + } +} + +Joycons::~Joycons() { + Reset(); +} + +void Joycons::Reset() { + scan_thread = {}; + for (const auto& device : left_joycons) { + if (!device) { + continue; + } + device->Stop(); + } + for (const auto& device : right_joycons) { + if (!device) { + continue; + } + device->Stop(); + } + for (const auto& device : pro_controller) { + if (!device) { + continue; + } + device->Stop(); + } + SDL_hid_exit(); +} + +void Joycons::Setup() { + u32 port = 0; + PreSetController(GetIdentifier(0, Joycon::ControllerType::None)); + for (auto& device : left_joycons) { + PreSetController(GetIdentifier(port, Joycon::ControllerType::Left)); + device = std::make_shared<Joycon::JoyconDriver>(port++); + } + port = 0; + for (auto& device : right_joycons) { + PreSetController(GetIdentifier(port, Joycon::ControllerType::Right)); + device = std::make_shared<Joycon::JoyconDriver>(port++); + } + port = 0; + for (auto& device : pro_controller) { + PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro)); + device = std::make_shared<Joycon::JoyconDriver>(port++); + } + + scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); }); +} + +void Joycons::ScanThread(std::stop_token stop_token) { + constexpr u16 nintendo_vendor_id = 0x057e; + Common::SetCurrentThreadName("JoyconScanThread"); + + do { + SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0); + SDL_hid_device_info* cur_dev = devs; + + while (cur_dev) { + if (IsDeviceNew(cur_dev)) { + LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id, + cur_dev->product_id); + RegisterNewDevice(cur_dev); + } + cur_dev = cur_dev->next; + } + + SDL_hid_free_enumeration(devs); + } while (Common::StoppableTimedWait(stop_token, std::chrono::seconds{5})); +} + +bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const { + Joycon::ControllerType type{}; + Joycon::SerialNumber serial_number{}; + + const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); + if (result != Joycon::DriverResult::Success) { + return false; + } + + const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number); + if (result2 != Joycon::DriverResult::Success) { + return false; + } + + auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) { + if (!device) { + return false; + } + if (!device->IsConnected()) { + return false; + } + if (device->GetHandleSerialNumber() != serial_number) { + return false; + } + return true; + }; + + // Check if device already exist + switch (type) { + case Joycon::ControllerType::Left: + if (!Settings::values.enable_joycon_driver) { + return false; + } + for (const auto& device : left_joycons) { + if (is_handle_identical(device)) { + return false; + } + } + break; + case Joycon::ControllerType::Right: + if (!Settings::values.enable_joycon_driver) { + return false; + } + for (const auto& device : right_joycons) { + if (is_handle_identical(device)) { + return false; + } + } + break; + case Joycon::ControllerType::Pro: + if (!Settings::values.enable_procon_driver) { + return false; + } + for (const auto& device : pro_controller) { + if (is_handle_identical(device)) { + return false; + } + } + break; + default: + return false; + } + + return true; +} + +void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { + Joycon::ControllerType type{}; + auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); + auto handle = GetNextFreeHandle(type); + if (handle == nullptr) { + LOG_WARNING(Input, "No free handles available"); + return; + } + if (result == Joycon::DriverResult::Success) { + result = handle->RequestDeviceAccess(device_info); + } + if (result == Joycon::DriverResult::Success) { + LOG_WARNING(Input, "Initialize device"); + + const std::size_t port = handle->GetDevicePort(); + const Joycon::JoyconCallbacks callbacks{ + .on_battery_data = {[this, port, type](Joycon::Battery value) { + OnBatteryUpdate(port, type, value); + }}, + .on_color_data = {[this, port, type](Joycon::Color value) { + OnColorUpdate(port, type, value); + }}, + .on_button_data = {[this, port, type](int id, bool value) { + OnButtonUpdate(port, type, id, value); + }}, + .on_stick_data = {[this, port, type](int id, f32 value) { + OnStickUpdate(port, type, id, value); + }}, + .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) { + OnMotionUpdate(port, type, id, value); + }}, + .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, + .on_amiibo_data = {[this, port, type](const Joycon::TagInfo& tag_info) { + OnAmiiboUpdate(port, type, tag_info); + }}, + .on_camera_data = {[this, port](const std::vector<u8>& camera_data, + Joycon::IrsResolution format) { + OnCameraUpdate(port, camera_data, format); + }}, + }; + + handle->InitializeDevice(); + handle->SetCallbacks(callbacks); + } +} + +std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle( + Joycon::ControllerType type) const { + if (type == Joycon::ControllerType::Left) { + const auto unconnected_device = + std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); }); + if (unconnected_device != left_joycons.end()) { + return *unconnected_device; + } + } + if (type == Joycon::ControllerType::Right) { + const auto unconnected_device = std::ranges::find_if( + right_joycons, [](auto& device) { return !device->IsConnected(); }); + + if (unconnected_device != right_joycons.end()) { + return *unconnected_device; + } + } + if (type == Joycon::ControllerType::Pro) { + const auto unconnected_device = std::ranges::find_if( + pro_controller, [](auto& device) { return !device->IsConnected(); }); + + if (unconnected_device != pro_controller.end()) { + return *unconnected_device; + } + } + return nullptr; +} + +bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) { + const auto handle = GetHandle(identifier); + if (handle == nullptr) { + return false; + } + return handle->IsVibrationEnabled(); +} + +Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier, + const Common::Input::VibrationStatus& vibration) { + const Joycon::VibrationValue native_vibration{ + .low_amplitude = vibration.low_amplitude, + .low_frequency = vibration.low_frequency, + .high_amplitude = vibration.high_amplitude, + .high_frequency = vibration.high_frequency, + }; + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::DriverResult::InvalidHandle; + } + + handle->SetVibration(native_vibration); + return Common::Input::DriverResult::Success; +} + +Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier, + const Common::Input::LedStatus& led_status) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::DriverResult::InvalidHandle; + } + int led_config = led_status.led_1 ? 1 : 0; + led_config += led_status.led_2 ? 2 : 0; + led_config += led_status.led_3 ? 4 : 0; + led_config += led_status.led_4 ? 8 : 0; + + return static_cast<Common::Input::DriverResult>( + handle->SetLedConfig(static_cast<u8>(led_config))); +} + +Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier, + Common::Input::CameraFormat camera_format) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::DriverResult::InvalidHandle; + } + return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig( + Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format))); +}; + +Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const { + return Common::Input::NfcState::Success; +}; + +Common::Input::NfcState Joycons::StartNfcPolling(const PadIdentifier& identifier) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + return TranslateDriverResult(handle->StartNfcPolling()); +}; + +Common::Input::NfcState Joycons::StopNfcPolling(const PadIdentifier& identifier) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + return TranslateDriverResult(handle->StopNfcPolling()); +}; + +Common::Input::NfcState Joycons::ReadAmiiboData(const PadIdentifier& identifier, + std::vector<u8>& out_data) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + return TranslateDriverResult(handle->ReadAmiiboData(out_data)); +} + +Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier, + const std::vector<u8>& data) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + return TranslateDriverResult(handle->WriteNfcData(data)); +}; + +Common::Input::NfcState Joycons::ReadMifareData(const PadIdentifier& identifier, + const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& data) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + + const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command); + std::vector<Joycon::MifareReadChunk> read_request{}; + for (const auto& request_data : request.data) { + if (request_data.command == 0) { + continue; + } + Joycon::MifareReadChunk chunk = { + .command = command, + .sector_key = {}, + .sector = request_data.sector, + }; + memcpy(chunk.sector_key.data(), request_data.key.data(), + sizeof(Joycon::MifareReadChunk::sector_key)); + read_request.emplace_back(chunk); + } + + std::vector<Joycon::MifareReadData> read_data(read_request.size()); + const auto result = handle->ReadMifareData(read_request, read_data); + if (result == Joycon::DriverResult::Success) { + for (std::size_t i = 0; i < read_request.size(); i++) { + data.data[i] = { + .command = static_cast<u8>(command), + .sector = read_data[i].sector, + .key = {}, + .data = read_data[i].data, + }; + } + } + return TranslateDriverResult(result); +}; + +Common::Input::NfcState Joycons::WriteMifareData(const PadIdentifier& identifier, + const Common::Input::MifareRequest& request) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + + const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command); + std::vector<Joycon::MifareWriteChunk> write_request{}; + for (const auto& request_data : request.data) { + if (request_data.command == 0) { + continue; + } + Joycon::MifareWriteChunk chunk = { + .command = command, + .sector_key = {}, + .sector = request_data.sector, + .data = {}, + }; + memcpy(chunk.sector_key.data(), request_data.key.data(), + sizeof(Joycon::MifareReadChunk::sector_key)); + memcpy(chunk.data.data(), request_data.data.data(), sizeof(Joycon::MifareWriteChunk::data)); + write_request.emplace_back(chunk); + } + + return TranslateDriverResult(handle->WriteMifareData(write_request)); +}; + +Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, + const Common::Input::PollingMode polling_mode) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + LOG_ERROR(Input, "Invalid handle {}", identifier.port); + return Common::Input::DriverResult::InvalidHandle; + } + + switch (polling_mode) { + case Common::Input::PollingMode::Active: + return static_cast<Common::Input::DriverResult>(handle->SetActiveMode()); + case Common::Input::PollingMode::Passive: + return static_cast<Common::Input::DriverResult>(handle->SetPassiveMode()); + case Common::Input::PollingMode::IR: + return static_cast<Common::Input::DriverResult>(handle->SetIrMode()); + case Common::Input::PollingMode::NFC: + return static_cast<Common::Input::DriverResult>(handle->SetNfcMode()); + case Common::Input::PollingMode::Ring: + return static_cast<Common::Input::DriverResult>(handle->SetRingConMode()); + default: + return Common::Input::DriverResult::NotSupported; + } +} + +void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, + Joycon::Battery value) { + const auto identifier = GetIdentifier(port, type); + if (value.charging != 0) { + SetBattery(identifier, Common::Input::BatteryLevel::Charging); + return; + } + + Common::Input::BatteryLevel battery{}; + switch (value.status) { + case 0: + battery = Common::Input::BatteryLevel::Empty; + break; + case 1: + battery = Common::Input::BatteryLevel::Critical; + break; + case 2: + battery = Common::Input::BatteryLevel::Low; + break; + case 3: + battery = Common::Input::BatteryLevel::Medium; + break; + case 4: + default: + battery = Common::Input::BatteryLevel::Full; + break; + } + SetBattery(identifier, battery); +} + +void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type, + const Joycon::Color& value) { + const auto identifier = GetIdentifier(port, type); + Common::Input::BodyColorStatus color{ + .body = value.body, + .buttons = value.buttons, + .left_grip = value.left_grip, + .right_grip = value.right_grip, + }; + SetColor(identifier, color); +} + +void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) { + const auto identifier = GetIdentifier(port, type); + SetButton(identifier, id, value); +} + +void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) { + const auto identifier = GetIdentifier(port, type); + SetAxis(identifier, id, value); +} + +void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, + const Joycon::MotionData& value) { + const auto identifier = GetIdentifier(port, type); + BasicMotion motion_data{ + .gyro_x = value.gyro_x, + .gyro_y = value.gyro_y, + .gyro_z = value.gyro_z, + .accel_x = value.accel_x, + .accel_y = value.accel_y, + .accel_z = value.accel_z, + .delta_timestamp = 15000, + }; + SetMotion(identifier, id, motion_data); +} + +void Joycons::OnRingConUpdate(f32 ring_data) { + // To simplify ring detection it will always be mapped to an empty identifier for all + // controllers + static constexpr PadIdentifier identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, + }; + SetAxis(identifier, 100, ring_data); +} + +void Joycons::OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type, + const Joycon::TagInfo& tag_info) { + const auto identifier = GetIdentifier(port, type); + const auto nfc_state = tag_info.uuid_length == 0 ? Common::Input::NfcState::AmiiboRemoved + : Common::Input::NfcState::NewAmiibo; + + const Common::Input::NfcStatus nfc_status{ + .state = nfc_state, + .uuid_length = tag_info.uuid_length, + .protocol = tag_info.protocol, + .tag_type = tag_info.tag_type, + .uuid = tag_info.uuid, + }; + + SetNfc(identifier, nfc_status); +} + +void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, + Joycon::IrsResolution format) { + const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); + SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data}); +} + +std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const { + auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) { + if (!device) { + return false; + } + if (!device->IsConnected()) { + return false; + } + if (device->GetDevicePort() == identifier.port) { + return true; + } + return false; + }; + const auto type = static_cast<Joycon::ControllerType>(identifier.pad); + + if (type == Joycon::ControllerType::Left) { + const auto matching_device = std::ranges::find_if( + left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); + + if (matching_device != left_joycons.end()) { + return *matching_device; + } + } + + if (type == Joycon::ControllerType::Right) { + const auto matching_device = std::ranges::find_if( + right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); + + if (matching_device != right_joycons.end()) { + return *matching_device; + } + } + + if (type == Joycon::ControllerType::Pro) { + const auto matching_device = std::ranges::find_if( + pro_controller, [is_handle_active](auto& device) { return is_handle_active(device); }); + + if (matching_device != pro_controller.end()) { + return *matching_device; + } + } + + return nullptr; +} + +PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const { + const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)}; + return { + .guid = Common::UUID{guid}, + .port = port, + .pad = static_cast<std::size_t>(type), + }; +} + +Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const { + const auto identifier = GetIdentifier(port, type); + return { + {"engine", GetEngineName()}, + {"guid", identifier.guid.RawString()}, + {"port", std::to_string(identifier.port)}, + {"pad", std::to_string(identifier.pad)}, + }; +} + +std::vector<Common::ParamPackage> Joycons::GetInputDevices() const { + std::vector<Common::ParamPackage> devices{}; + + auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) { + if (!device) { + return; + } + if (!device->IsConnected()) { + return; + } + auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType()); + std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()), + device->GetDevicePort() + 1); + param.Set("display", std::move(name)); + devices.emplace_back(param); + }; + + for (const auto& controller : left_joycons) { + add_entry(controller); + } + for (const auto& controller : right_joycons) { + add_entry(controller); + } + for (const auto& controller : pro_controller) { + add_entry(controller); + } + + // List dual joycon pairs + for (std::size_t i = 0; i < MaxSupportedControllers; i++) { + if (!left_joycons[i] || !right_joycons[i]) { + continue; + } + if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) { + continue; + } + auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType()); + const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType()); + const auto type = Joycon::ControllerType::Dual; + std::string name = fmt::format("{} {}", JoyconName(type), i + 1); + + main_param.Set("display", std::move(name)); + main_param.Set("guid2", second_param.Get("guid", "")); + main_param.Set("pad", std::to_string(static_cast<size_t>(type))); + devices.emplace_back(main_param); + } + + return devices; +} + +ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) { + static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>, + 18> + switch_to_joycon_button = { + std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true}, + {Settings::NativeButton::B, Joycon::PadButton::B, true}, + {Settings::NativeButton::X, Joycon::PadButton::X, true}, + {Settings::NativeButton::Y, Joycon::PadButton::Y, true}, + {Settings::NativeButton::DLeft, Joycon::PadButton::Left, false}, + {Settings::NativeButton::DUp, Joycon::PadButton::Up, false}, + {Settings::NativeButton::DRight, Joycon::PadButton::Right, false}, + {Settings::NativeButton::DDown, Joycon::PadButton::Down, false}, + {Settings::NativeButton::L, Joycon::PadButton::L, false}, + {Settings::NativeButton::R, Joycon::PadButton::R, true}, + {Settings::NativeButton::ZL, Joycon::PadButton::ZL, false}, + {Settings::NativeButton::ZR, Joycon::PadButton::ZR, true}, + {Settings::NativeButton::Plus, Joycon::PadButton::Plus, true}, + {Settings::NativeButton::Minus, Joycon::PadButton::Minus, false}, + {Settings::NativeButton::Home, Joycon::PadButton::Home, true}, + {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false}, + {Settings::NativeButton::LStick, Joycon::PadButton::StickL, false}, + {Settings::NativeButton::RStick, Joycon::PadButton::StickR, true}, + }; + + if (!params.Has("port")) { + return {}; + } + + ButtonMapping mapping{}; + for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) { + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); + if (pad == Joycon::ControllerType::Dual) { + pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left; + } + + Common::ParamPackage button_params = GetParamPackage(port, pad); + button_params.Set("button", static_cast<int>(joycon_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + // Map SL and SR buttons for left joycons + if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) { + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left); + + Common::ParamPackage sl_button_params = button_params; + Common::ParamPackage sr_button_params = button_params; + sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL)); + sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR)); + mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); + mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); + } + + // Map SL and SR buttons for right joycons + if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) { + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right); + + Common::ParamPackage sl_button_params = button_params; + Common::ParamPackage sr_button_params = button_params; + sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL)); + sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR)); + mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); + mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); + } + + return mapping; +} + +AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("port")) { + return {}; + } + + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); + auto pad_right = pad_left; + if (pad_left == Joycon::ControllerType::Dual) { + pad_left = Joycon::ControllerType::Left; + pad_right = Joycon::ControllerType::Right; + } + + AnalogMapping mapping = {}; + Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left); + left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX)); + left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right); + right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX)); + right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("port")) { + return {}; + } + + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); + auto pad_right = pad_left; + if (pad_left == Joycon::ControllerType::Dual) { + pad_left = Joycon::ControllerType::Left; + pad_right = Joycon::ControllerType::Right; + } + + MotionMapping mapping = {}; + Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left); + left_motion_params.Set("motion", 0); + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params)); + Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right); + right_Motion_params.Set("motion", 1); + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params)); + return mapping; +} + +Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const { + const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0)); + switch (button) { + case Joycon::PadButton::Left: + return Common::Input::ButtonNames::ButtonLeft; + case Joycon::PadButton::Right: + return Common::Input::ButtonNames::ButtonRight; + case Joycon::PadButton::Down: + return Common::Input::ButtonNames::ButtonDown; + case Joycon::PadButton::Up: + return Common::Input::ButtonNames::ButtonUp; + case Joycon::PadButton::LeftSL: + case Joycon::PadButton::RightSL: + return Common::Input::ButtonNames::TriggerSL; + case Joycon::PadButton::LeftSR: + case Joycon::PadButton::RightSR: + return Common::Input::ButtonNames::TriggerSR; + case Joycon::PadButton::L: + return Common::Input::ButtonNames::TriggerL; + case Joycon::PadButton::R: + return Common::Input::ButtonNames::TriggerR; + case Joycon::PadButton::ZL: + return Common::Input::ButtonNames::TriggerZL; + case Joycon::PadButton::ZR: + return Common::Input::ButtonNames::TriggerZR; + case Joycon::PadButton::A: + return Common::Input::ButtonNames::ButtonA; + case Joycon::PadButton::B: + return Common::Input::ButtonNames::ButtonB; + case Joycon::PadButton::X: + return Common::Input::ButtonNames::ButtonX; + case Joycon::PadButton::Y: + return Common::Input::ButtonNames::ButtonY; + case Joycon::PadButton::Plus: + return Common::Input::ButtonNames::ButtonPlus; + case Joycon::PadButton::Minus: + return Common::Input::ButtonNames::ButtonMinus; + case Joycon::PadButton::Home: + return Common::Input::ButtonNames::ButtonHome; + case Joycon::PadButton::Capture: + return Common::Input::ButtonNames::ButtonCapture; + case Joycon::PadButton::StickL: + return Common::Input::ButtonNames::ButtonStickL; + case Joycon::PadButton::StickR: + return Common::Input::ButtonNames::ButtonStickR; + default: + return Common::Input::ButtonNames::Undefined; + } +} + +Common::Input::ButtonNames Joycons::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; +} + +std::string Joycons::JoyconName(Joycon::ControllerType type) const { + switch (type) { + case Joycon::ControllerType::Left: + return "Left Joycon"; + case Joycon::ControllerType::Right: + return "Right Joycon"; + case Joycon::ControllerType::Pro: + return "Pro Controller"; + case Joycon::ControllerType::Dual: + return "Dual Joycon"; + default: + return "Unknown Switch Controller"; + } +} + +Common::Input::NfcState Joycons::TranslateDriverResult(Joycon::DriverResult result) const { + switch (result) { + case Joycon::DriverResult::Success: + return Common::Input::NfcState::Success; + case Joycon::DriverResult::Disabled: + return Common::Input::NfcState::WrongDeviceState; + case Joycon::DriverResult::NotSupported: + return Common::Input::NfcState::NotSupported; + default: + return Common::Input::NfcState::Unknown; + } +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h new file mode 100644 index 000000000..4c323d7d6 --- /dev/null +++ b/src/input_common/drivers/joycon.h @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> +#include <thread> +#include <SDL_hidapi.h> + +#include "input_common/input_engine.h" + +namespace InputCommon::Joycon { +using SerialNumber = std::array<u8, 15>; +struct Battery; +struct Color; +struct MotionData; +struct TagInfo; +enum class ControllerType : u8; +enum class DriverResult; +enum class IrsResolution; +class JoyconDriver; +} // namespace InputCommon::Joycon + +namespace InputCommon { + +class Joycons final : public InputCommon::InputEngine { +public: + explicit Joycons(const std::string& input_engine_); + + ~Joycons(); + + bool IsVibrationEnabled(const PadIdentifier& identifier) override; + Common::Input::DriverResult SetVibration( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; + + Common::Input::DriverResult SetLeds(const PadIdentifier& identifier, + const Common::Input::LedStatus& led_status) override; + + Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier, + Common::Input::CameraFormat camera_format) override; + + Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier) const override; + Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier) override; + Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier) override; + Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier, + std::vector<u8>& out_data) override; + Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier, + const std::vector<u8>& data) override; + Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier, + const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& out_data) override; + Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier, + const Common::Input::MifareRequest& request) override; + + Common::Input::DriverResult SetPollingMode( + const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override; + + /// Used for automapping features + std::vector<Common::ParamPackage> 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; + +private: + static constexpr std::size_t MaxSupportedControllers = 8; + + /// For shutting down, clear all data, join all threads, release usb devices + void Reset(); + + /// Registers controllers, clears all data and starts the scan thread + void Setup(); + + /// Actively searches for new devices + void ScanThread(std::stop_token stop_token); + + /// Returns true if device is valid and not registered + bool IsDeviceNew(SDL_hid_device_info* device_info) const; + + /// Tries to connect to the new device + void RegisterNewDevice(SDL_hid_device_info* device_info); + + /// Returns the next free handle + std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const; + + void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value); + void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value); + void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value); + void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value); + void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, + const Joycon::MotionData& value); + void OnRingConUpdate(f32 ring_data); + void OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type, + const Joycon::TagInfo& amiibo_data); + void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, + Joycon::IrsResolution format); + + /// Returns a JoyconHandle corresponding to a PadIdentifier + std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const; + + /// Returns a PadIdentifier corresponding to the port number and joycon type + PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const; + + /// Returns a ParamPackage corresponding to the port number and joycon type + Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const; + + std::string JoyconName(std::size_t port) const; + + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + + /// Returns the name of the device in text format + std::string JoyconName(Joycon::ControllerType type) const; + + Common::Input::NfcState TranslateDriverResult(Joycon::DriverResult result) const; + + std::jthread scan_thread; + + // Joycon types are split by type to ease supporting dualjoycon configurations + std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{}; + std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{}; + std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> pro_controller{}; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp index 71e612fbf..2567df9af 100644 --- a/src/input_common/drivers/keyboard.cpp +++ b/src/input_common/drivers/keyboard.cpp @@ -24,7 +24,7 @@ constexpr PadIdentifier keyboard_modifier_identifier = { }; Keyboard::Keyboard(std::string input_engine_) : InputEngine(std::move(input_engine_)) { - // Keyboard is broken into 3 diferent sets: + // Keyboard is broken into 3 different sets: // key: Unfiltered intended for controllers. // keyboard_key: Allows only Settings::NativeKeyboard::Keys intended for keyboard emulation. // keyboard_modifier: Allows only Settings::NativeKeyboard::Modifiers intended for keyboard diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index faf9cbdc3..f07cf8a0e 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -3,6 +3,7 @@ #include <thread> #include <fmt/format.h> +#include <math.h> #include "common/param_package.h" #include "common/settings.h" @@ -10,135 +11,232 @@ #include "input_common/drivers/mouse.h" namespace InputCommon { +constexpr int update_time = 10; +constexpr float default_stick_sensitivity = 0.0044f; +constexpr float default_motion_sensitivity = 0.0003f; +constexpr float maximum_rotation_speed = 2.0f; 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, }; +constexpr PadIdentifier motion_identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 1, +}; + +constexpr PadIdentifier real_mouse_identifier = { + .guid = Common::UUID{}, + .port = 1, + .pad = 0, +}; + +constexpr PadIdentifier touch_identifier = { + .guid = Common::UUID{}, + .port = 2, + .pad = 0, +}; + Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) { PreSetController(identifier); + PreSetController(real_mouse_identifier); + PreSetController(touch_identifier); + PreSetController(motion_identifier); + + // Initialize all mouse axis 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); + PreSetAxis(real_mouse_identifier, mouse_axis_x); + PreSetAxis(real_mouse_identifier, mouse_axis_y); + PreSetAxis(touch_identifier, mouse_axis_x); + PreSetAxis(touch_identifier, mouse_axis_y); + + // Initialize variables + mouse_origin = {}; + last_mouse_position = {}; + wheel_position = {}; + last_mouse_change = {}; + last_motion_change = {}; + 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()) { - if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { - // Slow movement by 4% - last_mouse_change *= 0.96f; - const float sensitivity = - Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f; - SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity); - SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity); - } - - SetAxis(identifier, motion_wheel_y, 0.0f); - - if (mouse_panning_timout++ > 20) { - StopPanning(); - } + UpdateStickInput(); + UpdateMotionInput(); + 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) { - // If native mouse is enabled just set the screen coordinates - if (Settings::values.mouse_enabled) { - SetAxis(identifier, mouse_axis_x, touch_x); - SetAxis(identifier, mouse_axis_y, touch_y); +void Mouse::UpdateStickInput() { + if (!Settings::values.mouse_panning) { return; } - SetAxis(identifier, touch_axis_x, touch_x); - SetAxis(identifier, touch_axis_y, touch_y); + const float length = last_mouse_change.Length(); - if (Settings::values.mouse_panning) { - auto mouse_change = - (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); - mouse_panning_timout = 0; + // Prevent input from exceeding the max range (1.0f) too much, + // but allow some room to make it easier to sustain + if (length > 1.2f) { + last_mouse_change /= length; + last_mouse_change *= 1.2f; + } + + auto mouse_change = last_mouse_change; + + // Bind the mouse change to [0 <= deadzone_counterweight <= 1,1] + if (length < 1.0f) { + const float deadzone_h_counterweight = + Settings::values.mouse_panning_deadzone_x_counterweight.GetValue(); + const float deadzone_v_counterweight = + Settings::values.mouse_panning_deadzone_y_counterweight.GetValue(); + mouse_change /= length; + mouse_change.x *= length + (1 - length) * deadzone_h_counterweight * 0.01f; + mouse_change.y *= length + (1 - length) * deadzone_v_counterweight * 0.01f; + } + + SetAxis(identifier, mouse_axis_x, mouse_change.x); + SetAxis(identifier, mouse_axis_y, -mouse_change.y); + + // Decay input over time + const float clamped_length = std::min(1.0f, length); + const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue(); + const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f; + const float min_decay = Settings::values.mouse_panning_min_decay.GetValue(); + const float clamped_decay = std::min(1 - min_decay / 100.0f, decay); + last_mouse_change *= clamped_decay; +} - const auto move_distance = mouse_change.Length(); - if (move_distance == 0) { - return; - } +void Mouse::UpdateMotionInput() { + // This may need its own sensitivity instead of using the average + const float sensitivity = (Settings::values.mouse_panning_x_sensitivity.GetValue() + + Settings::values.mouse_panning_y_sensitivity.GetValue()) / + 2.0f * default_motion_sensitivity; - // Make slow movements at least 3 units on lenght - if (move_distance < 3.0f) { - // Normalize value - mouse_change /= move_distance; - mouse_change *= 3.0f; - } + const float rotation_velocity = std::sqrt(last_motion_change.x * last_motion_change.x + + last_motion_change.y * last_motion_change.y); - // Average mouse movements - last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f); + if (rotation_velocity > maximum_rotation_speed / sensitivity) { + const float multiplier = maximum_rotation_speed / rotation_velocity / sensitivity; + last_motion_change.x = last_motion_change.x * multiplier; + last_motion_change.y = last_motion_change.y * multiplier; + } - const auto last_move_distance = last_mouse_change.Length(); + const BasicMotion motion_data{ + .gyro_x = last_motion_change.x * sensitivity, + .gyro_y = last_motion_change.y * sensitivity, + .gyro_z = last_motion_change.z * sensitivity, + .accel_x = 0, + .accel_y = 0, + .accel_z = 0, + .delta_timestamp = update_time * 1000, + }; - // Make fast movements clamp to 8 units on lenght - if (last_move_distance > 8.0f) { - // Normalize value - last_mouse_change /= last_move_distance; - last_mouse_change *= 8.0f; - } + if (Settings::values.mouse_panning) { + last_motion_change.x = 0; + last_motion_change.y = 0; + } + last_motion_change.z = 0; - // Ignore average if it's less than 1 unit and use current movement value - if (last_move_distance < 1.0f) { - last_mouse_change = mouse_change / mouse_change.Length(); - } + SetMotion(motion_identifier, 0, motion_data); +} + +void Mouse::Move(int x, int y, int center_x, int center_y) { + if (Settings::values.mouse_panning) { + const auto mouse_change = + (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); + const float x_sensitivity = + Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity; + const float y_sensitivity = + Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity; + + last_motion_change += {-mouse_change.y, -mouse_change.x, 0}; + last_mouse_change.x += mouse_change.x * x_sensitivity * 0.09f; + last_mouse_change.y += mouse_change.y * y_sensitivity * 0.09f; return; } if (button_pressed) { const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin; - const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f; - SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity); - SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity); + const float x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue(); + const float y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue(); + SetAxis(identifier, mouse_axis_x, + static_cast<float>(mouse_move.x) * x_sensitivity * 0.0012f); + SetAxis(identifier, mouse_axis_y, + static_cast<float>(-mouse_move.y) * y_sensitivity * 0.0012f); + + last_motion_change = { + static_cast<float>(-mouse_move.y) / 50.0f, + static_cast<float>(-mouse_move.x) / 50.0f, + last_motion_change.z, + }; } } -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); +void Mouse::MouseMove(f32 touch_x, f32 touch_y) { + SetAxis(real_mouse_identifier, mouse_axis_x, touch_x); + SetAxis(real_mouse_identifier, mouse_axis_y, touch_y); +} + +void Mouse::TouchMove(f32 touch_x, f32 touch_y) { + SetAxis(touch_identifier, mouse_axis_x, touch_x); + SetAxis(touch_identifier, mouse_axis_y, touch_y); +} + +void Mouse::PressButton(int x, int y, MouseButton button) { SetButton(identifier, static_cast<int>(button), true); + // Set initial analog parameters mouse_origin = {x, y}; last_mouse_position = {x, y}; button_pressed = true; } +void Mouse::PressMouseButton(MouseButton button) { + SetButton(real_mouse_identifier, static_cast<int>(button), true); +} + +void Mouse::PressTouchButton(f32 touch_x, f32 touch_y, MouseButton button) { + SetAxis(touch_identifier, mouse_axis_x, touch_x); + SetAxis(touch_identifier, mouse_axis_y, touch_y); + SetButton(touch_identifier, static_cast<int>(button), true); +} + void Mouse::ReleaseButton(MouseButton button) { SetButton(identifier, static_cast<int>(button), false); + SetButton(real_mouse_identifier, static_cast<int>(button), false); + SetButton(touch_identifier, static_cast<int>(button), false); - if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) { + if (!Settings::values.mouse_panning) { SetAxis(identifier, mouse_axis_x, 0); SetAxis(identifier, mouse_axis_y, 0); } + + last_motion_change.x = 0; + last_motion_change.y = 0; + button_pressed = false; } void Mouse::MouseWheelChange(int x, int y) { wheel_position.x += x; wheel_position.y += y; + last_motion_change.z += static_cast<f32>(y) / 100.0f; SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x)); SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y)); - SetAxis(identifier, motion_wheel_y, static_cast<f32>(y) / 100.0f); } void Mouse::ReleaseAllButtons() { @@ -146,10 +244,6 @@ void Mouse::ReleaseAllButtons() { button_pressed = false; } -void Mouse::StopPanning() { - last_mouse_change = {}; -} - std::vector<Common::ParamPackage> Mouse::GetInputDevices() const { std::vector<Common::ParamPackage> devices; devices.emplace_back(Common::ParamPackage{ @@ -207,6 +301,9 @@ Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { return Common::Input::ButtonNames::Engine; } + if (params.Has("motion")) { + return Common::Input::ButtonNames::Engine; + } return Common::Input::ButtonNames::Invalid; } diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index 72073cc23..0e8edcce1 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -37,13 +37,43 @@ public: * @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); + void Move(int x, int 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 + * Signals that real mouse has moved. + * @param x the absolute position on the touchscreen of the cursor + * @param y the absolute position on the touchscreen of the cursor */ - void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button); + void MouseMove(f32 touch_x, f32 touch_y); + + /** + * Signals that touch finger has moved. + * @param x the absolute position on the touchscreen of the cursor + * @param y the absolute position on the touchscreen of the cursor + */ + void TouchMove(f32 touch_x, f32 touch_y); + + /** + * Sets the status of a button to pressed + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param button the id of the button to press + */ + void PressButton(int x, int y, MouseButton button); + + /** + * Sets the status of a mouse button to pressed + * @param button the id of the button to press + */ + void PressMouseButton(MouseButton button); + + /** + * Sets the status of touch finger to pressed + * @param x the absolute position on the touchscreen of the cursor + * @param y the absolute position on the touchscreen of the cursor + * @param button the id of the button to press + */ + void PressTouchButton(f32 touch_x, f32 touch_y, MouseButton button); /** * Sets the status of all buttons bound with the key to released @@ -66,16 +96,17 @@ public: private: void UpdateThread(std::stop_token stop_token); - void StopPanning(); + void UpdateStickInput(); + void UpdateMotionInput(); Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; Common::Vec2<int> mouse_origin; Common::Vec2<int> last_mouse_position; Common::Vec2<float> last_mouse_change; + Common::Vec3<float> last_motion_change; Common::Vec2<int> wheel_position; bool button_pressed; - int mouse_panning_timout{}; std::jthread update_thread; }; diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 9835d99d2..9f26392b1 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -109,14 +109,37 @@ public: } bool RumblePlay(const Common::Input::VibrationStatus vibration) { - constexpr u32 rumble_max_duration_ms = 1000; + constexpr u32 rumble_max_duration_ms = 2000; + constexpr f32 low_start_sensitivity_limit = 140.0; + constexpr f32 low_width_sensitivity_limit = 400.0; + constexpr f32 high_start_sensitivity_limit = 200.0; + constexpr f32 high_width_sensitivity_limit = 700.0; + // Try to provide some feeling of the frequency by reducing the amplitude depending on it. + f32 low_frequency_scale = 1.0; + if (vibration.low_frequency > low_start_sensitivity_limit) { + low_frequency_scale = + std::max(1.0f - (vibration.low_frequency - low_start_sensitivity_limit) / + low_width_sensitivity_limit, + 0.3f); + } + f32 low_amplitude = vibration.low_amplitude * low_frequency_scale; + + f32 high_frequency_scale = 1.0; + if (vibration.high_frequency > high_start_sensitivity_limit) { + high_frequency_scale = + std::max(1.0f - (vibration.high_frequency - high_start_sensitivity_limit) / + high_width_sensitivity_limit, + 0.3f); + } + f32 high_amplitude = vibration.high_amplitude * high_frequency_scale; + if (sdl_controller) { - return SDL_GameControllerRumble( - sdl_controller.get(), static_cast<u16>(vibration.low_amplitude), - static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1; + return SDL_GameControllerRumble(sdl_controller.get(), static_cast<u16>(low_amplitude), + static_cast<u16>(high_amplitude), + rumble_max_duration_ms) != -1; } else if (sdl_joystick) { - return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude), - static_cast<u16>(vibration.high_amplitude), + return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(low_amplitude), + static_cast<u16>(high_amplitude), rumble_max_duration_ms) != -1; } @@ -127,6 +150,8 @@ public: if (sdl_controller) { const auto type = SDL_GameControllerGetType(sdl_controller.get()); return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) || + (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) || + (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) || (type == SDL_CONTROLLER_TYPE_PS5); } return false; @@ -205,9 +230,8 @@ public: return false; } - Common::Input::BatteryLevel GetBatteryLevel() { - const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get()); - switch (level) { + Common::Input::BatteryLevel GetBatteryLevel(SDL_JoystickPowerLevel battery_level) { + switch (battery_level) { case SDL_JOYSTICK_POWER_EMPTY: return Common::Input::BatteryLevel::Empty; case SDL_JOYSTICK_POWER_LOW: @@ -334,11 +358,27 @@ void SDLDriver::InitJoystick(int joystick_index) { const auto guid = GetGUID(sdl_joystick); + if (Settings::values.enable_joycon_driver) { + if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && + (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) { + LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); + SDL_JoystickClose(sdl_joystick); + return; + } + } + + if (Settings::values.enable_procon_driver) { + if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && guid.uuid[8] == 0x09) { + LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); + SDL_JoystickClose(sdl_joystick); + return; + } + } + std::scoped_lock lock{joystick_map_mutex}; if (joystick_map.find(guid) == joystick_map.end()) { auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); - SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); joystick->EnableMotion(); joystick_map[guid].emplace_back(std::move(joystick)); return; @@ -358,7 +398,6 @@ void SDLDriver::InitJoystick(int joystick_index) { const int port = static_cast<int>(joystick_guid_list.size()); auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); - SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); joystick->EnableMotion(); joystick_guid_list.emplace_back(std::move(joystick)); } @@ -398,8 +437,6 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetButton(identifier, event.jbutton.button, true); - // Battery doesn't trigger an event so just update every button press - SetBattery(identifier, joystick->GetBatteryLevel()); } break; } @@ -426,6 +463,13 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { } break; } + case SDL_JOYBATTERYUPDATED: { + if (auto joystick = GetSDLJoystickBySDLID(event.jbattery.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.level)); + } + break; + } case SDL_JOYDEVICEREMOVED: LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); @@ -443,6 +487,10 @@ void SDLDriver::CloseJoysticks() { } SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + // Set our application name. Currently passed to DBus by SDL and visible to the user through + // their desktop environment. + SDL_SetHint(SDL_HINT_APP_NAME, "yuzu"); + if (!Settings::values.enable_raw_input) { // Disable raw input. When enabled this setting causes SDL to die when a web applet opens SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); @@ -456,9 +504,25 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en 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 drivers for joycon controllers when the custom joycon driver is enabled + if (Settings::values.enable_joycon_driver) { + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0"); + } else { + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED, "0"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, "0"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, "1"); + } + + // Disable hidapi drivers for pro controllers when the custom joycon driver is enabled + if (Settings::values.enable_procon_driver) { + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0"); + } else { + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0"); + } + + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1"); // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native // driver on Linux. @@ -548,7 +612,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const { return devices; } -Common::Input::VibrationError SDLDriver::SetVibration( +Common::Input::DriverResult SDLDriver::SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { const auto joystick = GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); @@ -582,14 +646,14 @@ Common::Input::VibrationError SDLDriver::SetVibration( .vibration = new_vibration, }); - return Common::Input::VibrationError::None; + return Common::Input::DriverResult::Success; } bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { const auto joystick = GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); - constexpr Common::Input::VibrationStatus test_vibration{ + static constexpr Common::Input::VibrationStatus test_vibration{ .low_amplitude = 1, .low_frequency = 160.0f, .high_amplitude = 1, @@ -597,7 +661,7 @@ bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { .type = Common::Input::VibrationAmplificationType::Exponential, }; - constexpr Common::Input::VibrationStatus zero_vibration{ + static constexpr Common::Input::VibrationStatus zero_vibration{ .low_amplitude = 0, .low_frequency = 160.0f, .high_amplitude = 0, @@ -625,12 +689,27 @@ bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { } void SDLDriver::SendVibrations() { + std::vector<VibrationRequest> filtered_vibrations{}; while (!vibration_queue.Empty()) { VibrationRequest request; vibration_queue.Pop(request); const auto joystick = GetSDLJoystickByGUID(request.identifier.guid.RawString(), static_cast<int>(request.identifier.port)); - joystick->RumblePlay(request.vibration); + const auto it = std::find_if(filtered_vibrations.begin(), filtered_vibrations.end(), + [request](VibrationRequest vibration) { + return vibration.identifier == request.identifier; + }); + if (it == filtered_vibrations.end()) { + filtered_vibrations.push_back(std::move(request)); + continue; + } + *it = request; + } + + for (const auto& vibration : filtered_vibrations) { + const auto joystick = GetSDLJoystickByGUID(vibration.identifier.guid.RawString(), + static_cast<int>(vibration.identifier.port)); + joystick->RumblePlay(vibration.vibration); } } @@ -721,10 +800,12 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p // 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 + // This list also excludes Screenshot since there's not really a mapping for that ButtonBindings switch_to_sdl_button; - if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) { + if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO || + SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT || + SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) { switch_to_sdl_button = GetNintendoButtonBinding(joystick); } else { switch_to_sdl_button = GetDefaultButtonBinding(); @@ -980,7 +1061,7 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p 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 + // TODO(German77): Find how to substitute the values for real button names return Common::Input::ButtonNames::Value; } if (params.Has("hat")) { diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index 366bcc496..ffde169b3 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -63,7 +63,7 @@ public: bool IsStickInverted(const Common::ParamPackage& params) override; - Common::Input::VibrationError SetVibration( + Common::Input::DriverResult SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; bool IsVibrationEnabled(const PadIdentifier& identifier) override; diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index 63ffaca67..180eb53ef 100644 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp @@ -22,28 +22,61 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move( VirtualAmiibo::~VirtualAmiibo() = default; -Common::Input::PollingError VirtualAmiibo::SetPollingMode( +Common::Input::DriverResult VirtualAmiibo::SetPollingMode( [[maybe_unused]] const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) { polling_mode = polling_mode_; - if (polling_mode == Common::Input::PollingMode::NFC) { - if (state == State::Initialized) { - state = State::WaitingForAmiibo; - } - } else { - if (state == State::AmiiboIsOpen) { + switch (polling_mode) { + case Common::Input::PollingMode::NFC: + state = State::Initialized; + return Common::Input::DriverResult::Success; + default: + if (state == State::TagNearby) { CloseAmiibo(); } + state = State::Disabled; + return Common::Input::DriverResult::NotSupported; } - - return Common::Input::PollingError::None; } Common::Input::NfcState VirtualAmiibo::SupportsNfc( [[maybe_unused]] const PadIdentifier& identifier_) const { return Common::Input::NfcState::Success; } +Common::Input::NfcState VirtualAmiibo::StartNfcPolling(const PadIdentifier& identifier_) { + if (state != State::Initialized) { + return Common::Input::NfcState::WrongDeviceState; + } + state = State::WaitingForAmiibo; + return Common::Input::NfcState::Success; +} + +Common::Input::NfcState VirtualAmiibo::StopNfcPolling(const PadIdentifier& identifier_) { + if (state == State::Disabled) { + return Common::Input::NfcState::WrongDeviceState; + } + if (state == State::TagNearby) { + CloseAmiibo(); + } + state = State::Initialized; + return Common::Input::NfcState::Success; +} + +Common::Input::NfcState VirtualAmiibo::ReadAmiiboData(const PadIdentifier& identifier_, + std::vector<u8>& out_data) { + if (state != State::TagNearby) { + return Common::Input::NfcState::WrongDeviceState; + } + + if (status.tag_type != 1U << 1) { + return Common::Input::NfcState::InvalidTagType; + } + + out_data.resize(nfc_data.size()); + memcpy(out_data.data(), nfc_data.data(), nfc_data.size()); + return Common::Input::NfcState::Success; +} Common::Input::NfcState VirtualAmiibo::WriteNfcData( [[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) { @@ -56,7 +89,7 @@ Common::Input::NfcState VirtualAmiibo::WriteNfcData( } if (!nfc_file.Write(data)) { - LOG_ERROR(Service_NFP, "Error writting to file"); + LOG_ERROR(Service_NFP, "Error writing to file"); return Common::Input::NfcState::WriteFailed; } @@ -65,6 +98,69 @@ Common::Input::NfcState VirtualAmiibo::WriteNfcData( return Common::Input::NfcState::Success; } +Common::Input::NfcState VirtualAmiibo::ReadMifareData(const PadIdentifier& identifier_, + const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& out_data) { + if (state != State::TagNearby) { + return Common::Input::NfcState::WrongDeviceState; + } + + if (status.tag_type != 1U << 6) { + return Common::Input::NfcState::InvalidTagType; + } + + for (std::size_t i = 0; i < request.data.size(); i++) { + if (request.data[i].command == 0) { + continue; + } + out_data.data[i].command = request.data[i].command; + out_data.data[i].sector = request.data[i].sector; + + const std::size_t sector_index = + request.data[i].sector * sizeof(Common::Input::MifareData::data); + + if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) { + return Common::Input::NfcState::WriteFailed; + } + + // Ignore the sector key as we don't support it + memcpy(out_data.data[i].data.data(), nfc_data.data() + sector_index, + sizeof(Common::Input::MifareData::data)); + } + + return Common::Input::NfcState::Success; +} + +Common::Input::NfcState VirtualAmiibo::WriteMifareData( + const PadIdentifier& identifier_, const Common::Input::MifareRequest& request) { + if (state != State::TagNearby) { + return Common::Input::NfcState::WrongDeviceState; + } + + if (status.tag_type != 1U << 6) { + return Common::Input::NfcState::InvalidTagType; + } + + for (std::size_t i = 0; i < request.data.size(); i++) { + if (request.data[i].command == 0) { + continue; + } + + const std::size_t sector_index = + request.data[i].sector * sizeof(Common::Input::MifareData::data); + + if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) { + return Common::Input::NfcState::WriteFailed; + } + + // Ignore the sector key as we don't support it + memcpy(nfc_data.data() + sector_index, request.data[i].data.data(), + sizeof(Common::Input::MifareData::data)); + } + + return Common::Input::NfcState::Success; +} + VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const { return state; } @@ -72,10 +168,7 @@ VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const { VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) { const Common::FS::IOFile nfc_file{filename, Common::FS::FileAccessMode::Read, Common::FS::FileType::BinaryFile}; - - if (state != State::WaitingForAmiibo) { - return Info::WrongDeviceState; - } + std::vector<u8> data{}; if (!nfc_file.IsOpen()) { return Info::UnableToLoad; @@ -84,14 +177,15 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) { switch (nfc_file.GetSize()) { case AmiiboSize: case AmiiboSizeWithoutPassword: - nfc_data.resize(AmiiboSize); - if (nfc_file.Read(nfc_data) < AmiiboSizeWithoutPassword) { + case AmiiboSizeWithSignature: + data.resize(AmiiboSize); + if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { return Info::NotAnAmiibo; } break; case MifareSize: - nfc_data.resize(MifareSize); - if (nfc_file.Read(nfc_data) < MifareSize) { + data.resize(MifareSize); + if (nfc_file.Read(data) < MifareSize) { return Info::NotAnAmiibo; } break; @@ -100,14 +194,44 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) { } file_path = filename; - state = State::AmiiboIsOpen; - SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data}); + return LoadAmiibo(data); +} + +VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) { + if (state != State::WaitingForAmiibo) { + return Info::WrongDeviceState; + } + + switch (data.size_bytes()) { + case AmiiboSize: + case AmiiboSizeWithoutPassword: + case AmiiboSizeWithSignature: + nfc_data.resize(AmiiboSize); + status.tag_type = 1U << 1; + status.uuid_length = 7; + break; + case MifareSize: + nfc_data.resize(MifareSize); + status.tag_type = 1U << 6; + status.uuid_length = 4; + break; + default: + return Info::NotAnAmiibo; + } + + status.uuid = {}; + status.protocol = 1; + state = State::TagNearby; + status.state = Common::Input::NfcState::NewAmiibo, + memcpy(nfc_data.data(), data.data(), data.size_bytes()); + memcpy(status.uuid.data(), nfc_data.data(), status.uuid_length); + SetNfc(identifier, status); return Info::Success; } VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() { - if (state == State::AmiiboIsOpen) { - SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data}); + if (state == State::TagNearby) { + SetNfc(identifier, status); return Info::Success; } @@ -115,9 +239,14 @@ VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() { } VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() { - state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo - : State::Initialized; - SetNfc(identifier, {Common::Input::NfcState::AmiiboRemoved, {}}); + if (state != State::TagNearby) { + return Info::Success; + } + + state = State::WaitingForAmiibo; + status.state = Common::Input::NfcState::AmiiboRemoved; + SetNfc(identifier, status); + status.tag_type = 0; return Info::Success; } diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index 0f9dad333..490f38e05 100644 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h @@ -4,6 +4,7 @@ #pragma once #include <array> +#include <span> #include <string> #include <vector> @@ -19,9 +20,10 @@ namespace InputCommon { class VirtualAmiibo final : public InputEngine { public: enum class State { + Disabled, Initialized, WaitingForAmiibo, - AmiiboIsOpen, + TagNearby, }; enum class Info { @@ -36,17 +38,26 @@ public: ~VirtualAmiibo() override; // Sets polling mode to a controller - Common::Input::PollingError SetPollingMode( + Common::Input::DriverResult SetPollingMode( const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; - + Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier_) override; + Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier_) override; + Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier_, + std::vector<u8>& out_data) override; Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, const std::vector<u8>& data) override; + Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier_, + const Common::Input::MifareRequest& data, + Common::Input::MifareRequest& out_data) override; + Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier_, + const Common::Input::MifareRequest& data) override; State GetCurrentState() const; Info LoadAmiibo(const std::string& amiibo_file); + Info LoadAmiibo(std::span<u8> data); Info ReloadAmiibo(); Info CloseAmiibo(); @@ -55,11 +66,13 @@ public: private: static constexpr std::size_t AmiiboSize = 0x21C; static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; + static constexpr std::size_t AmiiboSizeWithSignature = AmiiboSize + 0x20; static constexpr std::size_t MifareSize = 0x400; std::string file_path{}; - State state{State::Initialized}; + State state{State::Disabled}; std::vector<u8> nfc_data; - Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Pasive}; + Common::Input::NfcStatus status; + Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Passive}; }; } // namespace InputCommon diff --git a/src/input_common/drivers/virtual_gamepad.cpp b/src/input_common/drivers/virtual_gamepad.cpp index 7db945aa6..c15cbbe58 100644 --- a/src/input_common/drivers/virtual_gamepad.cpp +++ b/src/input_common/drivers/virtual_gamepad.cpp @@ -39,6 +39,22 @@ void VirtualGamepad::SetStickPosition(std::size_t player_index, VirtualStick axi SetStickPosition(player_index, static_cast<int>(axis_id), x_value, y_value); } +void VirtualGamepad::SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, + float gyro_y, float gyro_z, float accel_x, float accel_y, + float accel_z) { + const auto identifier = GetIdentifier(player_index); + const BasicMotion motion_data{ + .gyro_x = gyro_x, + .gyro_y = gyro_y, + .gyro_z = gyro_z, + .accel_x = accel_x, + .accel_y = accel_y, + .accel_z = accel_z, + .delta_timestamp = delta_timestamp, + }; + SetMotion(identifier, 0, motion_data); +} + void VirtualGamepad::ResetControllers() { for (std::size_t i = 0; i < PlayerIndexCount; i++) { SetStickPosition(i, VirtualStick::Left, 0.0f, 0.0f); diff --git a/src/input_common/drivers/virtual_gamepad.h b/src/input_common/drivers/virtual_gamepad.h index 3df91cc6f..dfbc45a28 100644 --- a/src/input_common/drivers/virtual_gamepad.h +++ b/src/input_common/drivers/virtual_gamepad.h @@ -52,7 +52,7 @@ public: void SetButtonState(std::size_t player_index, VirtualButton button_id, bool value); /** - * Sets the status of all buttons bound with the key to released + * Sets the status of a stick to a specific player index * @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 @@ -62,6 +62,16 @@ public: void SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value, float y_value); + /** + * Sets the status of the motion sensor to a specific player index + * @param player_index the player number that will take this action + * @param delta_timestamp time passed since last reading + * @param gyro_x,gyro_y,gyro_z the gyro sensor readings + * @param accel_x,accel_y,accel_z the acelerometer reading + */ + void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y, + float gyro_z, float accel_x, float accel_y, float accel_z); + /// Restores all inputs into the neutral position void ResetControllers(); |