summaryrefslogtreecommitdiff
path: root/src/input_common/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/drivers')
-rw-r--r--src/input_common/drivers/camera.cpp4
-rw-r--r--src/input_common/drivers/camera.h4
-rw-r--r--src/input_common/drivers/gc_adapter.cpp12
-rw-r--r--src/input_common/drivers/gc_adapter.h2
-rw-r--r--src/input_common/drivers/joycon.cpp844
-rw-r--r--src/input_common/drivers/joycon.h125
-rw-r--r--src/input_common/drivers/keyboard.cpp2
-rw-r--r--src/input_common/drivers/mouse.cpp231
-rw-r--r--src/input_common/drivers/mouse.h43
-rw-r--r--src/input_common/drivers/sdl_driver.cpp129
-rw-r--r--src/input_common/drivers/sdl_driver.h2
-rw-r--r--src/input_common/drivers/virtual_amiibo.cpp179
-rw-r--r--src/input_common/drivers/virtual_amiibo.h23
-rw-r--r--src/input_common/drivers/virtual_gamepad.cpp16
-rw-r--r--src/input_common/drivers/virtual_gamepad.h12
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();