diff options
Diffstat (limited to 'src/input_common')
41 files changed, 5487 insertions, 135 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index cef2c4d52..e3b627e4f 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt | |||
| @@ -51,8 +51,29 @@ endif() | |||
| 51 | 51 | ||
| 52 | if (ENABLE_SDL2) | 52 | if (ENABLE_SDL2) |
| 53 | target_sources(input_common PRIVATE | 53 | target_sources(input_common PRIVATE |
| 54 | drivers/joycon.cpp | ||
| 55 | drivers/joycon.h | ||
| 54 | drivers/sdl_driver.cpp | 56 | drivers/sdl_driver.cpp |
| 55 | drivers/sdl_driver.h | 57 | drivers/sdl_driver.h |
| 58 | helpers/joycon_driver.cpp | ||
| 59 | helpers/joycon_driver.h | ||
| 60 | helpers/joycon_protocol/calibration.cpp | ||
| 61 | helpers/joycon_protocol/calibration.h | ||
| 62 | helpers/joycon_protocol/common_protocol.cpp | ||
| 63 | helpers/joycon_protocol/common_protocol.h | ||
| 64 | helpers/joycon_protocol/generic_functions.cpp | ||
| 65 | helpers/joycon_protocol/generic_functions.h | ||
| 66 | helpers/joycon_protocol/joycon_types.h | ||
| 67 | helpers/joycon_protocol/irs.cpp | ||
| 68 | helpers/joycon_protocol/irs.h | ||
| 69 | helpers/joycon_protocol/nfc.cpp | ||
| 70 | helpers/joycon_protocol/nfc.h | ||
| 71 | helpers/joycon_protocol/poller.cpp | ||
| 72 | helpers/joycon_protocol/poller.h | ||
| 73 | helpers/joycon_protocol/ringcon.cpp | ||
| 74 | helpers/joycon_protocol/ringcon.h | ||
| 75 | helpers/joycon_protocol/rumble.cpp | ||
| 76 | helpers/joycon_protocol/rumble.h | ||
| 56 | ) | 77 | ) |
| 57 | target_link_libraries(input_common PRIVATE SDL2::SDL2) | 78 | target_link_libraries(input_common PRIVATE SDL2::SDL2) |
| 58 | target_compile_definitions(input_common PRIVATE HAVE_SDL2) | 79 | target_compile_definitions(input_common PRIVATE HAVE_SDL2) |
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 { | |||
| 72 | } | 72 | } |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | Common::Input::CameraError Camera::SetCameraFormat( | 75 | Common::Input::DriverResult Camera::SetCameraFormat( |
| 76 | [[maybe_unused]] const PadIdentifier& identifier_, | 76 | [[maybe_unused]] const PadIdentifier& identifier_, |
| 77 | const Common::Input::CameraFormat camera_format) { | 77 | const Common::Input::CameraFormat camera_format) { |
| 78 | status.format = camera_format; | 78 | status.format = camera_format; |
| 79 | return Common::Input::CameraError::None; | 79 | return Common::Input::DriverResult::Success; |
| 80 | } | 80 | } |
| 81 | 81 | ||
| 82 | } // namespace InputCommon | 82 | } // 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: | |||
| 22 | std::size_t getImageWidth() const; | 22 | std::size_t getImageWidth() const; |
| 23 | std::size_t getImageHeight() const; | 23 | std::size_t getImageHeight() const; |
| 24 | 24 | ||
| 25 | Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, | 25 | Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_, |
| 26 | Common::Input::CameraFormat camera_format) override; | 26 | Common::Input::CameraFormat camera_format) override; |
| 27 | 27 | ||
| 28 | private: | 28 | private: |
| 29 | Common::Input::CameraStatus status{}; | 29 | Common::Input::CameraStatus status{}; |
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 826fa2109..d09ff178b 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 8 | #include "common/param_package.h" | 8 | #include "common/param_package.h" |
| 9 | #include "common/polyfill_thread.h" | ||
| 9 | #include "common/settings_input.h" | 10 | #include "common/settings_input.h" |
| 10 | #include "common/thread.h" | 11 | #include "common/thread.h" |
| 11 | #include "input_common/drivers/gc_adapter.h" | 12 | #include "input_common/drivers/gc_adapter.h" |
| @@ -217,8 +218,7 @@ void GCAdapter::AdapterScanThread(std::stop_token stop_token) { | |||
| 217 | Common::SetCurrentThreadName("ScanGCAdapter"); | 218 | Common::SetCurrentThreadName("ScanGCAdapter"); |
| 218 | usb_adapter_handle = nullptr; | 219 | usb_adapter_handle = nullptr; |
| 219 | pads = {}; | 220 | pads = {}; |
| 220 | while (!stop_token.stop_requested() && !Setup()) { | 221 | while (!Setup() && Common::StoppableTimedWait(stop_token, std::chrono::seconds{2})) { |
| 221 | std::this_thread::sleep_for(std::chrono::seconds(2)); | ||
| 222 | } | 222 | } |
| 223 | } | 223 | } |
| 224 | 224 | ||
| @@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) { | |||
| 324 | return true; | 324 | return true; |
| 325 | } | 325 | } |
| 326 | 326 | ||
| 327 | Common::Input::VibrationError GCAdapter::SetVibration( | 327 | Common::Input::DriverResult GCAdapter::SetVibration( |
| 328 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { | 328 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { |
| 329 | const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; | 329 | const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; |
| 330 | const auto processed_amplitude = | 330 | const auto processed_amplitude = |
| @@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration( | |||
| 333 | pads[identifier.port].rumble_amplitude = processed_amplitude; | 333 | pads[identifier.port].rumble_amplitude = processed_amplitude; |
| 334 | 334 | ||
| 335 | if (!rumble_enabled) { | 335 | if (!rumble_enabled) { |
| 336 | return Common::Input::VibrationError::Disabled; | 336 | return Common::Input::DriverResult::Disabled; |
| 337 | } | 337 | } |
| 338 | return Common::Input::VibrationError::None; | 338 | return Common::Input::DriverResult::Success; |
| 339 | } | 339 | } |
| 340 | 340 | ||
| 341 | bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { | 341 | bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { |
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: | |||
| 25 | explicit GCAdapter(std::string input_engine_); | 25 | explicit GCAdapter(std::string input_engine_); |
| 26 | ~GCAdapter() override; | 26 | ~GCAdapter() override; |
| 27 | 27 | ||
| 28 | Common::Input::VibrationError SetVibration( | 28 | Common::Input::DriverResult SetVibration( |
| 29 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; | 29 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; |
| 30 | 30 | ||
| 31 | bool IsVibrationEnabled(const PadIdentifier& identifier) override; | 31 | 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..b4cd39a20 --- /dev/null +++ b/src/input_common/drivers/joycon.cpp | |||
| @@ -0,0 +1,724 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <fmt/format.h> | ||
| 5 | |||
| 6 | #include "common/param_package.h" | ||
| 7 | #include "common/polyfill_ranges.h" | ||
| 8 | #include "common/polyfill_thread.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | #include "common/thread.h" | ||
| 11 | #include "input_common/drivers/joycon.h" | ||
| 12 | #include "input_common/helpers/joycon_driver.h" | ||
| 13 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 14 | |||
| 15 | namespace InputCommon { | ||
| 16 | |||
| 17 | Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) { | ||
| 18 | // Avoid conflicting with SDL driver | ||
| 19 | if (!Settings::values.enable_joycon_driver && !Settings::values.enable_procon_driver) { | ||
| 20 | return; | ||
| 21 | } | ||
| 22 | LOG_INFO(Input, "Joycon driver Initialization started"); | ||
| 23 | const int init_res = SDL_hid_init(); | ||
| 24 | if (init_res == 0) { | ||
| 25 | Setup(); | ||
| 26 | } else { | ||
| 27 | LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | Joycons::~Joycons() { | ||
| 32 | Reset(); | ||
| 33 | } | ||
| 34 | |||
| 35 | void Joycons::Reset() { | ||
| 36 | scan_thread = {}; | ||
| 37 | for (const auto& device : left_joycons) { | ||
| 38 | if (!device) { | ||
| 39 | continue; | ||
| 40 | } | ||
| 41 | device->Stop(); | ||
| 42 | } | ||
| 43 | for (const auto& device : right_joycons) { | ||
| 44 | if (!device) { | ||
| 45 | continue; | ||
| 46 | } | ||
| 47 | device->Stop(); | ||
| 48 | } | ||
| 49 | for (const auto& device : pro_controller) { | ||
| 50 | if (!device) { | ||
| 51 | continue; | ||
| 52 | } | ||
| 53 | device->Stop(); | ||
| 54 | } | ||
| 55 | SDL_hid_exit(); | ||
| 56 | } | ||
| 57 | |||
| 58 | void Joycons::Setup() { | ||
| 59 | u32 port = 0; | ||
| 60 | PreSetController(GetIdentifier(0, Joycon::ControllerType::None)); | ||
| 61 | for (auto& device : left_joycons) { | ||
| 62 | PreSetController(GetIdentifier(port, Joycon::ControllerType::Left)); | ||
| 63 | device = std::make_shared<Joycon::JoyconDriver>(port++); | ||
| 64 | } | ||
| 65 | port = 0; | ||
| 66 | for (auto& device : right_joycons) { | ||
| 67 | PreSetController(GetIdentifier(port, Joycon::ControllerType::Right)); | ||
| 68 | device = std::make_shared<Joycon::JoyconDriver>(port++); | ||
| 69 | } | ||
| 70 | port = 0; | ||
| 71 | for (auto& device : pro_controller) { | ||
| 72 | PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro)); | ||
| 73 | device = std::make_shared<Joycon::JoyconDriver>(port++); | ||
| 74 | } | ||
| 75 | |||
| 76 | scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); }); | ||
| 77 | } | ||
| 78 | |||
| 79 | void Joycons::ScanThread(std::stop_token stop_token) { | ||
| 80 | constexpr u16 nintendo_vendor_id = 0x057e; | ||
| 81 | Common::SetCurrentThreadName("JoyconScanThread"); | ||
| 82 | |||
| 83 | do { | ||
| 84 | SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0); | ||
| 85 | SDL_hid_device_info* cur_dev = devs; | ||
| 86 | |||
| 87 | while (cur_dev) { | ||
| 88 | if (IsDeviceNew(cur_dev)) { | ||
| 89 | LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id, | ||
| 90 | cur_dev->product_id); | ||
| 91 | RegisterNewDevice(cur_dev); | ||
| 92 | } | ||
| 93 | cur_dev = cur_dev->next; | ||
| 94 | } | ||
| 95 | |||
| 96 | SDL_hid_free_enumeration(devs); | ||
| 97 | } while (Common::StoppableTimedWait(stop_token, std::chrono::seconds{5})); | ||
| 98 | } | ||
| 99 | |||
| 100 | bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const { | ||
| 101 | Joycon::ControllerType type{}; | ||
| 102 | Joycon::SerialNumber serial_number{}; | ||
| 103 | |||
| 104 | const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); | ||
| 105 | if (result != Joycon::DriverResult::Success) { | ||
| 106 | return false; | ||
| 107 | } | ||
| 108 | |||
| 109 | const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number); | ||
| 110 | if (result2 != Joycon::DriverResult::Success) { | ||
| 111 | return false; | ||
| 112 | } | ||
| 113 | |||
| 114 | auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) { | ||
| 115 | if (!device) { | ||
| 116 | return false; | ||
| 117 | } | ||
| 118 | if (!device->IsConnected()) { | ||
| 119 | return false; | ||
| 120 | } | ||
| 121 | if (device->GetHandleSerialNumber() != serial_number) { | ||
| 122 | return false; | ||
| 123 | } | ||
| 124 | return true; | ||
| 125 | }; | ||
| 126 | |||
| 127 | // Check if device already exist | ||
| 128 | switch (type) { | ||
| 129 | case Joycon::ControllerType::Left: | ||
| 130 | if (!Settings::values.enable_joycon_driver) { | ||
| 131 | return false; | ||
| 132 | } | ||
| 133 | for (const auto& device : left_joycons) { | ||
| 134 | if (is_handle_identical(device)) { | ||
| 135 | return false; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | break; | ||
| 139 | case Joycon::ControllerType::Right: | ||
| 140 | if (!Settings::values.enable_joycon_driver) { | ||
| 141 | return false; | ||
| 142 | } | ||
| 143 | for (const auto& device : right_joycons) { | ||
| 144 | if (is_handle_identical(device)) { | ||
| 145 | return false; | ||
| 146 | } | ||
| 147 | } | ||
| 148 | break; | ||
| 149 | case Joycon::ControllerType::Pro: | ||
| 150 | if (!Settings::values.enable_procon_driver) { | ||
| 151 | return false; | ||
| 152 | } | ||
| 153 | for (const auto& device : pro_controller) { | ||
| 154 | if (is_handle_identical(device)) { | ||
| 155 | return false; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | break; | ||
| 159 | default: | ||
| 160 | return false; | ||
| 161 | } | ||
| 162 | |||
| 163 | return true; | ||
| 164 | } | ||
| 165 | |||
| 166 | void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { | ||
| 167 | Joycon::ControllerType type{}; | ||
| 168 | auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); | ||
| 169 | auto handle = GetNextFreeHandle(type); | ||
| 170 | if (handle == nullptr) { | ||
| 171 | LOG_WARNING(Input, "No free handles available"); | ||
| 172 | return; | ||
| 173 | } | ||
| 174 | if (result == Joycon::DriverResult::Success) { | ||
| 175 | result = handle->RequestDeviceAccess(device_info); | ||
| 176 | } | ||
| 177 | if (result == Joycon::DriverResult::Success) { | ||
| 178 | LOG_WARNING(Input, "Initialize device"); | ||
| 179 | |||
| 180 | const std::size_t port = handle->GetDevicePort(); | ||
| 181 | const Joycon::JoyconCallbacks callbacks{ | ||
| 182 | .on_battery_data = {[this, port, type](Joycon::Battery value) { | ||
| 183 | OnBatteryUpdate(port, type, value); | ||
| 184 | }}, | ||
| 185 | .on_color_data = {[this, port, type](Joycon::Color value) { | ||
| 186 | OnColorUpdate(port, type, value); | ||
| 187 | }}, | ||
| 188 | .on_button_data = {[this, port, type](int id, bool value) { | ||
| 189 | OnButtonUpdate(port, type, id, value); | ||
| 190 | }}, | ||
| 191 | .on_stick_data = {[this, port, type](int id, f32 value) { | ||
| 192 | OnStickUpdate(port, type, id, value); | ||
| 193 | }}, | ||
| 194 | .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) { | ||
| 195 | OnMotionUpdate(port, type, id, value); | ||
| 196 | }}, | ||
| 197 | .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, | ||
| 198 | .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) { | ||
| 199 | OnAmiiboUpdate(port, amiibo_data); | ||
| 200 | }}, | ||
| 201 | .on_camera_data = {[this, port](const std::vector<u8>& camera_data, | ||
| 202 | Joycon::IrsResolution format) { | ||
| 203 | OnCameraUpdate(port, camera_data, format); | ||
| 204 | }}, | ||
| 205 | }; | ||
| 206 | |||
| 207 | handle->InitializeDevice(); | ||
| 208 | handle->SetCallbacks(callbacks); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle( | ||
| 213 | Joycon::ControllerType type) const { | ||
| 214 | if (type == Joycon::ControllerType::Left) { | ||
| 215 | const auto unconnected_device = | ||
| 216 | std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); }); | ||
| 217 | if (unconnected_device != left_joycons.end()) { | ||
| 218 | return *unconnected_device; | ||
| 219 | } | ||
| 220 | } | ||
| 221 | if (type == Joycon::ControllerType::Right) { | ||
| 222 | const auto unconnected_device = std::ranges::find_if( | ||
| 223 | right_joycons, [](auto& device) { return !device->IsConnected(); }); | ||
| 224 | |||
| 225 | if (unconnected_device != right_joycons.end()) { | ||
| 226 | return *unconnected_device; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | if (type == Joycon::ControllerType::Pro) { | ||
| 230 | const auto unconnected_device = std::ranges::find_if( | ||
| 231 | pro_controller, [](auto& device) { return !device->IsConnected(); }); | ||
| 232 | |||
| 233 | if (unconnected_device != pro_controller.end()) { | ||
| 234 | return *unconnected_device; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | return nullptr; | ||
| 238 | } | ||
| 239 | |||
| 240 | bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) { | ||
| 241 | const auto handle = GetHandle(identifier); | ||
| 242 | if (handle == nullptr) { | ||
| 243 | return false; | ||
| 244 | } | ||
| 245 | return handle->IsVibrationEnabled(); | ||
| 246 | } | ||
| 247 | |||
| 248 | Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier, | ||
| 249 | const Common::Input::VibrationStatus& vibration) { | ||
| 250 | const Joycon::VibrationValue native_vibration{ | ||
| 251 | .low_amplitude = vibration.low_amplitude, | ||
| 252 | .low_frequency = vibration.low_frequency, | ||
| 253 | .high_amplitude = vibration.high_amplitude, | ||
| 254 | .high_frequency = vibration.high_frequency, | ||
| 255 | }; | ||
| 256 | auto handle = GetHandle(identifier); | ||
| 257 | if (handle == nullptr) { | ||
| 258 | return Common::Input::DriverResult::InvalidHandle; | ||
| 259 | } | ||
| 260 | |||
| 261 | handle->SetVibration(native_vibration); | ||
| 262 | return Common::Input::DriverResult::Success; | ||
| 263 | } | ||
| 264 | |||
| 265 | Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier, | ||
| 266 | const Common::Input::LedStatus& led_status) { | ||
| 267 | auto handle = GetHandle(identifier); | ||
| 268 | if (handle == nullptr) { | ||
| 269 | return Common::Input::DriverResult::InvalidHandle; | ||
| 270 | } | ||
| 271 | int led_config = led_status.led_1 ? 1 : 0; | ||
| 272 | led_config += led_status.led_2 ? 2 : 0; | ||
| 273 | led_config += led_status.led_3 ? 4 : 0; | ||
| 274 | led_config += led_status.led_4 ? 8 : 0; | ||
| 275 | |||
| 276 | return static_cast<Common::Input::DriverResult>( | ||
| 277 | handle->SetLedConfig(static_cast<u8>(led_config))); | ||
| 278 | } | ||
| 279 | |||
| 280 | Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier, | ||
| 281 | Common::Input::CameraFormat camera_format) { | ||
| 282 | auto handle = GetHandle(identifier); | ||
| 283 | if (handle == nullptr) { | ||
| 284 | return Common::Input::DriverResult::InvalidHandle; | ||
| 285 | } | ||
| 286 | return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig( | ||
| 287 | Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format))); | ||
| 288 | }; | ||
| 289 | |||
| 290 | Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const { | ||
| 291 | return Common::Input::NfcState::Success; | ||
| 292 | }; | ||
| 293 | |||
| 294 | Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_, | ||
| 295 | const std::vector<u8>& data) { | ||
| 296 | return Common::Input::NfcState::NotSupported; | ||
| 297 | }; | ||
| 298 | |||
| 299 | Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, | ||
| 300 | const Common::Input::PollingMode polling_mode) { | ||
| 301 | auto handle = GetHandle(identifier); | ||
| 302 | if (handle == nullptr) { | ||
| 303 | LOG_ERROR(Input, "Invalid handle {}", identifier.port); | ||
| 304 | return Common::Input::DriverResult::InvalidHandle; | ||
| 305 | } | ||
| 306 | |||
| 307 | switch (polling_mode) { | ||
| 308 | case Common::Input::PollingMode::Active: | ||
| 309 | return static_cast<Common::Input::DriverResult>(handle->SetActiveMode()); | ||
| 310 | case Common::Input::PollingMode::Pasive: | ||
| 311 | return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode()); | ||
| 312 | case Common::Input::PollingMode::IR: | ||
| 313 | return static_cast<Common::Input::DriverResult>(handle->SetIrMode()); | ||
| 314 | case Common::Input::PollingMode::NFC: | ||
| 315 | return static_cast<Common::Input::DriverResult>(handle->SetNfcMode()); | ||
| 316 | case Common::Input::PollingMode::Ring: | ||
| 317 | return static_cast<Common::Input::DriverResult>(handle->SetRingConMode()); | ||
| 318 | default: | ||
| 319 | return Common::Input::DriverResult::NotSupported; | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, | ||
| 324 | Joycon::Battery value) { | ||
| 325 | const auto identifier = GetIdentifier(port, type); | ||
| 326 | if (value.charging != 0) { | ||
| 327 | SetBattery(identifier, Common::Input::BatteryLevel::Charging); | ||
| 328 | return; | ||
| 329 | } | ||
| 330 | |||
| 331 | Common::Input::BatteryLevel battery{}; | ||
| 332 | switch (value.status) { | ||
| 333 | case 0: | ||
| 334 | battery = Common::Input::BatteryLevel::Empty; | ||
| 335 | break; | ||
| 336 | case 1: | ||
| 337 | battery = Common::Input::BatteryLevel::Critical; | ||
| 338 | break; | ||
| 339 | case 2: | ||
| 340 | battery = Common::Input::BatteryLevel::Low; | ||
| 341 | break; | ||
| 342 | case 3: | ||
| 343 | battery = Common::Input::BatteryLevel::Medium; | ||
| 344 | break; | ||
| 345 | case 4: | ||
| 346 | default: | ||
| 347 | battery = Common::Input::BatteryLevel::Full; | ||
| 348 | break; | ||
| 349 | } | ||
| 350 | SetBattery(identifier, battery); | ||
| 351 | } | ||
| 352 | |||
| 353 | void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type, | ||
| 354 | const Joycon::Color& value) { | ||
| 355 | const auto identifier = GetIdentifier(port, type); | ||
| 356 | Common::Input::BodyColorStatus color{ | ||
| 357 | .body = value.body, | ||
| 358 | .buttons = value.buttons, | ||
| 359 | .left_grip = value.left_grip, | ||
| 360 | .right_grip = value.right_grip, | ||
| 361 | }; | ||
| 362 | SetColor(identifier, color); | ||
| 363 | } | ||
| 364 | |||
| 365 | void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) { | ||
| 366 | const auto identifier = GetIdentifier(port, type); | ||
| 367 | SetButton(identifier, id, value); | ||
| 368 | } | ||
| 369 | |||
| 370 | void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) { | ||
| 371 | const auto identifier = GetIdentifier(port, type); | ||
| 372 | SetAxis(identifier, id, value); | ||
| 373 | } | ||
| 374 | |||
| 375 | void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, | ||
| 376 | const Joycon::MotionData& value) { | ||
| 377 | const auto identifier = GetIdentifier(port, type); | ||
| 378 | BasicMotion motion_data{ | ||
| 379 | .gyro_x = value.gyro_x, | ||
| 380 | .gyro_y = value.gyro_y, | ||
| 381 | .gyro_z = value.gyro_z, | ||
| 382 | .accel_x = value.accel_x, | ||
| 383 | .accel_y = value.accel_y, | ||
| 384 | .accel_z = value.accel_z, | ||
| 385 | .delta_timestamp = 15000, | ||
| 386 | }; | ||
| 387 | SetMotion(identifier, id, motion_data); | ||
| 388 | } | ||
| 389 | |||
| 390 | void Joycons::OnRingConUpdate(f32 ring_data) { | ||
| 391 | // To simplify ring detection it will always be mapped to an empty identifier for all | ||
| 392 | // controllers | ||
| 393 | static constexpr PadIdentifier identifier = { | ||
| 394 | .guid = Common::UUID{}, | ||
| 395 | .port = 0, | ||
| 396 | .pad = 0, | ||
| 397 | }; | ||
| 398 | SetAxis(identifier, 100, ring_data); | ||
| 399 | } | ||
| 400 | |||
| 401 | void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) { | ||
| 402 | const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); | ||
| 403 | const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved | ||
| 404 | : Common::Input::NfcState::NewAmiibo; | ||
| 405 | SetNfc(identifier, {nfc_state, amiibo_data}); | ||
| 406 | } | ||
| 407 | |||
| 408 | void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, | ||
| 409 | Joycon::IrsResolution format) { | ||
| 410 | const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); | ||
| 411 | SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data}); | ||
| 412 | } | ||
| 413 | |||
| 414 | std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const { | ||
| 415 | auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) { | ||
| 416 | if (!device) { | ||
| 417 | return false; | ||
| 418 | } | ||
| 419 | if (!device->IsConnected()) { | ||
| 420 | return false; | ||
| 421 | } | ||
| 422 | if (device->GetDevicePort() == identifier.port) { | ||
| 423 | return true; | ||
| 424 | } | ||
| 425 | return false; | ||
| 426 | }; | ||
| 427 | const auto type = static_cast<Joycon::ControllerType>(identifier.pad); | ||
| 428 | |||
| 429 | if (type == Joycon::ControllerType::Left) { | ||
| 430 | const auto matching_device = std::ranges::find_if( | ||
| 431 | left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); | ||
| 432 | |||
| 433 | if (matching_device != left_joycons.end()) { | ||
| 434 | return *matching_device; | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | if (type == Joycon::ControllerType::Right) { | ||
| 439 | const auto matching_device = std::ranges::find_if( | ||
| 440 | right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); | ||
| 441 | |||
| 442 | if (matching_device != right_joycons.end()) { | ||
| 443 | return *matching_device; | ||
| 444 | } | ||
| 445 | } | ||
| 446 | |||
| 447 | if (type == Joycon::ControllerType::Pro) { | ||
| 448 | const auto matching_device = std::ranges::find_if( | ||
| 449 | pro_controller, [is_handle_active](auto& device) { return is_handle_active(device); }); | ||
| 450 | |||
| 451 | if (matching_device != pro_controller.end()) { | ||
| 452 | return *matching_device; | ||
| 453 | } | ||
| 454 | } | ||
| 455 | |||
| 456 | return nullptr; | ||
| 457 | } | ||
| 458 | |||
| 459 | PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const { | ||
| 460 | const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0, | ||
| 461 | 0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)}; | ||
| 462 | return { | ||
| 463 | .guid = Common::UUID{guid}, | ||
| 464 | .port = port, | ||
| 465 | .pad = static_cast<std::size_t>(type), | ||
| 466 | }; | ||
| 467 | } | ||
| 468 | |||
| 469 | Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const { | ||
| 470 | const auto identifier = GetIdentifier(port, type); | ||
| 471 | return { | ||
| 472 | {"engine", GetEngineName()}, | ||
| 473 | {"guid", identifier.guid.RawString()}, | ||
| 474 | {"port", std::to_string(identifier.port)}, | ||
| 475 | {"pad", std::to_string(identifier.pad)}, | ||
| 476 | }; | ||
| 477 | } | ||
| 478 | |||
| 479 | std::vector<Common::ParamPackage> Joycons::GetInputDevices() const { | ||
| 480 | std::vector<Common::ParamPackage> devices{}; | ||
| 481 | |||
| 482 | auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) { | ||
| 483 | if (!device) { | ||
| 484 | return; | ||
| 485 | } | ||
| 486 | if (!device->IsConnected()) { | ||
| 487 | return; | ||
| 488 | } | ||
| 489 | auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType()); | ||
| 490 | std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()), | ||
| 491 | device->GetDevicePort() + 1); | ||
| 492 | param.Set("display", std::move(name)); | ||
| 493 | devices.emplace_back(param); | ||
| 494 | }; | ||
| 495 | |||
| 496 | for (const auto& controller : left_joycons) { | ||
| 497 | add_entry(controller); | ||
| 498 | } | ||
| 499 | for (const auto& controller : right_joycons) { | ||
| 500 | add_entry(controller); | ||
| 501 | } | ||
| 502 | for (const auto& controller : pro_controller) { | ||
| 503 | add_entry(controller); | ||
| 504 | } | ||
| 505 | |||
| 506 | // List dual joycon pairs | ||
| 507 | for (std::size_t i = 0; i < MaxSupportedControllers; i++) { | ||
| 508 | if (!left_joycons[i] || !right_joycons[i]) { | ||
| 509 | continue; | ||
| 510 | } | ||
| 511 | if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) { | ||
| 512 | continue; | ||
| 513 | } | ||
| 514 | auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType()); | ||
| 515 | const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType()); | ||
| 516 | const auto type = Joycon::ControllerType::Dual; | ||
| 517 | std::string name = fmt::format("{} {}", JoyconName(type), i + 1); | ||
| 518 | |||
| 519 | main_param.Set("display", std::move(name)); | ||
| 520 | main_param.Set("guid2", second_param.Get("guid", "")); | ||
| 521 | main_param.Set("pad", std::to_string(static_cast<size_t>(type))); | ||
| 522 | devices.emplace_back(main_param); | ||
| 523 | } | ||
| 524 | |||
| 525 | return devices; | ||
| 526 | } | ||
| 527 | |||
| 528 | ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) { | ||
| 529 | static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>, | ||
| 530 | 18> | ||
| 531 | switch_to_joycon_button = { | ||
| 532 | std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true}, | ||
| 533 | {Settings::NativeButton::B, Joycon::PadButton::B, true}, | ||
| 534 | {Settings::NativeButton::X, Joycon::PadButton::X, true}, | ||
| 535 | {Settings::NativeButton::Y, Joycon::PadButton::Y, true}, | ||
| 536 | {Settings::NativeButton::DLeft, Joycon::PadButton::Left, false}, | ||
| 537 | {Settings::NativeButton::DUp, Joycon::PadButton::Up, false}, | ||
| 538 | {Settings::NativeButton::DRight, Joycon::PadButton::Right, false}, | ||
| 539 | {Settings::NativeButton::DDown, Joycon::PadButton::Down, false}, | ||
| 540 | {Settings::NativeButton::L, Joycon::PadButton::L, false}, | ||
| 541 | {Settings::NativeButton::R, Joycon::PadButton::R, true}, | ||
| 542 | {Settings::NativeButton::ZL, Joycon::PadButton::ZL, false}, | ||
| 543 | {Settings::NativeButton::ZR, Joycon::PadButton::ZR, true}, | ||
| 544 | {Settings::NativeButton::Plus, Joycon::PadButton::Plus, true}, | ||
| 545 | {Settings::NativeButton::Minus, Joycon::PadButton::Minus, false}, | ||
| 546 | {Settings::NativeButton::Home, Joycon::PadButton::Home, true}, | ||
| 547 | {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false}, | ||
| 548 | {Settings::NativeButton::LStick, Joycon::PadButton::StickL, false}, | ||
| 549 | {Settings::NativeButton::RStick, Joycon::PadButton::StickR, true}, | ||
| 550 | }; | ||
| 551 | |||
| 552 | if (!params.Has("port")) { | ||
| 553 | return {}; | ||
| 554 | } | ||
| 555 | |||
| 556 | ButtonMapping mapping{}; | ||
| 557 | for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) { | ||
| 558 | const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); | ||
| 559 | auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); | ||
| 560 | if (pad == Joycon::ControllerType::Dual) { | ||
| 561 | pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left; | ||
| 562 | } | ||
| 563 | |||
| 564 | Common::ParamPackage button_params = GetParamPackage(port, pad); | ||
| 565 | button_params.Set("button", static_cast<int>(joycon_button)); | ||
| 566 | mapping.insert_or_assign(switch_button, std::move(button_params)); | ||
| 567 | } | ||
| 568 | |||
| 569 | // Map SL and SR buttons for left joycons | ||
| 570 | if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) { | ||
| 571 | const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); | ||
| 572 | Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left); | ||
| 573 | |||
| 574 | Common::ParamPackage sl_button_params = button_params; | ||
| 575 | Common::ParamPackage sr_button_params = button_params; | ||
| 576 | sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL)); | ||
| 577 | sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR)); | ||
| 578 | mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); | ||
| 579 | mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); | ||
| 580 | } | ||
| 581 | |||
| 582 | // Map SL and SR buttons for right joycons | ||
| 583 | if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) { | ||
| 584 | const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); | ||
| 585 | Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right); | ||
| 586 | |||
| 587 | Common::ParamPackage sl_button_params = button_params; | ||
| 588 | Common::ParamPackage sr_button_params = button_params; | ||
| 589 | sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL)); | ||
| 590 | sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR)); | ||
| 591 | mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); | ||
| 592 | mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); | ||
| 593 | } | ||
| 594 | |||
| 595 | return mapping; | ||
| 596 | } | ||
| 597 | |||
| 598 | AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) { | ||
| 599 | if (!params.Has("port")) { | ||
| 600 | return {}; | ||
| 601 | } | ||
| 602 | |||
| 603 | const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); | ||
| 604 | auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); | ||
| 605 | auto pad_right = pad_left; | ||
| 606 | if (pad_left == Joycon::ControllerType::Dual) { | ||
| 607 | pad_left = Joycon::ControllerType::Left; | ||
| 608 | pad_right = Joycon::ControllerType::Right; | ||
| 609 | } | ||
| 610 | |||
| 611 | AnalogMapping mapping = {}; | ||
| 612 | Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left); | ||
| 613 | left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX)); | ||
| 614 | left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY)); | ||
| 615 | mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); | ||
| 616 | Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right); | ||
| 617 | right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX)); | ||
| 618 | right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY)); | ||
| 619 | mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); | ||
| 620 | return mapping; | ||
| 621 | } | ||
| 622 | |||
| 623 | MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) { | ||
| 624 | if (!params.Has("port")) { | ||
| 625 | return {}; | ||
| 626 | } | ||
| 627 | |||
| 628 | const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); | ||
| 629 | auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); | ||
| 630 | auto pad_right = pad_left; | ||
| 631 | if (pad_left == Joycon::ControllerType::Dual) { | ||
| 632 | pad_left = Joycon::ControllerType::Left; | ||
| 633 | pad_right = Joycon::ControllerType::Right; | ||
| 634 | } | ||
| 635 | |||
| 636 | MotionMapping mapping = {}; | ||
| 637 | Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left); | ||
| 638 | left_motion_params.Set("motion", 0); | ||
| 639 | mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params)); | ||
| 640 | Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right); | ||
| 641 | right_Motion_params.Set("motion", 1); | ||
| 642 | mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params)); | ||
| 643 | return mapping; | ||
| 644 | } | ||
| 645 | |||
| 646 | Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const { | ||
| 647 | const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0)); | ||
| 648 | switch (button) { | ||
| 649 | case Joycon::PadButton::Left: | ||
| 650 | return Common::Input::ButtonNames::ButtonLeft; | ||
| 651 | case Joycon::PadButton::Right: | ||
| 652 | return Common::Input::ButtonNames::ButtonRight; | ||
| 653 | case Joycon::PadButton::Down: | ||
| 654 | return Common::Input::ButtonNames::ButtonDown; | ||
| 655 | case Joycon::PadButton::Up: | ||
| 656 | return Common::Input::ButtonNames::ButtonUp; | ||
| 657 | case Joycon::PadButton::LeftSL: | ||
| 658 | case Joycon::PadButton::RightSL: | ||
| 659 | return Common::Input::ButtonNames::TriggerSL; | ||
| 660 | case Joycon::PadButton::LeftSR: | ||
| 661 | case Joycon::PadButton::RightSR: | ||
| 662 | return Common::Input::ButtonNames::TriggerSR; | ||
| 663 | case Joycon::PadButton::L: | ||
| 664 | return Common::Input::ButtonNames::TriggerL; | ||
| 665 | case Joycon::PadButton::R: | ||
| 666 | return Common::Input::ButtonNames::TriggerR; | ||
| 667 | case Joycon::PadButton::ZL: | ||
| 668 | return Common::Input::ButtonNames::TriggerZL; | ||
| 669 | case Joycon::PadButton::ZR: | ||
| 670 | return Common::Input::ButtonNames::TriggerZR; | ||
| 671 | case Joycon::PadButton::A: | ||
| 672 | return Common::Input::ButtonNames::ButtonA; | ||
| 673 | case Joycon::PadButton::B: | ||
| 674 | return Common::Input::ButtonNames::ButtonB; | ||
| 675 | case Joycon::PadButton::X: | ||
| 676 | return Common::Input::ButtonNames::ButtonX; | ||
| 677 | case Joycon::PadButton::Y: | ||
| 678 | return Common::Input::ButtonNames::ButtonY; | ||
| 679 | case Joycon::PadButton::Plus: | ||
| 680 | return Common::Input::ButtonNames::ButtonPlus; | ||
| 681 | case Joycon::PadButton::Minus: | ||
| 682 | return Common::Input::ButtonNames::ButtonMinus; | ||
| 683 | case Joycon::PadButton::Home: | ||
| 684 | return Common::Input::ButtonNames::ButtonHome; | ||
| 685 | case Joycon::PadButton::Capture: | ||
| 686 | return Common::Input::ButtonNames::ButtonCapture; | ||
| 687 | case Joycon::PadButton::StickL: | ||
| 688 | return Common::Input::ButtonNames::ButtonStickL; | ||
| 689 | case Joycon::PadButton::StickR: | ||
| 690 | return Common::Input::ButtonNames::ButtonStickR; | ||
| 691 | default: | ||
| 692 | return Common::Input::ButtonNames::Undefined; | ||
| 693 | } | ||
| 694 | } | ||
| 695 | |||
| 696 | Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const { | ||
| 697 | if (params.Has("button")) { | ||
| 698 | return GetUIButtonName(params); | ||
| 699 | } | ||
| 700 | if (params.Has("axis")) { | ||
| 701 | return Common::Input::ButtonNames::Value; | ||
| 702 | } | ||
| 703 | if (params.Has("motion")) { | ||
| 704 | return Common::Input::ButtonNames::Engine; | ||
| 705 | } | ||
| 706 | |||
| 707 | return Common::Input::ButtonNames::Invalid; | ||
| 708 | } | ||
| 709 | |||
| 710 | std::string Joycons::JoyconName(Joycon::ControllerType type) const { | ||
| 711 | switch (type) { | ||
| 712 | case Joycon::ControllerType::Left: | ||
| 713 | return "Left Joycon"; | ||
| 714 | case Joycon::ControllerType::Right: | ||
| 715 | return "Right Joycon"; | ||
| 716 | case Joycon::ControllerType::Pro: | ||
| 717 | return "Pro Controller"; | ||
| 718 | case Joycon::ControllerType::Dual: | ||
| 719 | return "Dual Joycon"; | ||
| 720 | default: | ||
| 721 | return "Unknown Switch Controller"; | ||
| 722 | } | ||
| 723 | } | ||
| 724 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h new file mode 100644 index 000000000..473ba1b9e --- /dev/null +++ b/src/input_common/drivers/joycon.h | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <span> | ||
| 8 | #include <thread> | ||
| 9 | #include <SDL_hidapi.h> | ||
| 10 | |||
| 11 | #include "input_common/input_engine.h" | ||
| 12 | |||
| 13 | namespace InputCommon::Joycon { | ||
| 14 | using SerialNumber = std::array<u8, 15>; | ||
| 15 | struct Battery; | ||
| 16 | struct Color; | ||
| 17 | struct MotionData; | ||
| 18 | enum class ControllerType : u8; | ||
| 19 | enum class DriverResult; | ||
| 20 | enum class IrsResolution; | ||
| 21 | class JoyconDriver; | ||
| 22 | } // namespace InputCommon::Joycon | ||
| 23 | |||
| 24 | namespace InputCommon { | ||
| 25 | |||
| 26 | class Joycons final : public InputCommon::InputEngine { | ||
| 27 | public: | ||
| 28 | explicit Joycons(const std::string& input_engine_); | ||
| 29 | |||
| 30 | ~Joycons(); | ||
| 31 | |||
| 32 | bool IsVibrationEnabled(const PadIdentifier& identifier) override; | ||
| 33 | Common::Input::DriverResult SetVibration( | ||
| 34 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; | ||
| 35 | |||
| 36 | Common::Input::DriverResult SetLeds(const PadIdentifier& identifier, | ||
| 37 | const Common::Input::LedStatus& led_status) override; | ||
| 38 | |||
| 39 | Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier, | ||
| 40 | Common::Input::CameraFormat camera_format) override; | ||
| 41 | |||
| 42 | Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; | ||
| 43 | Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, | ||
| 44 | const std::vector<u8>& data) override; | ||
| 45 | |||
| 46 | Common::Input::DriverResult SetPollingMode( | ||
| 47 | const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override; | ||
| 48 | |||
| 49 | /// Used for automapping features | ||
| 50 | std::vector<Common::ParamPackage> GetInputDevices() const override; | ||
| 51 | ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; | ||
| 52 | AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; | ||
| 53 | MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; | ||
| 54 | Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; | ||
| 55 | |||
| 56 | private: | ||
| 57 | static constexpr std::size_t MaxSupportedControllers = 8; | ||
| 58 | |||
| 59 | /// For shutting down, clear all data, join all threads, release usb devices | ||
| 60 | void Reset(); | ||
| 61 | |||
| 62 | /// Registers controllers, clears all data and starts the scan thread | ||
| 63 | void Setup(); | ||
| 64 | |||
| 65 | /// Actively searchs for new devices | ||
| 66 | void ScanThread(std::stop_token stop_token); | ||
| 67 | |||
| 68 | /// Returns true if device is valid and not registered | ||
| 69 | bool IsDeviceNew(SDL_hid_device_info* device_info) const; | ||
| 70 | |||
| 71 | /// Tries to connect to the new device | ||
| 72 | void RegisterNewDevice(SDL_hid_device_info* device_info); | ||
| 73 | |||
| 74 | /// Returns the next free handle | ||
| 75 | std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const; | ||
| 76 | |||
| 77 | void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value); | ||
| 78 | void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value); | ||
| 79 | void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value); | ||
| 80 | void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value); | ||
| 81 | void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, | ||
| 82 | const Joycon::MotionData& value); | ||
| 83 | void OnRingConUpdate(f32 ring_data); | ||
| 84 | void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data); | ||
| 85 | void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, | ||
| 86 | Joycon::IrsResolution format); | ||
| 87 | |||
| 88 | /// Returns a JoyconHandle corresponding to a PadIdentifier | ||
| 89 | std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const; | ||
| 90 | |||
| 91 | /// Returns a PadIdentifier corresponding to the port number and joycon type | ||
| 92 | PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const; | ||
| 93 | |||
| 94 | /// Returns a ParamPackage corresponding to the port number and joycon type | ||
| 95 | Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const; | ||
| 96 | |||
| 97 | std::string JoyconName(std::size_t port) const; | ||
| 98 | |||
| 99 | Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; | ||
| 100 | |||
| 101 | /// Returns the name of the device in text format | ||
| 102 | std::string JoyconName(Joycon::ControllerType type) const; | ||
| 103 | |||
| 104 | std::jthread scan_thread; | ||
| 105 | |||
| 106 | // Joycon types are split by type to ease supporting dualjoycon configurations | ||
| 107 | std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{}; | ||
| 108 | std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{}; | ||
| 109 | std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> pro_controller{}; | ||
| 110 | }; | ||
| 111 | |||
| 112 | } // namespace InputCommon | ||
diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index faf9cbdc3..da50e0a24 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp | |||
| @@ -15,23 +15,39 @@ constexpr int mouse_axis_y = 1; | |||
| 15 | constexpr int wheel_axis_x = 2; | 15 | constexpr int wheel_axis_x = 2; |
| 16 | constexpr int wheel_axis_y = 3; | 16 | constexpr int wheel_axis_y = 3; |
| 17 | constexpr int motion_wheel_y = 4; | 17 | constexpr int motion_wheel_y = 4; |
| 18 | constexpr int touch_axis_x = 10; | ||
| 19 | constexpr int touch_axis_y = 11; | ||
| 20 | constexpr PadIdentifier identifier = { | 18 | constexpr PadIdentifier identifier = { |
| 21 | .guid = Common::UUID{}, | 19 | .guid = Common::UUID{}, |
| 22 | .port = 0, | 20 | .port = 0, |
| 23 | .pad = 0, | 21 | .pad = 0, |
| 24 | }; | 22 | }; |
| 25 | 23 | ||
| 24 | constexpr PadIdentifier real_mouse_identifier = { | ||
| 25 | .guid = Common::UUID{}, | ||
| 26 | .port = 1, | ||
| 27 | .pad = 0, | ||
| 28 | }; | ||
| 29 | |||
| 30 | constexpr PadIdentifier touch_identifier = { | ||
| 31 | .guid = Common::UUID{}, | ||
| 32 | .port = 2, | ||
| 33 | .pad = 0, | ||
| 34 | }; | ||
| 35 | |||
| 26 | Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | 36 | Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) { |
| 27 | PreSetController(identifier); | 37 | PreSetController(identifier); |
| 38 | PreSetController(real_mouse_identifier); | ||
| 39 | PreSetController(touch_identifier); | ||
| 40 | |||
| 41 | // Initialize all mouse axis | ||
| 28 | PreSetAxis(identifier, mouse_axis_x); | 42 | PreSetAxis(identifier, mouse_axis_x); |
| 29 | PreSetAxis(identifier, mouse_axis_y); | 43 | PreSetAxis(identifier, mouse_axis_y); |
| 30 | PreSetAxis(identifier, wheel_axis_x); | 44 | PreSetAxis(identifier, wheel_axis_x); |
| 31 | PreSetAxis(identifier, wheel_axis_y); | 45 | PreSetAxis(identifier, wheel_axis_y); |
| 32 | PreSetAxis(identifier, motion_wheel_y); | 46 | PreSetAxis(identifier, motion_wheel_y); |
| 33 | PreSetAxis(identifier, touch_axis_x); | 47 | PreSetAxis(real_mouse_identifier, mouse_axis_x); |
| 34 | PreSetAxis(identifier, touch_axis_y); | 48 | PreSetAxis(real_mouse_identifier, mouse_axis_y); |
| 49 | PreSetAxis(touch_identifier, mouse_axis_x); | ||
| 50 | PreSetAxis(touch_identifier, mouse_axis_y); | ||
| 35 | update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); | 51 | update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); |
| 36 | } | 52 | } |
| 37 | 53 | ||
| @@ -39,7 +55,7 @@ void Mouse::UpdateThread(std::stop_token stop_token) { | |||
| 39 | Common::SetCurrentThreadName("Mouse"); | 55 | Common::SetCurrentThreadName("Mouse"); |
| 40 | constexpr int update_time = 10; | 56 | constexpr int update_time = 10; |
| 41 | while (!stop_token.stop_requested()) { | 57 | while (!stop_token.stop_requested()) { |
| 42 | if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { | 58 | if (Settings::values.mouse_panning) { |
| 43 | // Slow movement by 4% | 59 | // Slow movement by 4% |
| 44 | last_mouse_change *= 0.96f; | 60 | last_mouse_change *= 0.96f; |
| 45 | const float sensitivity = | 61 | const float sensitivity = |
| @@ -57,17 +73,7 @@ void Mouse::UpdateThread(std::stop_token stop_token) { | |||
| 57 | } | 73 | } |
| 58 | } | 74 | } |
| 59 | 75 | ||
| 60 | void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) { | 76 | void Mouse::Move(int x, int y, int center_x, int center_y) { |
| 61 | // If native mouse is enabled just set the screen coordinates | ||
| 62 | if (Settings::values.mouse_enabled) { | ||
| 63 | SetAxis(identifier, mouse_axis_x, touch_x); | ||
| 64 | SetAxis(identifier, mouse_axis_y, touch_y); | ||
| 65 | return; | ||
| 66 | } | ||
| 67 | |||
| 68 | SetAxis(identifier, touch_axis_x, touch_x); | ||
| 69 | SetAxis(identifier, touch_axis_y, touch_y); | ||
| 70 | |||
| 71 | if (Settings::values.mouse_panning) { | 77 | if (Settings::values.mouse_panning) { |
| 72 | auto mouse_change = | 78 | auto mouse_change = |
| 73 | (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); | 79 | (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); |
| @@ -113,20 +119,41 @@ void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int | |||
| 113 | } | 119 | } |
| 114 | } | 120 | } |
| 115 | 121 | ||
| 116 | void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) { | 122 | void Mouse::MouseMove(f32 touch_x, f32 touch_y) { |
| 117 | SetAxis(identifier, touch_axis_x, touch_x); | 123 | SetAxis(real_mouse_identifier, mouse_axis_x, touch_x); |
| 118 | SetAxis(identifier, touch_axis_y, touch_y); | 124 | SetAxis(real_mouse_identifier, mouse_axis_y, touch_y); |
| 125 | } | ||
| 126 | |||
| 127 | void Mouse::TouchMove(f32 touch_x, f32 touch_y) { | ||
| 128 | SetAxis(touch_identifier, mouse_axis_x, touch_x); | ||
| 129 | SetAxis(touch_identifier, mouse_axis_y, touch_y); | ||
| 130 | } | ||
| 131 | |||
| 132 | void Mouse::PressButton(int x, int y, MouseButton button) { | ||
| 119 | SetButton(identifier, static_cast<int>(button), true); | 133 | SetButton(identifier, static_cast<int>(button), true); |
| 134 | |||
| 120 | // Set initial analog parameters | 135 | // Set initial analog parameters |
| 121 | mouse_origin = {x, y}; | 136 | mouse_origin = {x, y}; |
| 122 | last_mouse_position = {x, y}; | 137 | last_mouse_position = {x, y}; |
| 123 | button_pressed = true; | 138 | button_pressed = true; |
| 124 | } | 139 | } |
| 125 | 140 | ||
| 141 | void Mouse::PressMouseButton(MouseButton button) { | ||
| 142 | SetButton(real_mouse_identifier, static_cast<int>(button), true); | ||
| 143 | } | ||
| 144 | |||
| 145 | void Mouse::PressTouchButton(f32 touch_x, f32 touch_y, MouseButton button) { | ||
| 146 | SetAxis(touch_identifier, mouse_axis_x, touch_x); | ||
| 147 | SetAxis(touch_identifier, mouse_axis_y, touch_y); | ||
| 148 | SetButton(touch_identifier, static_cast<int>(button), true); | ||
| 149 | } | ||
| 150 | |||
| 126 | void Mouse::ReleaseButton(MouseButton button) { | 151 | void Mouse::ReleaseButton(MouseButton button) { |
| 127 | SetButton(identifier, static_cast<int>(button), false); | 152 | SetButton(identifier, static_cast<int>(button), false); |
| 153 | SetButton(real_mouse_identifier, static_cast<int>(button), false); | ||
| 154 | SetButton(touch_identifier, static_cast<int>(button), false); | ||
| 128 | 155 | ||
| 129 | if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) { | 156 | if (!Settings::values.mouse_panning) { |
| 130 | SetAxis(identifier, mouse_axis_x, 0); | 157 | SetAxis(identifier, mouse_axis_x, 0); |
| 131 | SetAxis(identifier, mouse_axis_y, 0); | 158 | SetAxis(identifier, mouse_axis_y, 0); |
| 132 | } | 159 | } |
diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index 72073cc23..f3b65bdd1 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h | |||
| @@ -37,13 +37,43 @@ public: | |||
| 37 | * @param center_x the x-coordinate of the middle of the screen | 37 | * @param center_x the x-coordinate of the middle of the screen |
| 38 | * @param center_y the y-coordinate of the middle of the screen | 38 | * @param center_y the y-coordinate of the middle of the screen |
| 39 | */ | 39 | */ |
| 40 | void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y); | 40 | void Move(int x, int y, int center_x, int center_y); |
| 41 | 41 | ||
| 42 | /** | 42 | /** |
| 43 | * Sets the status of all buttons bound with the key to pressed | 43 | * Signals that real mouse has moved. |
| 44 | * @param key_code the code of the key to press | 44 | * @param x the absolute position on the touchscreen of the cursor |
| 45 | * @param y the absolute position on the touchscreen of the cursor | ||
| 45 | */ | 46 | */ |
| 46 | void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button); | 47 | void MouseMove(f32 touch_x, f32 touch_y); |
| 48 | |||
| 49 | /** | ||
| 50 | * Signals that touch finger has moved. | ||
| 51 | * @param x the absolute position on the touchscreen of the cursor | ||
| 52 | * @param y the absolute position on the touchscreen of the cursor | ||
| 53 | */ | ||
| 54 | void TouchMove(f32 touch_x, f32 touch_y); | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Sets the status of a button to pressed | ||
| 58 | * @param x the x-coordinate of the cursor | ||
| 59 | * @param y the y-coordinate of the cursor | ||
| 60 | * @param button the id of the button to press | ||
| 61 | */ | ||
| 62 | void PressButton(int x, int y, MouseButton button); | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Sets the status of a mouse button to pressed | ||
| 66 | * @param button the id of the button to press | ||
| 67 | */ | ||
| 68 | void PressMouseButton(MouseButton button); | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Sets the status of touch finger to pressed | ||
| 72 | * @param x the absolute position on the touchscreen of the cursor | ||
| 73 | * @param y the absolute position on the touchscreen of the cursor | ||
| 74 | * @param button the id of the button to press | ||
| 75 | */ | ||
| 76 | void PressTouchButton(f32 touch_x, f32 touch_y, MouseButton button); | ||
| 47 | 77 | ||
| 48 | /** | 78 | /** |
| 49 | * Sets the status of all buttons bound with the key to released | 79 | * Sets the status of all buttons bound with the key to released |
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 4818bb744..5c20b3426 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp | |||
| @@ -40,25 +40,26 @@ public: | |||
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | void EnableMotion() { | 42 | void EnableMotion() { |
| 43 | if (sdl_controller) { | 43 | if (!sdl_controller) { |
| 44 | SDL_GameController* controller = sdl_controller.get(); | 44 | return; |
| 45 | has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; | 45 | } |
| 46 | has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; | 46 | SDL_GameController* controller = sdl_controller.get(); |
| 47 | if (has_accel) { | 47 | if (HasMotion()) { |
| 48 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); | 48 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE); |
| 49 | } | 49 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE); |
| 50 | if (has_gyro) { | 50 | } |
| 51 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); | 51 | has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; |
| 52 | } | 52 | has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; |
| 53 | if (has_accel) { | ||
| 54 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); | ||
| 55 | } | ||
| 56 | if (has_gyro) { | ||
| 57 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); | ||
| 53 | } | 58 | } |
| 54 | } | 59 | } |
| 55 | 60 | ||
| 56 | bool HasGyro() const { | 61 | bool HasMotion() const { |
| 57 | return has_gyro; | 62 | return has_gyro || has_accel; |
| 58 | } | ||
| 59 | |||
| 60 | bool HasAccel() const { | ||
| 61 | return has_accel; | ||
| 62 | } | 63 | } |
| 63 | 64 | ||
| 64 | bool UpdateMotion(SDL_ControllerSensorEvent event) { | 65 | bool UpdateMotion(SDL_ControllerSensorEvent event) { |
| @@ -85,6 +86,20 @@ public: | |||
| 85 | if (time_difference == 0) { | 86 | if (time_difference == 0) { |
| 86 | return false; | 87 | return false; |
| 87 | } | 88 | } |
| 89 | |||
| 90 | // Motion data is invalid | ||
| 91 | if (motion.accel_x == 0 && motion.gyro_x == 0 && motion.accel_y == 0 && | ||
| 92 | motion.gyro_y == 0 && motion.accel_z == 0 && motion.gyro_z == 0) { | ||
| 93 | if (motion_error_count++ < 200) { | ||
| 94 | return false; | ||
| 95 | } | ||
| 96 | // Try restarting the sensor | ||
| 97 | motion_error_count = 0; | ||
| 98 | EnableMotion(); | ||
| 99 | return false; | ||
| 100 | } | ||
| 101 | |||
| 102 | motion_error_count = 0; | ||
| 88 | motion.delta_timestamp = time_difference * 1000; | 103 | motion.delta_timestamp = time_difference * 1000; |
| 89 | return true; | 104 | return true; |
| 90 | } | 105 | } |
| @@ -250,6 +265,7 @@ private: | |||
| 250 | mutable std::mutex mutex; | 265 | mutable std::mutex mutex; |
| 251 | 266 | ||
| 252 | u64 last_motion_update{}; | 267 | u64 last_motion_update{}; |
| 268 | std::size_t motion_error_count{}; | ||
| 253 | bool has_gyro{false}; | 269 | bool has_gyro{false}; |
| 254 | bool has_accel{false}; | 270 | bool has_accel{false}; |
| 255 | bool has_vibration{false}; | 271 | bool has_vibration{false}; |
| @@ -318,6 +334,23 @@ void SDLDriver::InitJoystick(int joystick_index) { | |||
| 318 | 334 | ||
| 319 | const auto guid = GetGUID(sdl_joystick); | 335 | const auto guid = GetGUID(sdl_joystick); |
| 320 | 336 | ||
| 337 | if (Settings::values.enable_joycon_driver) { | ||
| 338 | if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && | ||
| 339 | (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) { | ||
| 340 | LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); | ||
| 341 | SDL_JoystickClose(sdl_joystick); | ||
| 342 | return; | ||
| 343 | } | ||
| 344 | } | ||
| 345 | |||
| 346 | if (Settings::values.enable_procon_driver) { | ||
| 347 | if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && guid.uuid[8] == 0x09) { | ||
| 348 | LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); | ||
| 349 | SDL_JoystickClose(sdl_joystick); | ||
| 350 | return; | ||
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 321 | std::scoped_lock lock{joystick_map_mutex}; | 354 | std::scoped_lock lock{joystick_map_mutex}; |
| 322 | if (joystick_map.find(guid) == joystick_map.end()) { | 355 | if (joystick_map.find(guid) == joystick_map.end()) { |
| 323 | auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); | 356 | auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); |
| @@ -440,9 +473,19 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en | |||
| 440 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); | 473 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); |
| 441 | SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); | 474 | SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); |
| 442 | 475 | ||
| 443 | // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and | 476 | // Disable hidapi drivers for joycon controllers when the custom joycon driver is enabled |
| 444 | // not a generic one | 477 | if (Settings::values.enable_joycon_driver) { |
| 445 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); | 478 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0"); |
| 479 | } else { | ||
| 480 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); | ||
| 481 | } | ||
| 482 | |||
| 483 | // Disable hidapi drivers for pro controllers when the custom joycon driver is enabled | ||
| 484 | if (Settings::values.enable_procon_driver) { | ||
| 485 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0"); | ||
| 486 | } else { | ||
| 487 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); | ||
| 488 | } | ||
| 446 | 489 | ||
| 447 | // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native | 490 | // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native |
| 448 | // driver on Linux. | 491 | // driver on Linux. |
| @@ -532,7 +575,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const { | |||
| 532 | return devices; | 575 | return devices; |
| 533 | } | 576 | } |
| 534 | 577 | ||
| 535 | Common::Input::VibrationError SDLDriver::SetVibration( | 578 | Common::Input::DriverResult SDLDriver::SetVibration( |
| 536 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { | 579 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { |
| 537 | const auto joystick = | 580 | const auto joystick = |
| 538 | GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); | 581 | GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); |
| @@ -566,14 +609,14 @@ Common::Input::VibrationError SDLDriver::SetVibration( | |||
| 566 | .vibration = new_vibration, | 609 | .vibration = new_vibration, |
| 567 | }); | 610 | }); |
| 568 | 611 | ||
| 569 | return Common::Input::VibrationError::None; | 612 | return Common::Input::DriverResult::Success; |
| 570 | } | 613 | } |
| 571 | 614 | ||
| 572 | bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { | 615 | bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { |
| 573 | const auto joystick = | 616 | const auto joystick = |
| 574 | GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); | 617 | GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); |
| 575 | 618 | ||
| 576 | constexpr Common::Input::VibrationStatus test_vibration{ | 619 | static constexpr Common::Input::VibrationStatus test_vibration{ |
| 577 | .low_amplitude = 1, | 620 | .low_amplitude = 1, |
| 578 | .low_frequency = 160.0f, | 621 | .low_frequency = 160.0f, |
| 579 | .high_amplitude = 1, | 622 | .high_amplitude = 1, |
| @@ -581,7 +624,7 @@ bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { | |||
| 581 | .type = Common::Input::VibrationAmplificationType::Exponential, | 624 | .type = Common::Input::VibrationAmplificationType::Exponential, |
| 582 | }; | 625 | }; |
| 583 | 626 | ||
| 584 | constexpr Common::Input::VibrationStatus zero_vibration{ | 627 | static constexpr Common::Input::VibrationStatus zero_vibration{ |
| 585 | .low_amplitude = 0, | 628 | .low_amplitude = 0, |
| 586 | .low_frequency = 160.0f, | 629 | .low_frequency = 160.0f, |
| 587 | .high_amplitude = 0, | 630 | .high_amplitude = 0, |
| @@ -942,18 +985,18 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p | |||
| 942 | MotionMapping mapping = {}; | 985 | MotionMapping mapping = {}; |
| 943 | joystick->EnableMotion(); | 986 | joystick->EnableMotion(); |
| 944 | 987 | ||
| 945 | if (joystick->HasGyro() || joystick->HasAccel()) { | 988 | if (joystick->HasMotion()) { |
| 946 | mapping.insert_or_assign(Settings::NativeMotion::MotionRight, | 989 | mapping.insert_or_assign(Settings::NativeMotion::MotionRight, |
| 947 | BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); | 990 | BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); |
| 948 | } | 991 | } |
| 949 | if (params.Has("guid2")) { | 992 | if (params.Has("guid2")) { |
| 950 | joystick2->EnableMotion(); | 993 | joystick2->EnableMotion(); |
| 951 | if (joystick2->HasGyro() || joystick2->HasAccel()) { | 994 | if (joystick2->HasMotion()) { |
| 952 | mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, | 995 | mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, |
| 953 | BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); | 996 | BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); |
| 954 | } | 997 | } |
| 955 | } else { | 998 | } else { |
| 956 | if (joystick->HasGyro() || joystick->HasAccel()) { | 999 | if (joystick->HasMotion()) { |
| 957 | mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, | 1000 | mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, |
| 958 | BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); | 1001 | BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); |
| 959 | } | 1002 | } |
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: | |||
| 63 | 63 | ||
| 64 | bool IsStickInverted(const Common::ParamPackage& params) override; | 64 | bool IsStickInverted(const Common::ParamPackage& params) override; |
| 65 | 65 | ||
| 66 | Common::Input::VibrationError SetVibration( | 66 | Common::Input::DriverResult SetVibration( |
| 67 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; | 67 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; |
| 68 | 68 | ||
| 69 | bool IsVibrationEnabled(const PadIdentifier& identifier) override; | 69 | bool IsVibrationEnabled(const PadIdentifier& identifier) override; |
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp index f3ade90da..f3cb14c56 100644 --- a/src/input_common/drivers/tas_input.cpp +++ b/src/input_common/drivers/tas_input.cpp | |||
| @@ -156,10 +156,12 @@ void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) { | |||
| 156 | }; | 156 | }; |
| 157 | } | 157 | } |
| 158 | 158 | ||
| 159 | std::tuple<TasState, size_t, size_t> Tas::GetStatus() const { | 159 | std::tuple<TasState, size_t, std::array<size_t, PLAYER_NUMBER>> Tas::GetStatus() const { |
| 160 | TasState state; | 160 | TasState state; |
| 161 | std::array<size_t, PLAYER_NUMBER> lengths{0}; | ||
| 161 | if (is_recording) { | 162 | if (is_recording) { |
| 162 | return {TasState::Recording, 0, record_commands.size()}; | 163 | lengths[0] = record_commands.size(); |
| 164 | return {TasState::Recording, record_commands.size(), lengths}; | ||
| 163 | } | 165 | } |
| 164 | 166 | ||
| 165 | if (is_running) { | 167 | if (is_running) { |
| @@ -168,7 +170,11 @@ std::tuple<TasState, size_t, size_t> Tas::GetStatus() const { | |||
| 168 | state = TasState::Stopped; | 170 | state = TasState::Stopped; |
| 169 | } | 171 | } |
| 170 | 172 | ||
| 171 | return {state, current_command, script_length}; | 173 | for (size_t i = 0; i < PLAYER_NUMBER; i++) { |
| 174 | lengths[i] = commands[i].size(); | ||
| 175 | } | ||
| 176 | |||
| 177 | return {state, current_command, lengths}; | ||
| 172 | } | 178 | } |
| 173 | 179 | ||
| 174 | void Tas::UpdateThread() { | 180 | void Tas::UpdateThread() { |
diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h index 38a27a230..5be66d142 100644 --- a/src/input_common/drivers/tas_input.h +++ b/src/input_common/drivers/tas_input.h | |||
| @@ -124,7 +124,7 @@ public: | |||
| 124 | * Current playback progress ; | 124 | * Current playback progress ; |
| 125 | * Total length of script file currently loaded or being recorded | 125 | * Total length of script file currently loaded or being recorded |
| 126 | */ | 126 | */ |
| 127 | std::tuple<TasState, size_t, size_t> GetStatus() const; | 127 | std::tuple<TasState, size_t, std::array<size_t, PLAYER_NUMBER>> GetStatus() const; |
| 128 | 128 | ||
| 129 | private: | 129 | private: |
| 130 | enum class TasAxis : u8; | 130 | enum class TasAxis : u8; |
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index 63ffaca67..4a0268a4d 100644 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp | |||
| @@ -22,22 +22,23 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move( | |||
| 22 | 22 | ||
| 23 | VirtualAmiibo::~VirtualAmiibo() = default; | 23 | VirtualAmiibo::~VirtualAmiibo() = default; |
| 24 | 24 | ||
| 25 | Common::Input::PollingError VirtualAmiibo::SetPollingMode( | 25 | Common::Input::DriverResult VirtualAmiibo::SetPollingMode( |
| 26 | [[maybe_unused]] const PadIdentifier& identifier_, | 26 | [[maybe_unused]] const PadIdentifier& identifier_, |
| 27 | const Common::Input::PollingMode polling_mode_) { | 27 | const Common::Input::PollingMode polling_mode_) { |
| 28 | polling_mode = polling_mode_; | 28 | polling_mode = polling_mode_; |
| 29 | 29 | ||
| 30 | if (polling_mode == Common::Input::PollingMode::NFC) { | 30 | switch (polling_mode) { |
| 31 | case Common::Input::PollingMode::NFC: | ||
| 31 | if (state == State::Initialized) { | 32 | if (state == State::Initialized) { |
| 32 | state = State::WaitingForAmiibo; | 33 | state = State::WaitingForAmiibo; |
| 33 | } | 34 | } |
| 34 | } else { | 35 | return Common::Input::DriverResult::Success; |
| 36 | default: | ||
| 35 | if (state == State::AmiiboIsOpen) { | 37 | if (state == State::AmiiboIsOpen) { |
| 36 | CloseAmiibo(); | 38 | CloseAmiibo(); |
| 37 | } | 39 | } |
| 40 | return Common::Input::DriverResult::NotSupported; | ||
| 38 | } | 41 | } |
| 39 | |||
| 40 | return Common::Input::PollingError::None; | ||
| 41 | } | 42 | } |
| 42 | 43 | ||
| 43 | Common::Input::NfcState VirtualAmiibo::SupportsNfc( | 44 | Common::Input::NfcState VirtualAmiibo::SupportsNfc( |
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index 0f9dad333..13cacfc0a 100644 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h | |||
| @@ -36,7 +36,7 @@ public: | |||
| 36 | ~VirtualAmiibo() override; | 36 | ~VirtualAmiibo() override; |
| 37 | 37 | ||
| 38 | // Sets polling mode to a controller | 38 | // Sets polling mode to a controller |
| 39 | Common::Input::PollingError SetPollingMode( | 39 | Common::Input::DriverResult SetPollingMode( |
| 40 | const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; | 40 | const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; |
| 41 | 41 | ||
| 42 | Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; | 42 | Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; |
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp new file mode 100644 index 000000000..e65b6b845 --- /dev/null +++ b/src/input_common/helpers/joycon_driver.cpp | |||
| @@ -0,0 +1,576 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/logging/log.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "common/thread.h" | ||
| 7 | #include "input_common/helpers/joycon_driver.h" | ||
| 8 | #include "input_common/helpers/joycon_protocol/calibration.h" | ||
| 9 | #include "input_common/helpers/joycon_protocol/generic_functions.h" | ||
| 10 | #include "input_common/helpers/joycon_protocol/irs.h" | ||
| 11 | #include "input_common/helpers/joycon_protocol/nfc.h" | ||
| 12 | #include "input_common/helpers/joycon_protocol/poller.h" | ||
| 13 | #include "input_common/helpers/joycon_protocol/ringcon.h" | ||
| 14 | #include "input_common/helpers/joycon_protocol/rumble.h" | ||
| 15 | |||
| 16 | namespace InputCommon::Joycon { | ||
| 17 | JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} { | ||
| 18 | hidapi_handle = std::make_shared<JoyconHandle>(); | ||
| 19 | } | ||
| 20 | |||
| 21 | JoyconDriver::~JoyconDriver() { | ||
| 22 | Stop(); | ||
| 23 | } | ||
| 24 | |||
| 25 | void JoyconDriver::Stop() { | ||
| 26 | is_connected = false; | ||
| 27 | input_thread = {}; | ||
| 28 | } | ||
| 29 | |||
| 30 | DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) { | ||
| 31 | std::scoped_lock lock{mutex}; | ||
| 32 | |||
| 33 | handle_device_type = ControllerType::None; | ||
| 34 | GetDeviceType(device_info, handle_device_type); | ||
| 35 | if (handle_device_type == ControllerType::None) { | ||
| 36 | return DriverResult::UnsupportedControllerType; | ||
| 37 | } | ||
| 38 | |||
| 39 | hidapi_handle->handle = | ||
| 40 | SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); | ||
| 41 | std::memcpy(&handle_serial_number, device_info->serial_number, 15); | ||
| 42 | if (!hidapi_handle->handle) { | ||
| 43 | LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", | ||
| 44 | device_info->vendor_id, device_info->product_id); | ||
| 45 | return DriverResult::HandleInUse; | ||
| 46 | } | ||
| 47 | SDL_hid_set_nonblocking(hidapi_handle->handle, 1); | ||
| 48 | return DriverResult::Success; | ||
| 49 | } | ||
| 50 | |||
| 51 | DriverResult JoyconDriver::InitializeDevice() { | ||
| 52 | if (!hidapi_handle->handle) { | ||
| 53 | return DriverResult::InvalidHandle; | ||
| 54 | } | ||
| 55 | std::scoped_lock lock{mutex}; | ||
| 56 | disable_input_thread = true; | ||
| 57 | |||
| 58 | // Reset Counters | ||
| 59 | error_counter = 0; | ||
| 60 | hidapi_handle->packet_counter = 0; | ||
| 61 | |||
| 62 | // Reset external device status | ||
| 63 | starlink_connected = false; | ||
| 64 | ring_connected = false; | ||
| 65 | amiibo_detected = false; | ||
| 66 | |||
| 67 | // Set HW default configuration | ||
| 68 | vibration_enabled = true; | ||
| 69 | motion_enabled = true; | ||
| 70 | hidbus_enabled = false; | ||
| 71 | nfc_enabled = false; | ||
| 72 | passive_enabled = false; | ||
| 73 | irs_enabled = false; | ||
| 74 | gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; | ||
| 75 | gyro_performance = Joycon::GyroPerformance::HZ833; | ||
| 76 | accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; | ||
| 77 | accelerometer_performance = Joycon::AccelerometerPerformance::HZ100; | ||
| 78 | |||
| 79 | // Initialize HW Protocols | ||
| 80 | calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle); | ||
| 81 | generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle); | ||
| 82 | irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle); | ||
| 83 | nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle); | ||
| 84 | ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle); | ||
| 85 | rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle); | ||
| 86 | |||
| 87 | // Get fixed joycon info | ||
| 88 | generic_protocol->GetVersionNumber(version); | ||
| 89 | generic_protocol->SetLowPowerMode(false); | ||
| 90 | generic_protocol->GetColor(color); | ||
| 91 | if (handle_device_type == ControllerType::Pro) { | ||
| 92 | // Some 3rd party controllers aren't pro controllers | ||
| 93 | generic_protocol->GetControllerType(device_type); | ||
| 94 | } else { | ||
| 95 | device_type = handle_device_type; | ||
| 96 | } | ||
| 97 | generic_protocol->GetSerialNumber(serial_number); | ||
| 98 | supported_features = GetSupportedFeatures(); | ||
| 99 | |||
| 100 | // Get Calibration data | ||
| 101 | calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration); | ||
| 102 | calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration); | ||
| 103 | calibration_protocol->GetImuCalibration(motion_calibration); | ||
| 104 | |||
| 105 | // Set led status | ||
| 106 | generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port)); | ||
| 107 | |||
| 108 | // Apply HW configuration | ||
| 109 | SetPollingMode(); | ||
| 110 | |||
| 111 | // Initialize joycon poller | ||
| 112 | joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration, | ||
| 113 | right_stick_calibration, motion_calibration); | ||
| 114 | |||
| 115 | // Start pooling for data | ||
| 116 | is_connected = true; | ||
| 117 | if (!input_thread_running) { | ||
| 118 | input_thread = | ||
| 119 | std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); }); | ||
| 120 | } | ||
| 121 | |||
| 122 | disable_input_thread = false; | ||
| 123 | return DriverResult::Success; | ||
| 124 | } | ||
| 125 | |||
| 126 | void JoyconDriver::InputThread(std::stop_token stop_token) { | ||
| 127 | LOG_INFO(Input, "Joycon Adapter input thread started"); | ||
| 128 | Common::SetCurrentThreadName("JoyconInput"); | ||
| 129 | input_thread_running = true; | ||
| 130 | |||
| 131 | // Max update rate is 5ms, ensure we are always able to read a bit faster | ||
| 132 | constexpr int ThreadDelay = 2; | ||
| 133 | std::vector<u8> buffer(MaxBufferSize); | ||
| 134 | |||
| 135 | while (!stop_token.stop_requested()) { | ||
| 136 | int status = 0; | ||
| 137 | |||
| 138 | if (!IsInputThreadValid()) { | ||
| 139 | input_thread.request_stop(); | ||
| 140 | continue; | ||
| 141 | } | ||
| 142 | |||
| 143 | // By disabling the input thread we can ensure custom commands will succeed as no package is | ||
| 144 | // skipped | ||
| 145 | if (!disable_input_thread) { | ||
| 146 | status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(), | ||
| 147 | ThreadDelay); | ||
| 148 | } else { | ||
| 149 | std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay)); | ||
| 150 | } | ||
| 151 | |||
| 152 | if (IsPayloadCorrect(status, buffer)) { | ||
| 153 | OnNewData(buffer); | ||
| 154 | } | ||
| 155 | |||
| 156 | std::this_thread::yield(); | ||
| 157 | } | ||
| 158 | |||
| 159 | is_connected = false; | ||
| 160 | input_thread_running = false; | ||
| 161 | LOG_INFO(Input, "Joycon Adapter input thread stopped"); | ||
| 162 | } | ||
| 163 | |||
| 164 | void JoyconDriver::OnNewData(std::span<u8> buffer) { | ||
| 165 | const auto report_mode = static_cast<ReportMode>(buffer[0]); | ||
| 166 | |||
| 167 | // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion | ||
| 168 | // experience | ||
| 169 | switch (report_mode) { | ||
| 170 | case ReportMode::STANDARD_FULL_60HZ: | ||
| 171 | case ReportMode::NFC_IR_MODE_60HZ: | ||
| 172 | case ReportMode::SIMPLE_HID_MODE: { | ||
| 173 | const auto now = std::chrono::steady_clock::now(); | ||
| 174 | const auto new_delta_time = static_cast<u64>( | ||
| 175 | std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); | ||
| 176 | delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10; | ||
| 177 | last_update = now; | ||
| 178 | joycon_poller->UpdateColor(color); | ||
| 179 | break; | ||
| 180 | } | ||
| 181 | default: | ||
| 182 | break; | ||
| 183 | } | ||
| 184 | |||
| 185 | const MotionStatus motion_status{ | ||
| 186 | .is_enabled = motion_enabled, | ||
| 187 | .delta_time = delta_time, | ||
| 188 | .gyro_sensitivity = gyro_sensitivity, | ||
| 189 | .accelerometer_sensitivity = accelerometer_sensitivity, | ||
| 190 | }; | ||
| 191 | |||
| 192 | // TODO: Remove this when calibration is properly loaded and not calculated | ||
| 193 | if (ring_connected && report_mode == ReportMode::STANDARD_FULL_60HZ) { | ||
| 194 | InputReportActive data{}; | ||
| 195 | memcpy(&data, buffer.data(), sizeof(InputReportActive)); | ||
| 196 | calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input); | ||
| 197 | } | ||
| 198 | |||
| 199 | const RingStatus ring_status{ | ||
| 200 | .is_enabled = ring_connected, | ||
| 201 | .default_value = ring_calibration.default_value, | ||
| 202 | .max_value = ring_calibration.max_value, | ||
| 203 | .min_value = ring_calibration.min_value, | ||
| 204 | }; | ||
| 205 | |||
| 206 | if (irs_protocol->IsEnabled()) { | ||
| 207 | irs_protocol->RequestImage(buffer); | ||
| 208 | joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); | ||
| 209 | } | ||
| 210 | |||
| 211 | if (nfc_protocol->IsEnabled()) { | ||
| 212 | if (amiibo_detected) { | ||
| 213 | if (!nfc_protocol->HasAmiibo()) { | ||
| 214 | joycon_poller->UpdateAmiibo({}); | ||
| 215 | amiibo_detected = false; | ||
| 216 | return; | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | if (!amiibo_detected) { | ||
| 221 | std::vector<u8> data(0x21C); | ||
| 222 | const auto result = nfc_protocol->ScanAmiibo(data); | ||
| 223 | if (result == DriverResult::Success) { | ||
| 224 | joycon_poller->UpdateAmiibo(data); | ||
| 225 | amiibo_detected = true; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | switch (report_mode) { | ||
| 231 | case ReportMode::STANDARD_FULL_60HZ: | ||
| 232 | joycon_poller->ReadActiveMode(buffer, motion_status, ring_status); | ||
| 233 | break; | ||
| 234 | case ReportMode::NFC_IR_MODE_60HZ: | ||
| 235 | joycon_poller->ReadNfcIRMode(buffer, motion_status); | ||
| 236 | break; | ||
| 237 | case ReportMode::SIMPLE_HID_MODE: | ||
| 238 | joycon_poller->ReadPassiveMode(buffer); | ||
| 239 | break; | ||
| 240 | case ReportMode::SUBCMD_REPLY: | ||
| 241 | LOG_DEBUG(Input, "Unhandled command reply"); | ||
| 242 | break; | ||
| 243 | default: | ||
| 244 | LOG_ERROR(Input, "Report mode not Implemented {}", report_mode); | ||
| 245 | break; | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | DriverResult JoyconDriver::SetPollingMode() { | ||
| 250 | disable_input_thread = true; | ||
| 251 | |||
| 252 | rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); | ||
| 253 | |||
| 254 | if (motion_enabled && supported_features.motion) { | ||
| 255 | generic_protocol->EnableImu(true); | ||
| 256 | generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance, | ||
| 257 | accelerometer_sensitivity, accelerometer_performance); | ||
| 258 | } else { | ||
| 259 | generic_protocol->EnableImu(false); | ||
| 260 | } | ||
| 261 | |||
| 262 | if (irs_protocol->IsEnabled()) { | ||
| 263 | irs_protocol->DisableIrs(); | ||
| 264 | } | ||
| 265 | |||
| 266 | if (nfc_protocol->IsEnabled()) { | ||
| 267 | amiibo_detected = false; | ||
| 268 | nfc_protocol->DisableNfc(); | ||
| 269 | } | ||
| 270 | |||
| 271 | if (ring_protocol->IsEnabled()) { | ||
| 272 | ring_connected = false; | ||
| 273 | ring_protocol->DisableRingCon(); | ||
| 274 | } | ||
| 275 | |||
| 276 | if (irs_enabled && supported_features.irs) { | ||
| 277 | auto result = irs_protocol->EnableIrs(); | ||
| 278 | if (result == DriverResult::Success) { | ||
| 279 | disable_input_thread = false; | ||
| 280 | return result; | ||
| 281 | } | ||
| 282 | irs_protocol->DisableIrs(); | ||
| 283 | LOG_ERROR(Input, "Error enabling IRS"); | ||
| 284 | } | ||
| 285 | |||
| 286 | if (nfc_enabled && supported_features.nfc) { | ||
| 287 | auto result = nfc_protocol->EnableNfc(); | ||
| 288 | if (result == DriverResult::Success) { | ||
| 289 | result = nfc_protocol->StartNFCPollingMode(); | ||
| 290 | } | ||
| 291 | if (result == DriverResult::Success) { | ||
| 292 | disable_input_thread = false; | ||
| 293 | return result; | ||
| 294 | } | ||
| 295 | nfc_protocol->DisableNfc(); | ||
| 296 | LOG_ERROR(Input, "Error enabling NFC"); | ||
| 297 | } | ||
| 298 | |||
| 299 | if (hidbus_enabled && supported_features.hidbus) { | ||
| 300 | auto result = ring_protocol->EnableRingCon(); | ||
| 301 | if (result == DriverResult::Success) { | ||
| 302 | result = ring_protocol->StartRingconPolling(); | ||
| 303 | } | ||
| 304 | if (result == DriverResult::Success) { | ||
| 305 | ring_connected = true; | ||
| 306 | disable_input_thread = false; | ||
| 307 | return result; | ||
| 308 | } | ||
| 309 | ring_connected = false; | ||
| 310 | ring_protocol->DisableRingCon(); | ||
| 311 | LOG_ERROR(Input, "Error enabling Ringcon"); | ||
| 312 | } | ||
| 313 | |||
| 314 | if (passive_enabled && supported_features.passive) { | ||
| 315 | const auto result = generic_protocol->EnablePassiveMode(); | ||
| 316 | if (result == DriverResult::Success) { | ||
| 317 | disable_input_thread = false; | ||
| 318 | return result; | ||
| 319 | } | ||
| 320 | LOG_ERROR(Input, "Error enabling passive mode"); | ||
| 321 | } | ||
| 322 | |||
| 323 | // Default Mode | ||
| 324 | const auto result = generic_protocol->EnableActiveMode(); | ||
| 325 | if (result != DriverResult::Success) { | ||
| 326 | LOG_ERROR(Input, "Error enabling active mode"); | ||
| 327 | } | ||
| 328 | // Switch calls this function after enabling active mode | ||
| 329 | generic_protocol->TriggersElapsed(); | ||
| 330 | |||
| 331 | disable_input_thread = false; | ||
| 332 | return result; | ||
| 333 | } | ||
| 334 | |||
| 335 | JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() { | ||
| 336 | SupportedFeatures features{ | ||
| 337 | .passive = true, | ||
| 338 | .motion = true, | ||
| 339 | .vibration = true, | ||
| 340 | }; | ||
| 341 | |||
| 342 | if (device_type == ControllerType::Right) { | ||
| 343 | features.nfc = true; | ||
| 344 | features.irs = true; | ||
| 345 | features.hidbus = true; | ||
| 346 | } | ||
| 347 | |||
| 348 | if (device_type == ControllerType::Pro) { | ||
| 349 | features.nfc = true; | ||
| 350 | } | ||
| 351 | return features; | ||
| 352 | } | ||
| 353 | |||
| 354 | bool JoyconDriver::IsInputThreadValid() const { | ||
| 355 | if (!is_connected.load()) { | ||
| 356 | return false; | ||
| 357 | } | ||
| 358 | if (hidapi_handle->handle == nullptr) { | ||
| 359 | return false; | ||
| 360 | } | ||
| 361 | // Controller is not responding. Terminate connection | ||
| 362 | if (error_counter > MaxErrorCount) { | ||
| 363 | return false; | ||
| 364 | } | ||
| 365 | return true; | ||
| 366 | } | ||
| 367 | |||
| 368 | bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) { | ||
| 369 | if (status <= -1) { | ||
| 370 | error_counter++; | ||
| 371 | return false; | ||
| 372 | } | ||
| 373 | // There's no new data | ||
| 374 | if (status == 0) { | ||
| 375 | return false; | ||
| 376 | } | ||
| 377 | // No reply ever starts with zero | ||
| 378 | if (buffer[0] == 0x00) { | ||
| 379 | error_counter++; | ||
| 380 | return false; | ||
| 381 | } | ||
| 382 | error_counter = 0; | ||
| 383 | return true; | ||
| 384 | } | ||
| 385 | |||
| 386 | DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) { | ||
| 387 | std::scoped_lock lock{mutex}; | ||
| 388 | if (disable_input_thread) { | ||
| 389 | return DriverResult::HandleInUse; | ||
| 390 | } | ||
| 391 | return rumble_protocol->SendVibration(vibration); | ||
| 392 | } | ||
| 393 | |||
| 394 | DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { | ||
| 395 | std::scoped_lock lock{mutex}; | ||
| 396 | if (disable_input_thread) { | ||
| 397 | return DriverResult::HandleInUse; | ||
| 398 | } | ||
| 399 | return generic_protocol->SetLedPattern(led_pattern); | ||
| 400 | } | ||
| 401 | |||
| 402 | DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) { | ||
| 403 | std::scoped_lock lock{mutex}; | ||
| 404 | if (disable_input_thread) { | ||
| 405 | return DriverResult::HandleInUse; | ||
| 406 | } | ||
| 407 | disable_input_thread = true; | ||
| 408 | const auto result = irs_protocol->SetIrsConfig(mode_, format_); | ||
| 409 | disable_input_thread = false; | ||
| 410 | return result; | ||
| 411 | } | ||
| 412 | |||
| 413 | DriverResult JoyconDriver::SetPasiveMode() { | ||
| 414 | std::scoped_lock lock{mutex}; | ||
| 415 | motion_enabled = false; | ||
| 416 | hidbus_enabled = false; | ||
| 417 | nfc_enabled = false; | ||
| 418 | passive_enabled = true; | ||
| 419 | irs_enabled = false; | ||
| 420 | return SetPollingMode(); | ||
| 421 | } | ||
| 422 | |||
| 423 | DriverResult JoyconDriver::SetActiveMode() { | ||
| 424 | if (is_ring_disabled_by_irs) { | ||
| 425 | is_ring_disabled_by_irs = false; | ||
| 426 | SetActiveMode(); | ||
| 427 | return SetRingConMode(); | ||
| 428 | } | ||
| 429 | |||
| 430 | std::scoped_lock lock{mutex}; | ||
| 431 | motion_enabled = true; | ||
| 432 | hidbus_enabled = false; | ||
| 433 | nfc_enabled = false; | ||
| 434 | passive_enabled = false; | ||
| 435 | irs_enabled = false; | ||
| 436 | return SetPollingMode(); | ||
| 437 | } | ||
| 438 | |||
| 439 | DriverResult JoyconDriver::SetIrMode() { | ||
| 440 | std::scoped_lock lock{mutex}; | ||
| 441 | |||
| 442 | if (!supported_features.irs) { | ||
| 443 | return DriverResult::NotSupported; | ||
| 444 | } | ||
| 445 | |||
| 446 | if (ring_connected) { | ||
| 447 | is_ring_disabled_by_irs = true; | ||
| 448 | } | ||
| 449 | |||
| 450 | motion_enabled = false; | ||
| 451 | hidbus_enabled = false; | ||
| 452 | nfc_enabled = false; | ||
| 453 | passive_enabled = false; | ||
| 454 | irs_enabled = true; | ||
| 455 | return SetPollingMode(); | ||
| 456 | } | ||
| 457 | |||
| 458 | DriverResult JoyconDriver::SetNfcMode() { | ||
| 459 | std::scoped_lock lock{mutex}; | ||
| 460 | |||
| 461 | if (!supported_features.nfc) { | ||
| 462 | return DriverResult::NotSupported; | ||
| 463 | } | ||
| 464 | |||
| 465 | motion_enabled = true; | ||
| 466 | hidbus_enabled = false; | ||
| 467 | nfc_enabled = true; | ||
| 468 | passive_enabled = false; | ||
| 469 | irs_enabled = false; | ||
| 470 | return SetPollingMode(); | ||
| 471 | } | ||
| 472 | |||
| 473 | DriverResult JoyconDriver::SetRingConMode() { | ||
| 474 | std::scoped_lock lock{mutex}; | ||
| 475 | |||
| 476 | if (!supported_features.hidbus) { | ||
| 477 | return DriverResult::NotSupported; | ||
| 478 | } | ||
| 479 | |||
| 480 | motion_enabled = true; | ||
| 481 | hidbus_enabled = true; | ||
| 482 | nfc_enabled = false; | ||
| 483 | passive_enabled = false; | ||
| 484 | irs_enabled = false; | ||
| 485 | |||
| 486 | const auto result = SetPollingMode(); | ||
| 487 | |||
| 488 | if (!ring_connected) { | ||
| 489 | return DriverResult::NoDeviceDetected; | ||
| 490 | } | ||
| 491 | |||
| 492 | return result; | ||
| 493 | } | ||
| 494 | |||
| 495 | bool JoyconDriver::IsConnected() const { | ||
| 496 | std::scoped_lock lock{mutex}; | ||
| 497 | return is_connected.load(); | ||
| 498 | } | ||
| 499 | |||
| 500 | bool JoyconDriver::IsVibrationEnabled() const { | ||
| 501 | std::scoped_lock lock{mutex}; | ||
| 502 | return vibration_enabled; | ||
| 503 | } | ||
| 504 | |||
| 505 | FirmwareVersion JoyconDriver::GetDeviceVersion() const { | ||
| 506 | std::scoped_lock lock{mutex}; | ||
| 507 | return version; | ||
| 508 | } | ||
| 509 | |||
| 510 | Color JoyconDriver::GetDeviceColor() const { | ||
| 511 | std::scoped_lock lock{mutex}; | ||
| 512 | return color; | ||
| 513 | } | ||
| 514 | |||
| 515 | std::size_t JoyconDriver::GetDevicePort() const { | ||
| 516 | std::scoped_lock lock{mutex}; | ||
| 517 | return port; | ||
| 518 | } | ||
| 519 | |||
| 520 | ControllerType JoyconDriver::GetDeviceType() const { | ||
| 521 | std::scoped_lock lock{mutex}; | ||
| 522 | return device_type; | ||
| 523 | } | ||
| 524 | |||
| 525 | ControllerType JoyconDriver::GetHandleDeviceType() const { | ||
| 526 | std::scoped_lock lock{mutex}; | ||
| 527 | return handle_device_type; | ||
| 528 | } | ||
| 529 | |||
| 530 | SerialNumber JoyconDriver::GetSerialNumber() const { | ||
| 531 | std::scoped_lock lock{mutex}; | ||
| 532 | return serial_number; | ||
| 533 | } | ||
| 534 | |||
| 535 | SerialNumber JoyconDriver::GetHandleSerialNumber() const { | ||
| 536 | std::scoped_lock lock{mutex}; | ||
| 537 | return handle_serial_number; | ||
| 538 | } | ||
| 539 | |||
| 540 | void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) { | ||
| 541 | joycon_poller->SetCallbacks(callbacks); | ||
| 542 | } | ||
| 543 | |||
| 544 | DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, | ||
| 545 | ControllerType& controller_type) { | ||
| 546 | static constexpr std::array<std::pair<u32, ControllerType>, 6> supported_devices{ | ||
| 547 | std::pair<u32, ControllerType>{0x2006, ControllerType::Left}, | ||
| 548 | {0x2007, ControllerType::Right}, | ||
| 549 | {0x2009, ControllerType::Pro}, | ||
| 550 | }; | ||
| 551 | constexpr u16 nintendo_vendor_id = 0x057e; | ||
| 552 | |||
| 553 | controller_type = ControllerType::None; | ||
| 554 | if (device_info->vendor_id != nintendo_vendor_id) { | ||
| 555 | return DriverResult::UnsupportedControllerType; | ||
| 556 | } | ||
| 557 | |||
| 558 | for (const auto& [product_id, type] : supported_devices) { | ||
| 559 | if (device_info->product_id == static_cast<u16>(product_id)) { | ||
| 560 | controller_type = type; | ||
| 561 | return Joycon::DriverResult::Success; | ||
| 562 | } | ||
| 563 | } | ||
| 564 | return Joycon::DriverResult::UnsupportedControllerType; | ||
| 565 | } | ||
| 566 | |||
| 567 | DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info, | ||
| 568 | SerialNumber& serial_number) { | ||
| 569 | if (device_info->serial_number == nullptr) { | ||
| 570 | return DriverResult::Unknown; | ||
| 571 | } | ||
| 572 | std::memcpy(&serial_number, device_info->serial_number, 15); | ||
| 573 | return Joycon::DriverResult::Success; | ||
| 574 | } | ||
| 575 | |||
| 576 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h new file mode 100644 index 000000000..c1e189fa5 --- /dev/null +++ b/src/input_common/helpers/joycon_driver.h | |||
| @@ -0,0 +1,150 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <atomic> | ||
| 7 | #include <functional> | ||
| 8 | #include <mutex> | ||
| 9 | #include <span> | ||
| 10 | #include <thread> | ||
| 11 | |||
| 12 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 13 | |||
| 14 | namespace InputCommon::Joycon { | ||
| 15 | class CalibrationProtocol; | ||
| 16 | class GenericProtocol; | ||
| 17 | class IrsProtocol; | ||
| 18 | class NfcProtocol; | ||
| 19 | class JoyconPoller; | ||
| 20 | class RingConProtocol; | ||
| 21 | class RumbleProtocol; | ||
| 22 | |||
| 23 | class JoyconDriver final { | ||
| 24 | public: | ||
| 25 | explicit JoyconDriver(std::size_t port_); | ||
| 26 | |||
| 27 | ~JoyconDriver(); | ||
| 28 | |||
| 29 | DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info); | ||
| 30 | DriverResult InitializeDevice(); | ||
| 31 | void Stop(); | ||
| 32 | |||
| 33 | bool IsConnected() const; | ||
| 34 | bool IsVibrationEnabled() const; | ||
| 35 | |||
| 36 | FirmwareVersion GetDeviceVersion() const; | ||
| 37 | Color GetDeviceColor() const; | ||
| 38 | std::size_t GetDevicePort() const; | ||
| 39 | ControllerType GetDeviceType() const; | ||
| 40 | ControllerType GetHandleDeviceType() const; | ||
| 41 | SerialNumber GetSerialNumber() const; | ||
| 42 | SerialNumber GetHandleSerialNumber() const; | ||
| 43 | |||
| 44 | DriverResult SetVibration(const VibrationValue& vibration); | ||
| 45 | DriverResult SetLedConfig(u8 led_pattern); | ||
| 46 | DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_); | ||
| 47 | DriverResult SetPasiveMode(); | ||
| 48 | DriverResult SetActiveMode(); | ||
| 49 | DriverResult SetIrMode(); | ||
| 50 | DriverResult SetNfcMode(); | ||
| 51 | DriverResult SetRingConMode(); | ||
| 52 | |||
| 53 | void SetCallbacks(const JoyconCallbacks& callbacks); | ||
| 54 | |||
| 55 | // Returns device type from hidapi handle | ||
| 56 | static DriverResult GetDeviceType(SDL_hid_device_info* device_info, | ||
| 57 | ControllerType& controller_type); | ||
| 58 | |||
| 59 | // Returns serial number from hidapi handle | ||
| 60 | static DriverResult GetSerialNumber(SDL_hid_device_info* device_info, | ||
| 61 | SerialNumber& serial_number); | ||
| 62 | |||
| 63 | private: | ||
| 64 | struct SupportedFeatures { | ||
| 65 | bool passive{}; | ||
| 66 | bool hidbus{}; | ||
| 67 | bool irs{}; | ||
| 68 | bool motion{}; | ||
| 69 | bool nfc{}; | ||
| 70 | bool vibration{}; | ||
| 71 | }; | ||
| 72 | |||
| 73 | /// Main thread, actively request new data from the handle | ||
| 74 | void InputThread(std::stop_token stop_token); | ||
| 75 | |||
| 76 | /// Called everytime a valid package arrives | ||
| 77 | void OnNewData(std::span<u8> buffer); | ||
| 78 | |||
| 79 | /// Updates device configuration to enable or disable features | ||
| 80 | DriverResult SetPollingMode(); | ||
| 81 | |||
| 82 | /// Returns true if input thread is valid and doesn't need to be stopped | ||
| 83 | bool IsInputThreadValid() const; | ||
| 84 | |||
| 85 | /// Returns true if the data should be interpreted. Otherwise the error counter is incremented | ||
| 86 | bool IsPayloadCorrect(int status, std::span<const u8> buffer); | ||
| 87 | |||
| 88 | /// Returns a list of supported features that can be enabled on this device | ||
| 89 | SupportedFeatures GetSupportedFeatures(); | ||
| 90 | |||
| 91 | // Protocol Features | ||
| 92 | std::unique_ptr<CalibrationProtocol> calibration_protocol; | ||
| 93 | std::unique_ptr<GenericProtocol> generic_protocol; | ||
| 94 | std::unique_ptr<IrsProtocol> irs_protocol; | ||
| 95 | std::unique_ptr<NfcProtocol> nfc_protocol; | ||
| 96 | std::unique_ptr<JoyconPoller> joycon_poller; | ||
| 97 | std::unique_ptr<RingConProtocol> ring_protocol; | ||
| 98 | std::unique_ptr<RumbleProtocol> rumble_protocol; | ||
| 99 | |||
| 100 | // Connection status | ||
| 101 | std::atomic<bool> is_connected{}; | ||
| 102 | u64 delta_time; | ||
| 103 | std::size_t error_counter{}; | ||
| 104 | std::shared_ptr<JoyconHandle> hidapi_handle; | ||
| 105 | std::chrono::time_point<std::chrono::steady_clock> last_update; | ||
| 106 | |||
| 107 | // External device status | ||
| 108 | bool starlink_connected{}; | ||
| 109 | bool ring_connected{}; | ||
| 110 | bool amiibo_detected{}; | ||
| 111 | bool is_ring_disabled_by_irs{}; | ||
| 112 | |||
| 113 | // Harware configuration | ||
| 114 | u8 leds{}; | ||
| 115 | ReportMode mode{}; | ||
| 116 | bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time | ||
| 117 | bool hidbus_enabled{}; // External device support | ||
| 118 | bool irs_enabled{}; // Infrared camera input | ||
| 119 | bool motion_enabled{}; // Enables motion input | ||
| 120 | bool nfc_enabled{}; // Enables Amiibo detection | ||
| 121 | bool vibration_enabled{}; // Allows vibrations | ||
| 122 | |||
| 123 | // Calibration data | ||
| 124 | GyroSensitivity gyro_sensitivity{}; | ||
| 125 | GyroPerformance gyro_performance{}; | ||
| 126 | AccelerometerSensitivity accelerometer_sensitivity{}; | ||
| 127 | AccelerometerPerformance accelerometer_performance{}; | ||
| 128 | JoyStickCalibration left_stick_calibration{}; | ||
| 129 | JoyStickCalibration right_stick_calibration{}; | ||
| 130 | MotionCalibration motion_calibration{}; | ||
| 131 | RingCalibration ring_calibration{}; | ||
| 132 | |||
| 133 | // Fixed joycon info | ||
| 134 | FirmwareVersion version{}; | ||
| 135 | Color color{}; | ||
| 136 | std::size_t port{}; | ||
| 137 | ControllerType device_type{}; // Device type reported by controller | ||
| 138 | ControllerType handle_device_type{}; // Device type reported by hidapi | ||
| 139 | SerialNumber serial_number{}; // Serial number reported by controller | ||
| 140 | SerialNumber handle_serial_number{}; // Serial number type reported by hidapi | ||
| 141 | SupportedFeatures supported_features{}; | ||
| 142 | |||
| 143 | // Thread related | ||
| 144 | mutable std::mutex mutex; | ||
| 145 | std::jthread input_thread; | ||
| 146 | bool input_thread_running{}; | ||
| 147 | bool disable_input_thread{}; | ||
| 148 | }; | ||
| 149 | |||
| 150 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp new file mode 100644 index 000000000..d8f040f75 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/calibration.cpp | |||
| @@ -0,0 +1,218 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <cstring> | ||
| 5 | |||
| 6 | #include "input_common/helpers/joycon_protocol/calibration.h" | ||
| 7 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 8 | |||
| 9 | namespace InputCommon::Joycon { | ||
| 10 | |||
| 11 | CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle) | ||
| 12 | : JoyconCommonProtocol(std::move(handle)) {} | ||
| 13 | |||
| 14 | DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) { | ||
| 15 | ScopedSetBlocking sb(this); | ||
| 16 | DriverResult result{DriverResult::Success}; | ||
| 17 | JoystickLeftSpiCalibration spi_calibration{}; | ||
| 18 | bool has_user_calibration = false; | ||
| 19 | calibration = {}; | ||
| 20 | |||
| 21 | if (result == DriverResult::Success) { | ||
| 22 | result = HasUserCalibration(SpiAddress::USER_LEFT_MAGIC, has_user_calibration); | ||
| 23 | } | ||
| 24 | |||
| 25 | // Read User defined calibration | ||
| 26 | if (result == DriverResult::Success && has_user_calibration) { | ||
| 27 | result = ReadSPI(SpiAddress::USER_LEFT_DATA, spi_calibration); | ||
| 28 | } | ||
| 29 | |||
| 30 | // Read Factory calibration | ||
| 31 | if (result == DriverResult::Success && !has_user_calibration) { | ||
| 32 | result = ReadSPI(SpiAddress::FACT_LEFT_DATA, spi_calibration); | ||
| 33 | } | ||
| 34 | |||
| 35 | if (result == DriverResult::Success) { | ||
| 36 | calibration.x.center = GetXAxisCalibrationValue(spi_calibration.center); | ||
| 37 | calibration.y.center = GetYAxisCalibrationValue(spi_calibration.center); | ||
| 38 | calibration.x.min = GetXAxisCalibrationValue(spi_calibration.min); | ||
| 39 | calibration.y.min = GetYAxisCalibrationValue(spi_calibration.min); | ||
| 40 | calibration.x.max = GetXAxisCalibrationValue(spi_calibration.max); | ||
| 41 | calibration.y.max = GetYAxisCalibrationValue(spi_calibration.max); | ||
| 42 | } | ||
| 43 | |||
| 44 | // Set a valid default calibration if data is missing | ||
| 45 | ValidateCalibration(calibration); | ||
| 46 | |||
| 47 | return result; | ||
| 48 | } | ||
| 49 | |||
| 50 | DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) { | ||
| 51 | ScopedSetBlocking sb(this); | ||
| 52 | DriverResult result{DriverResult::Success}; | ||
| 53 | JoystickRightSpiCalibration spi_calibration{}; | ||
| 54 | bool has_user_calibration = false; | ||
| 55 | calibration = {}; | ||
| 56 | |||
| 57 | if (result == DriverResult::Success) { | ||
| 58 | result = HasUserCalibration(SpiAddress::USER_RIGHT_MAGIC, has_user_calibration); | ||
| 59 | } | ||
| 60 | |||
| 61 | // Read User defined calibration | ||
| 62 | if (result == DriverResult::Success && has_user_calibration) { | ||
| 63 | result = ReadSPI(SpiAddress::USER_RIGHT_DATA, spi_calibration); | ||
| 64 | } | ||
| 65 | |||
| 66 | // Read Factory calibration | ||
| 67 | if (result == DriverResult::Success && !has_user_calibration) { | ||
| 68 | result = ReadSPI(SpiAddress::FACT_RIGHT_DATA, spi_calibration); | ||
| 69 | } | ||
| 70 | |||
| 71 | if (result == DriverResult::Success) { | ||
| 72 | calibration.x.center = GetXAxisCalibrationValue(spi_calibration.center); | ||
| 73 | calibration.y.center = GetYAxisCalibrationValue(spi_calibration.center); | ||
| 74 | calibration.x.min = GetXAxisCalibrationValue(spi_calibration.min); | ||
| 75 | calibration.y.min = GetYAxisCalibrationValue(spi_calibration.min); | ||
| 76 | calibration.x.max = GetXAxisCalibrationValue(spi_calibration.max); | ||
| 77 | calibration.y.max = GetYAxisCalibrationValue(spi_calibration.max); | ||
| 78 | } | ||
| 79 | |||
| 80 | // Set a valid default calibration if data is missing | ||
| 81 | ValidateCalibration(calibration); | ||
| 82 | |||
| 83 | return result; | ||
| 84 | } | ||
| 85 | |||
| 86 | DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) { | ||
| 87 | ScopedSetBlocking sb(this); | ||
| 88 | DriverResult result{DriverResult::Success}; | ||
| 89 | ImuSpiCalibration spi_calibration{}; | ||
| 90 | bool has_user_calibration = false; | ||
| 91 | calibration = {}; | ||
| 92 | |||
| 93 | if (result == DriverResult::Success) { | ||
| 94 | result = HasUserCalibration(SpiAddress::USER_IMU_MAGIC, has_user_calibration); | ||
| 95 | } | ||
| 96 | |||
| 97 | // Read User defined calibration | ||
| 98 | if (result == DriverResult::Success && has_user_calibration) { | ||
| 99 | result = ReadSPI(SpiAddress::USER_IMU_DATA, spi_calibration); | ||
| 100 | } | ||
| 101 | |||
| 102 | // Read Factory calibration | ||
| 103 | if (result == DriverResult::Success && !has_user_calibration) { | ||
| 104 | result = ReadSPI(SpiAddress::FACT_IMU_DATA, spi_calibration); | ||
| 105 | } | ||
| 106 | |||
| 107 | if (result == DriverResult::Success) { | ||
| 108 | calibration.accelerometer[0].offset = spi_calibration.accelerometer_offset[0]; | ||
| 109 | calibration.accelerometer[1].offset = spi_calibration.accelerometer_offset[1]; | ||
| 110 | calibration.accelerometer[2].offset = spi_calibration.accelerometer_offset[2]; | ||
| 111 | |||
| 112 | calibration.accelerometer[0].scale = spi_calibration.accelerometer_scale[0]; | ||
| 113 | calibration.accelerometer[1].scale = spi_calibration.accelerometer_scale[1]; | ||
| 114 | calibration.accelerometer[2].scale = spi_calibration.accelerometer_scale[2]; | ||
| 115 | |||
| 116 | calibration.gyro[0].offset = spi_calibration.gyroscope_offset[0]; | ||
| 117 | calibration.gyro[1].offset = spi_calibration.gyroscope_offset[1]; | ||
| 118 | calibration.gyro[2].offset = spi_calibration.gyroscope_offset[2]; | ||
| 119 | |||
| 120 | calibration.gyro[0].scale = spi_calibration.gyroscope_scale[0]; | ||
| 121 | calibration.gyro[1].scale = spi_calibration.gyroscope_scale[1]; | ||
| 122 | calibration.gyro[2].scale = spi_calibration.gyroscope_scale[2]; | ||
| 123 | } | ||
| 124 | |||
| 125 | ValidateCalibration(calibration); | ||
| 126 | |||
| 127 | return result; | ||
| 128 | } | ||
| 129 | |||
| 130 | DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration, | ||
| 131 | s16 current_value) { | ||
| 132 | constexpr s16 DefaultRingRange{800}; | ||
| 133 | |||
| 134 | // TODO: Get default calibration form ring itself | ||
| 135 | if (ring_data_max == 0 && ring_data_min == 0) { | ||
| 136 | ring_data_max = current_value + DefaultRingRange; | ||
| 137 | ring_data_min = current_value - DefaultRingRange; | ||
| 138 | ring_data_default = current_value; | ||
| 139 | } | ||
| 140 | ring_data_max = std::max(ring_data_max, current_value); | ||
| 141 | ring_data_min = std::min(ring_data_min, current_value); | ||
| 142 | calibration = { | ||
| 143 | .default_value = ring_data_default, | ||
| 144 | .max_value = ring_data_max, | ||
| 145 | .min_value = ring_data_min, | ||
| 146 | }; | ||
| 147 | return DriverResult::Success; | ||
| 148 | } | ||
| 149 | |||
| 150 | DriverResult CalibrationProtocol::HasUserCalibration(SpiAddress address, | ||
| 151 | bool& has_user_calibration) { | ||
| 152 | MagicSpiCalibration spi_magic{}; | ||
| 153 | const DriverResult result{ReadSPI(address, spi_magic)}; | ||
| 154 | has_user_calibration = false; | ||
| 155 | if (result == DriverResult::Success) { | ||
| 156 | has_user_calibration = spi_magic.first == CalibrationMagic::USR_MAGIC_0 && | ||
| 157 | spi_magic.second == CalibrationMagic::USR_MAGIC_1; | ||
| 158 | } | ||
| 159 | return result; | ||
| 160 | } | ||
| 161 | |||
| 162 | u16 CalibrationProtocol::GetXAxisCalibrationValue(std::span<u8> block) const { | ||
| 163 | return static_cast<u16>(((block[1] & 0x0F) << 8) | block[0]); | ||
| 164 | } | ||
| 165 | |||
| 166 | u16 CalibrationProtocol::GetYAxisCalibrationValue(std::span<u8> block) const { | ||
| 167 | return static_cast<u16>((block[2] << 4) | (block[1] >> 4)); | ||
| 168 | } | ||
| 169 | |||
| 170 | void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) { | ||
| 171 | constexpr u16 DefaultStickCenter{0x800}; | ||
| 172 | constexpr u16 DefaultStickRange{0x6cc}; | ||
| 173 | |||
| 174 | calibration.x.center = ValidateValue(calibration.x.center, DefaultStickCenter); | ||
| 175 | calibration.x.max = ValidateValue(calibration.x.max, DefaultStickRange); | ||
| 176 | calibration.x.min = ValidateValue(calibration.x.min, DefaultStickRange); | ||
| 177 | |||
| 178 | calibration.y.center = ValidateValue(calibration.y.center, DefaultStickCenter); | ||
| 179 | calibration.y.max = ValidateValue(calibration.y.max, DefaultStickRange); | ||
| 180 | calibration.y.min = ValidateValue(calibration.y.min, DefaultStickRange); | ||
| 181 | } | ||
| 182 | |||
| 183 | void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) { | ||
| 184 | constexpr s16 DefaultAccelerometerScale{0x4000}; | ||
| 185 | constexpr s16 DefaultGyroScale{0x3be7}; | ||
| 186 | constexpr s16 DefaultOffset{0}; | ||
| 187 | |||
| 188 | for (auto& sensor : calibration.accelerometer) { | ||
| 189 | sensor.scale = ValidateValue(sensor.scale, DefaultAccelerometerScale); | ||
| 190 | sensor.offset = ValidateValue(sensor.offset, DefaultOffset); | ||
| 191 | } | ||
| 192 | for (auto& sensor : calibration.gyro) { | ||
| 193 | sensor.scale = ValidateValue(sensor.scale, DefaultGyroScale); | ||
| 194 | sensor.offset = ValidateValue(sensor.offset, DefaultOffset); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | u16 CalibrationProtocol::ValidateValue(u16 value, u16 default_value) const { | ||
| 199 | if (value == 0) { | ||
| 200 | return default_value; | ||
| 201 | } | ||
| 202 | if (value == 0xFFF) { | ||
| 203 | return default_value; | ||
| 204 | } | ||
| 205 | return value; | ||
| 206 | } | ||
| 207 | |||
| 208 | s16 CalibrationProtocol::ValidateValue(s16 value, s16 default_value) const { | ||
| 209 | if (value == 0) { | ||
| 210 | return default_value; | ||
| 211 | } | ||
| 212 | if (value == 0xFFF) { | ||
| 213 | return default_value; | ||
| 214 | } | ||
| 215 | return value; | ||
| 216 | } | ||
| 217 | |||
| 218 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h new file mode 100644 index 000000000..c6fd0f729 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/calibration.h | |||
| @@ -0,0 +1,82 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <vector> | ||
| 12 | |||
| 13 | #include "input_common/helpers/joycon_protocol/common_protocol.h" | ||
| 14 | |||
| 15 | namespace InputCommon::Joycon { | ||
| 16 | enum class DriverResult; | ||
| 17 | struct JoyStickCalibration; | ||
| 18 | struct IMUCalibration; | ||
| 19 | struct JoyconHandle; | ||
| 20 | } // namespace InputCommon::Joycon | ||
| 21 | |||
| 22 | namespace InputCommon::Joycon { | ||
| 23 | |||
| 24 | /// Driver functions related to retrieving calibration data from the device | ||
| 25 | class CalibrationProtocol final : private JoyconCommonProtocol { | ||
| 26 | public: | ||
| 27 | explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle); | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Sends a request to obtain the left stick calibration from memory | ||
| 31 | * @param is_factory_calibration if true factory values will be returned | ||
| 32 | * @returns JoyStickCalibration of the left joystick | ||
| 33 | */ | ||
| 34 | DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration); | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Sends a request to obtain the right stick calibration from memory | ||
| 38 | * @param is_factory_calibration if true factory values will be returned | ||
| 39 | * @returns JoyStickCalibration of the right joystick | ||
| 40 | */ | ||
| 41 | DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Sends a request to obtain the motion calibration from memory | ||
| 45 | * @returns ImuCalibration of the motion sensor | ||
| 46 | */ | ||
| 47 | DriverResult GetImuCalibration(MotionCalibration& calibration); | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Calculates on run time the proper calibration of the ring controller | ||
| 51 | * @returns RingCalibration of the ring sensor | ||
| 52 | */ | ||
| 53 | DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value); | ||
| 54 | |||
| 55 | private: | ||
| 56 | /// Returns true if the specified address corresponds to the magic value of user calibration | ||
| 57 | DriverResult HasUserCalibration(SpiAddress address, bool& has_user_calibration); | ||
| 58 | |||
| 59 | /// Converts a raw calibration block to an u16 value containing the x axis value | ||
| 60 | u16 GetXAxisCalibrationValue(std::span<u8> block) const; | ||
| 61 | |||
| 62 | /// Converts a raw calibration block to an u16 value containing the y axis value | ||
| 63 | u16 GetYAxisCalibrationValue(std::span<u8> block) const; | ||
| 64 | |||
| 65 | /// Ensures that all joystick calibration values are set | ||
| 66 | void ValidateCalibration(JoyStickCalibration& calibration); | ||
| 67 | |||
| 68 | /// Ensures that all motion calibration values are set | ||
| 69 | void ValidateCalibration(MotionCalibration& calibration); | ||
| 70 | |||
| 71 | /// Returns the default value if the value is either zero or 0xFFF | ||
| 72 | u16 ValidateValue(u16 value, u16 default_value) const; | ||
| 73 | |||
| 74 | /// Returns the default value if the value is either zero or 0xFFF | ||
| 75 | s16 ValidateValue(s16 value, s16 default_value) const; | ||
| 76 | |||
| 77 | s16 ring_data_max = 0; | ||
| 78 | s16 ring_data_default = 0; | ||
| 79 | s16 ring_data_min = 0; | ||
| 80 | }; | ||
| 81 | |||
| 82 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp new file mode 100644 index 000000000..2b42a4555 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp | |||
| @@ -0,0 +1,316 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/logging/log.h" | ||
| 5 | #include "input_common/helpers/joycon_protocol/common_protocol.h" | ||
| 6 | |||
| 7 | namespace InputCommon::Joycon { | ||
| 8 | JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_) | ||
| 9 | : hidapi_handle{std::move(hidapi_handle_)} {} | ||
| 10 | |||
| 11 | u8 JoyconCommonProtocol::GetCounter() { | ||
| 12 | hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F; | ||
| 13 | return hidapi_handle->packet_counter; | ||
| 14 | } | ||
| 15 | |||
| 16 | void JoyconCommonProtocol::SetBlocking() { | ||
| 17 | SDL_hid_set_nonblocking(hidapi_handle->handle, 0); | ||
| 18 | } | ||
| 19 | |||
| 20 | void JoyconCommonProtocol::SetNonBlocking() { | ||
| 21 | SDL_hid_set_nonblocking(hidapi_handle->handle, 1); | ||
| 22 | } | ||
| 23 | |||
| 24 | DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) { | ||
| 25 | const auto result = ReadSPI(SpiAddress::DEVICE_TYPE, controller_type); | ||
| 26 | |||
| 27 | if (result == DriverResult::Success) { | ||
| 28 | // Fallback to 3rd party pro controllers | ||
| 29 | if (controller_type == ControllerType::None) { | ||
| 30 | controller_type = ControllerType::Pro; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | return result; | ||
| 35 | } | ||
| 36 | |||
| 37 | DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) { | ||
| 38 | ControllerType controller_type{ControllerType::None}; | ||
| 39 | const auto result = GetDeviceType(controller_type); | ||
| 40 | |||
| 41 | if (result != DriverResult::Success || controller_type == ControllerType::None) { | ||
| 42 | return DriverResult::UnsupportedControllerType; | ||
| 43 | } | ||
| 44 | |||
| 45 | hidapi_handle->handle = | ||
| 46 | SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); | ||
| 47 | |||
| 48 | if (!hidapi_handle->handle) { | ||
| 49 | LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", | ||
| 50 | device_info->vendor_id, device_info->product_id); | ||
| 51 | return DriverResult::HandleInUse; | ||
| 52 | } | ||
| 53 | |||
| 54 | SetNonBlocking(); | ||
| 55 | return DriverResult::Success; | ||
| 56 | } | ||
| 57 | |||
| 58 | DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) { | ||
| 59 | const std::array<u8, 1> buffer{static_cast<u8>(report_mode)}; | ||
| 60 | return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer); | ||
| 61 | } | ||
| 62 | |||
| 63 | DriverResult JoyconCommonProtocol::SendRawData(std::span<const u8> buffer) { | ||
| 64 | const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size()); | ||
| 65 | |||
| 66 | if (result == -1) { | ||
| 67 | return DriverResult::ErrorWritingData; | ||
| 68 | } | ||
| 69 | |||
| 70 | return DriverResult::Success; | ||
| 71 | } | ||
| 72 | |||
| 73 | DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, | ||
| 74 | SubCommandResponse& output) { | ||
| 75 | constexpr int timeout_mili = 66; | ||
| 76 | constexpr int MaxTries = 15; | ||
| 77 | int tries = 0; | ||
| 78 | |||
| 79 | do { | ||
| 80 | int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output), | ||
| 81 | sizeof(SubCommandResponse), timeout_mili); | ||
| 82 | |||
| 83 | if (result < 1) { | ||
| 84 | LOG_ERROR(Input, "No response from joycon"); | ||
| 85 | } | ||
| 86 | if (tries++ > MaxTries) { | ||
| 87 | return DriverResult::Timeout; | ||
| 88 | } | ||
| 89 | } while (output.input_report.report_mode != ReportMode::SUBCMD_REPLY && | ||
| 90 | output.sub_command != sc); | ||
| 91 | |||
| 92 | return DriverResult::Success; | ||
| 93 | } | ||
| 94 | |||
| 95 | DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer, | ||
| 96 | SubCommandResponse& output) { | ||
| 97 | SubCommandPacket packet{ | ||
| 98 | .output_report = OutputReport::RUMBLE_AND_SUBCMD, | ||
| 99 | .packet_counter = GetCounter(), | ||
| 100 | .sub_command = sc, | ||
| 101 | .command_data = {}, | ||
| 102 | }; | ||
| 103 | |||
| 104 | if (buffer.size() > packet.command_data.size()) { | ||
| 105 | return DriverResult::InvalidParameters; | ||
| 106 | } | ||
| 107 | |||
| 108 | memcpy(packet.command_data.data(), buffer.data(), buffer.size()); | ||
| 109 | |||
| 110 | auto result = SendData(packet); | ||
| 111 | |||
| 112 | if (result != DriverResult::Success) { | ||
| 113 | return result; | ||
| 114 | } | ||
| 115 | |||
| 116 | result = GetSubCommandResponse(sc, output); | ||
| 117 | |||
| 118 | return DriverResult::Success; | ||
| 119 | } | ||
| 120 | |||
| 121 | DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) { | ||
| 122 | SubCommandResponse output{}; | ||
| 123 | return SendSubCommand(sc, buffer, output); | ||
| 124 | } | ||
| 125 | |||
| 126 | DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) { | ||
| 127 | SubCommandPacket packet{ | ||
| 128 | .output_report = OutputReport::MCU_DATA, | ||
| 129 | .packet_counter = GetCounter(), | ||
| 130 | .sub_command = sc, | ||
| 131 | .command_data = {}, | ||
| 132 | }; | ||
| 133 | |||
| 134 | if (buffer.size() > packet.command_data.size()) { | ||
| 135 | return DriverResult::InvalidParameters; | ||
| 136 | } | ||
| 137 | |||
| 138 | memcpy(packet.command_data.data(), buffer.data(), buffer.size()); | ||
| 139 | |||
| 140 | return SendData(packet); | ||
| 141 | } | ||
| 142 | |||
| 143 | DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) { | ||
| 144 | VibrationPacket packet{ | ||
| 145 | .output_report = OutputReport::RUMBLE_ONLY, | ||
| 146 | .packet_counter = GetCounter(), | ||
| 147 | .vibration_data = {}, | ||
| 148 | }; | ||
| 149 | |||
| 150 | if (buffer.size() > packet.vibration_data.size()) { | ||
| 151 | return DriverResult::InvalidParameters; | ||
| 152 | } | ||
| 153 | |||
| 154 | memcpy(packet.vibration_data.data(), buffer.data(), buffer.size()); | ||
| 155 | |||
| 156 | return SendData(packet); | ||
| 157 | } | ||
| 158 | |||
| 159 | DriverResult JoyconCommonProtocol::ReadRawSPI(SpiAddress addr, std::span<u8> output) { | ||
| 160 | constexpr std::size_t HeaderSize = 5; | ||
| 161 | constexpr std::size_t MaxTries = 10; | ||
| 162 | std::size_t tries = 0; | ||
| 163 | SubCommandResponse response{}; | ||
| 164 | std::array<u8, sizeof(ReadSpiPacket)> buffer{}; | ||
| 165 | const ReadSpiPacket packet_data{ | ||
| 166 | .spi_address = addr, | ||
| 167 | .size = static_cast<u8>(output.size()), | ||
| 168 | }; | ||
| 169 | |||
| 170 | memcpy(buffer.data(), &packet_data, sizeof(ReadSpiPacket)); | ||
| 171 | do { | ||
| 172 | const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, response); | ||
| 173 | if (result != DriverResult::Success) { | ||
| 174 | return result; | ||
| 175 | } | ||
| 176 | |||
| 177 | if (tries++ > MaxTries) { | ||
| 178 | return DriverResult::Timeout; | ||
| 179 | } | ||
| 180 | } while (response.spi_address != addr); | ||
| 181 | |||
| 182 | if (response.command_data.size() < packet_data.size + HeaderSize) { | ||
| 183 | return DriverResult::WrongReply; | ||
| 184 | } | ||
| 185 | |||
| 186 | // Remove header from output | ||
| 187 | memcpy(output.data(), response.command_data.data() + HeaderSize, packet_data.size); | ||
| 188 | return DriverResult::Success; | ||
| 189 | } | ||
| 190 | |||
| 191 | DriverResult JoyconCommonProtocol::EnableMCU(bool enable) { | ||
| 192 | const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)}; | ||
| 193 | const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state); | ||
| 194 | |||
| 195 | if (result != DriverResult::Success) { | ||
| 196 | LOG_ERROR(Input, "Failed with error {}", result); | ||
| 197 | } | ||
| 198 | |||
| 199 | return result; | ||
| 200 | } | ||
| 201 | |||
| 202 | DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) { | ||
| 203 | LOG_DEBUG(Input, "ConfigureMCU"); | ||
| 204 | std::array<u8, sizeof(MCUConfig)> config_buffer; | ||
| 205 | memcpy(config_buffer.data(), &config, sizeof(MCUConfig)); | ||
| 206 | config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36); | ||
| 207 | |||
| 208 | const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer); | ||
| 209 | |||
| 210 | if (result != DriverResult::Success) { | ||
| 211 | LOG_ERROR(Input, "Failed with error {}", result); | ||
| 212 | } | ||
| 213 | |||
| 214 | return result; | ||
| 215 | } | ||
| 216 | |||
| 217 | DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode, | ||
| 218 | MCUCommandResponse& output) { | ||
| 219 | constexpr int TimeoutMili = 200; | ||
| 220 | constexpr int MaxTries = 9; | ||
| 221 | int tries = 0; | ||
| 222 | |||
| 223 | do { | ||
| 224 | int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output), | ||
| 225 | sizeof(MCUCommandResponse), TimeoutMili); | ||
| 226 | |||
| 227 | if (result < 1) { | ||
| 228 | LOG_ERROR(Input, "No response from joycon attempt {}", tries); | ||
| 229 | } | ||
| 230 | if (tries++ > MaxTries) { | ||
| 231 | return DriverResult::Timeout; | ||
| 232 | } | ||
| 233 | } while (output.input_report.report_mode != report_mode || | ||
| 234 | output.mcu_report == MCUReport::EmptyAwaitingCmd); | ||
| 235 | |||
| 236 | return DriverResult::Success; | ||
| 237 | } | ||
| 238 | |||
| 239 | DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc, | ||
| 240 | std::span<const u8> buffer, | ||
| 241 | MCUCommandResponse& output) { | ||
| 242 | SubCommandPacket packet{ | ||
| 243 | .output_report = OutputReport::MCU_DATA, | ||
| 244 | .packet_counter = GetCounter(), | ||
| 245 | .sub_command = sc, | ||
| 246 | .command_data = {}, | ||
| 247 | }; | ||
| 248 | |||
| 249 | if (buffer.size() > packet.command_data.size()) { | ||
| 250 | return DriverResult::InvalidParameters; | ||
| 251 | } | ||
| 252 | |||
| 253 | memcpy(packet.command_data.data(), buffer.data(), buffer.size()); | ||
| 254 | |||
| 255 | auto result = SendData(packet); | ||
| 256 | |||
| 257 | if (result != DriverResult::Success) { | ||
| 258 | return result; | ||
| 259 | } | ||
| 260 | |||
| 261 | result = GetMCUDataResponse(report_mode, output); | ||
| 262 | |||
| 263 | return DriverResult::Success; | ||
| 264 | } | ||
| 265 | |||
| 266 | DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) { | ||
| 267 | MCUCommandResponse output{}; | ||
| 268 | constexpr std::size_t MaxTries{8}; | ||
| 269 | std::size_t tries{}; | ||
| 270 | |||
| 271 | do { | ||
| 272 | const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)}; | ||
| 273 | const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output); | ||
| 274 | |||
| 275 | if (result != DriverResult::Success) { | ||
| 276 | return result; | ||
| 277 | } | ||
| 278 | |||
| 279 | if (tries++ > MaxTries) { | ||
| 280 | return DriverResult::WrongReply; | ||
| 281 | } | ||
| 282 | } while (output.mcu_report != MCUReport::StateReport || | ||
| 283 | output.mcu_data[6] != static_cast<u8>(mode)); | ||
| 284 | |||
| 285 | return DriverResult::Success; | ||
| 286 | } | ||
| 287 | |||
| 288 | // crc-8-ccitt / polynomial 0x07 look up table | ||
| 289 | constexpr std::array<u8, 256> mcu_crc8_table = { | ||
| 290 | 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, | ||
| 291 | 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, | ||
| 292 | 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, | ||
| 293 | 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, | ||
| 294 | 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, | ||
| 295 | 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, | ||
| 296 | 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, | ||
| 297 | 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, | ||
| 298 | 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, | ||
| 299 | 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, | ||
| 300 | 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, | ||
| 301 | 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, | ||
| 302 | 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, | ||
| 303 | 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, | ||
| 304 | 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, | ||
| 305 | 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3}; | ||
| 306 | |||
| 307 | u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const { | ||
| 308 | u8 crc8 = 0x0; | ||
| 309 | |||
| 310 | for (int i = 0; i < size; ++i) { | ||
| 311 | crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])]; | ||
| 312 | } | ||
| 313 | return crc8; | ||
| 314 | } | ||
| 315 | |||
| 316 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h new file mode 100644 index 000000000..f44f73ba4 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/common_protocol.h | |||
| @@ -0,0 +1,201 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <memory> | ||
| 12 | #include <span> | ||
| 13 | #include <vector> | ||
| 14 | |||
| 15 | #include "common/common_types.h" | ||
| 16 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 17 | |||
| 18 | namespace InputCommon::Joycon { | ||
| 19 | |||
| 20 | /// Joycon driver functions that handle low level communication | ||
| 21 | class JoyconCommonProtocol { | ||
| 22 | public: | ||
| 23 | explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_); | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is | ||
| 27 | * data to read before returning. | ||
| 28 | */ | ||
| 29 | void SetBlocking(); | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return | ||
| 33 | * immediately with a value of 0 if there is no data to be read | ||
| 34 | */ | ||
| 35 | void SetNonBlocking(); | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Sends a request to obtain the joycon type from device | ||
| 39 | * @returns controller type of the joycon | ||
| 40 | */ | ||
| 41 | DriverResult GetDeviceType(ControllerType& controller_type); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Verifies and sets the joycon_handle if device is valid | ||
| 45 | * @param device info from the driver | ||
| 46 | * @returns success if the device is valid | ||
| 47 | */ | ||
| 48 | DriverResult CheckDeviceAccess(SDL_hid_device_info* device); | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Sends a request to set the polling mode of the joycon | ||
| 52 | * @param report_mode polling mode to be set | ||
| 53 | */ | ||
| 54 | DriverResult SetReportMode(Joycon::ReportMode report_mode); | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Sends data to the joycon device | ||
| 58 | * @param buffer data to be send | ||
| 59 | */ | ||
| 60 | DriverResult SendRawData(std::span<const u8> buffer); | ||
| 61 | |||
| 62 | template <typename Output> | ||
| 63 | requires std::is_trivially_copyable_v<Output> | ||
| 64 | DriverResult SendData(const Output& output) { | ||
| 65 | std::array<u8, sizeof(Output)> buffer; | ||
| 66 | std::memcpy(buffer.data(), &output, sizeof(Output)); | ||
| 67 | return SendRawData(buffer); | ||
| 68 | } | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Waits for incoming data of the joycon device that matchs the subcommand | ||
| 72 | * @param sub_command type of data to be returned | ||
| 73 | * @returns a buffer containing the response | ||
| 74 | */ | ||
| 75 | DriverResult GetSubCommandResponse(SubCommand sub_command, SubCommandResponse& output); | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Sends a sub command to the device and waits for it's reply | ||
| 79 | * @param sc sub command to be send | ||
| 80 | * @param buffer data to be send | ||
| 81 | * @returns output buffer containing the response | ||
| 82 | */ | ||
| 83 | DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, | ||
| 84 | SubCommandResponse& output); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Sends a sub command to the device and waits for it's reply and ignores the output | ||
| 88 | * @param sc sub command to be send | ||
| 89 | * @param buffer data to be send | ||
| 90 | */ | ||
| 91 | DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Sends a mcu command to the device | ||
| 95 | * @param sc sub command to be send | ||
| 96 | * @param buffer data to be send | ||
| 97 | */ | ||
| 98 | DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Sends vibration data to the joycon | ||
| 102 | * @param buffer data to be send | ||
| 103 | */ | ||
| 104 | DriverResult SendVibrationReport(std::span<const u8> buffer); | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Reads the SPI memory stored on the joycon | ||
| 108 | * @param Initial address location | ||
| 109 | * @returns output buffer containing the response | ||
| 110 | */ | ||
| 111 | DriverResult ReadRawSPI(SpiAddress addr, std::span<u8> output); | ||
| 112 | |||
| 113 | /** | ||
| 114 | * Reads the SPI memory stored on the joycon | ||
| 115 | * @param Initial address location | ||
| 116 | * @returns output object containing the response | ||
| 117 | */ | ||
| 118 | template <typename Output> | ||
| 119 | requires std::is_trivially_copyable_v<Output> | ||
| 120 | DriverResult ReadSPI(SpiAddress addr, Output& output) { | ||
| 121 | std::array<u8, sizeof(Output)> buffer; | ||
| 122 | output = {}; | ||
| 123 | |||
| 124 | const auto result = ReadRawSPI(addr, buffer); | ||
| 125 | if (result != DriverResult::Success) { | ||
| 126 | return result; | ||
| 127 | } | ||
| 128 | |||
| 129 | std::memcpy(&output, buffer.data(), sizeof(Output)); | ||
| 130 | return DriverResult::Success; | ||
| 131 | } | ||
| 132 | |||
| 133 | /** | ||
| 134 | * Enables MCU chip on the joycon | ||
| 135 | * @param enable if true the chip will be enabled | ||
| 136 | */ | ||
| 137 | DriverResult EnableMCU(bool enable); | ||
| 138 | |||
| 139 | /** | ||
| 140 | * Configures the MCU to the correspoinding mode | ||
| 141 | * @param MCUConfig configuration | ||
| 142 | */ | ||
| 143 | DriverResult ConfigureMCU(const MCUConfig& config); | ||
| 144 | |||
| 145 | /** | ||
| 146 | * Waits until there's MCU data available. On timeout returns error | ||
| 147 | * @param report mode of the expected reply | ||
| 148 | * @returns a buffer containing the response | ||
| 149 | */ | ||
| 150 | DriverResult GetMCUDataResponse(ReportMode report_mode_, MCUCommandResponse& output); | ||
| 151 | |||
| 152 | /** | ||
| 153 | * Sends data to the MCU chip and waits for it's reply | ||
| 154 | * @param report mode of the expected reply | ||
| 155 | * @param sub command to be send | ||
| 156 | * @param buffer data to be send | ||
| 157 | * @returns output buffer containing the response | ||
| 158 | */ | ||
| 159 | DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer, | ||
| 160 | MCUCommandResponse& output); | ||
| 161 | |||
| 162 | /** | ||
| 163 | * Wait's until the MCU chip is on the specified mode | ||
| 164 | * @param report mode of the expected reply | ||
| 165 | * @param MCUMode configuration | ||
| 166 | */ | ||
| 167 | DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode); | ||
| 168 | |||
| 169 | /** | ||
| 170 | * Calculates the checksum from the MCU data | ||
| 171 | * @param buffer containing the data to be send | ||
| 172 | * @param size of the buffer in bytes | ||
| 173 | * @returns byte with the correct checksum | ||
| 174 | */ | ||
| 175 | u8 CalculateMCU_CRC8(u8* buffer, u8 size) const; | ||
| 176 | |||
| 177 | private: | ||
| 178 | /** | ||
| 179 | * Increments and returns the packet counter of the handle | ||
| 180 | * @param joycon_handle device to send the data | ||
| 181 | * @returns packet counter value | ||
| 182 | */ | ||
| 183 | u8 GetCounter(); | ||
| 184 | |||
| 185 | std::shared_ptr<JoyconHandle> hidapi_handle; | ||
| 186 | }; | ||
| 187 | |||
| 188 | class ScopedSetBlocking { | ||
| 189 | public: | ||
| 190 | explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} { | ||
| 191 | m_self->SetBlocking(); | ||
| 192 | } | ||
| 193 | |||
| 194 | ~ScopedSetBlocking() { | ||
| 195 | m_self->SetNonBlocking(); | ||
| 196 | } | ||
| 197 | |||
| 198 | private: | ||
| 199 | JoyconCommonProtocol* m_self{}; | ||
| 200 | }; | ||
| 201 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp new file mode 100644 index 000000000..548a4b9e3 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp | |||
| @@ -0,0 +1,136 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/logging/log.h" | ||
| 5 | #include "input_common/helpers/joycon_protocol/generic_functions.h" | ||
| 6 | |||
| 7 | namespace InputCommon::Joycon { | ||
| 8 | |||
| 9 | GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle) | ||
| 10 | : JoyconCommonProtocol(std::move(handle)) {} | ||
| 11 | |||
| 12 | DriverResult GenericProtocol::EnablePassiveMode() { | ||
| 13 | ScopedSetBlocking sb(this); | ||
| 14 | return SetReportMode(ReportMode::SIMPLE_HID_MODE); | ||
| 15 | } | ||
| 16 | |||
| 17 | DriverResult GenericProtocol::EnableActiveMode() { | ||
| 18 | ScopedSetBlocking sb(this); | ||
| 19 | return SetReportMode(ReportMode::STANDARD_FULL_60HZ); | ||
| 20 | } | ||
| 21 | |||
| 22 | DriverResult GenericProtocol::SetLowPowerMode(bool enable) { | ||
| 23 | ScopedSetBlocking sb(this); | ||
| 24 | const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)}; | ||
| 25 | return SendSubCommand(SubCommand::LOW_POWER_MODE, buffer); | ||
| 26 | } | ||
| 27 | |||
| 28 | DriverResult GenericProtocol::TriggersElapsed() { | ||
| 29 | ScopedSetBlocking sb(this); | ||
| 30 | return SendSubCommand(SubCommand::TRIGGERS_ELAPSED, {}); | ||
| 31 | } | ||
| 32 | |||
| 33 | DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) { | ||
| 34 | ScopedSetBlocking sb(this); | ||
| 35 | SubCommandResponse output{}; | ||
| 36 | |||
| 37 | const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output); | ||
| 38 | |||
| 39 | device_info = {}; | ||
| 40 | if (result == DriverResult::Success) { | ||
| 41 | device_info = output.device_info; | ||
| 42 | } | ||
| 43 | |||
| 44 | return result; | ||
| 45 | } | ||
| 46 | |||
| 47 | DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) { | ||
| 48 | return GetDeviceType(controller_type); | ||
| 49 | } | ||
| 50 | |||
| 51 | DriverResult GenericProtocol::EnableImu(bool enable) { | ||
| 52 | ScopedSetBlocking sb(this); | ||
| 53 | const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)}; | ||
| 54 | return SendSubCommand(SubCommand::ENABLE_IMU, buffer); | ||
| 55 | } | ||
| 56 | |||
| 57 | DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec, | ||
| 58 | AccelerometerSensitivity asen, | ||
| 59 | AccelerometerPerformance afrec) { | ||
| 60 | ScopedSetBlocking sb(this); | ||
| 61 | const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen), | ||
| 62 | static_cast<u8>(gfrec), static_cast<u8>(afrec)}; | ||
| 63 | return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer); | ||
| 64 | } | ||
| 65 | |||
| 66 | DriverResult GenericProtocol::GetBattery(u32& battery_level) { | ||
| 67 | // This function is meant to request the high resolution battery status | ||
| 68 | battery_level = 0; | ||
| 69 | return DriverResult::NotSupported; | ||
| 70 | } | ||
| 71 | |||
| 72 | DriverResult GenericProtocol::GetColor(Color& color) { | ||
| 73 | ScopedSetBlocking sb(this); | ||
| 74 | std::array<u8, 12> buffer{}; | ||
| 75 | const auto result = ReadRawSPI(SpiAddress::COLOR_DATA, buffer); | ||
| 76 | |||
| 77 | color = {}; | ||
| 78 | if (result == DriverResult::Success) { | ||
| 79 | color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]); | ||
| 80 | color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]); | ||
| 81 | color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]); | ||
| 82 | color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]); | ||
| 83 | } | ||
| 84 | |||
| 85 | return result; | ||
| 86 | } | ||
| 87 | |||
| 88 | DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) { | ||
| 89 | ScopedSetBlocking sb(this); | ||
| 90 | std::array<u8, 16> buffer{}; | ||
| 91 | const auto result = ReadRawSPI(SpiAddress::SERIAL_NUMBER, buffer); | ||
| 92 | |||
| 93 | serial_number = {}; | ||
| 94 | if (result == DriverResult::Success) { | ||
| 95 | memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber)); | ||
| 96 | } | ||
| 97 | |||
| 98 | return result; | ||
| 99 | } | ||
| 100 | |||
| 101 | DriverResult GenericProtocol::GetTemperature(u32& temperature) { | ||
| 102 | // Not all devices have temperature sensor | ||
| 103 | temperature = 25; | ||
| 104 | return DriverResult::NotSupported; | ||
| 105 | } | ||
| 106 | |||
| 107 | DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) { | ||
| 108 | DeviceInfo device_info{}; | ||
| 109 | |||
| 110 | const auto result = GetDeviceInfo(device_info); | ||
| 111 | version = device_info.firmware; | ||
| 112 | |||
| 113 | return result; | ||
| 114 | } | ||
| 115 | |||
| 116 | DriverResult GenericProtocol::SetHomeLight() { | ||
| 117 | ScopedSetBlocking sb(this); | ||
| 118 | static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00}; | ||
| 119 | return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer); | ||
| 120 | } | ||
| 121 | |||
| 122 | DriverResult GenericProtocol::SetLedBusy() { | ||
| 123 | return DriverResult::NotSupported; | ||
| 124 | } | ||
| 125 | |||
| 126 | DriverResult GenericProtocol::SetLedPattern(u8 leds) { | ||
| 127 | ScopedSetBlocking sb(this); | ||
| 128 | const std::array<u8, 1> buffer{leds}; | ||
| 129 | return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer); | ||
| 130 | } | ||
| 131 | |||
| 132 | DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) { | ||
| 133 | return SetLedPattern(static_cast<u8>(leds << 4)); | ||
| 134 | } | ||
| 135 | |||
| 136 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h new file mode 100644 index 000000000..424831e81 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/generic_functions.h | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include "input_common/helpers/joycon_protocol/common_protocol.h" | ||
| 12 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 13 | |||
| 14 | namespace InputCommon::Joycon { | ||
| 15 | |||
| 16 | /// Joycon driver functions that easily implemented | ||
| 17 | class GenericProtocol final : private JoyconCommonProtocol { | ||
| 18 | public: | ||
| 19 | explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle); | ||
| 20 | |||
| 21 | /// Enables passive mode. This mode only sends button data on change. Sticks will return digital | ||
| 22 | /// data instead of analog. Motion will be disabled | ||
| 23 | DriverResult EnablePassiveMode(); | ||
| 24 | |||
| 25 | /// Enables active mode. This mode will return the current status every 5-15ms | ||
| 26 | DriverResult EnableActiveMode(); | ||
| 27 | |||
| 28 | /// Enables or disables the low power mode | ||
| 29 | DriverResult SetLowPowerMode(bool enable); | ||
| 30 | |||
| 31 | /// Unknown function used by the switch | ||
| 32 | DriverResult TriggersElapsed(); | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Sends a request to obtain the joycon firmware and mac from handle | ||
| 36 | * @returns controller device info | ||
| 37 | */ | ||
| 38 | DriverResult GetDeviceInfo(DeviceInfo& controller_type); | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Sends a request to obtain the joycon type from handle | ||
| 42 | * @returns controller type of the joycon | ||
| 43 | */ | ||
| 44 | DriverResult GetControllerType(ControllerType& controller_type); | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Enables motion input | ||
| 48 | * @param enable if true motion data will be enabled | ||
| 49 | */ | ||
| 50 | DriverResult EnableImu(bool enable); | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Configures the motion sensor with the specified parameters | ||
| 54 | * @param gsen gyroscope sensor sensitvity in degrees per second | ||
| 55 | * @param gfrec gyroscope sensor frequency in hertz | ||
| 56 | * @param asen accelerometer sensitivity in G force | ||
| 57 | * @param afrec accelerometer frequency in hertz | ||
| 58 | */ | ||
| 59 | DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec, | ||
| 60 | AccelerometerSensitivity asen, AccelerometerPerformance afrec); | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Request battery level from the device | ||
| 64 | * @returns battery level | ||
| 65 | */ | ||
| 66 | DriverResult GetBattery(u32& battery_level); | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Request joycon colors from the device | ||
| 70 | * @returns colors of the body and buttons | ||
| 71 | */ | ||
| 72 | DriverResult GetColor(Color& color); | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Request joycon serial number from the device | ||
| 76 | * @returns 16 byte serial number | ||
| 77 | */ | ||
| 78 | DriverResult GetSerialNumber(SerialNumber& serial_number); | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Request joycon serial number from the device | ||
| 82 | * @returns 16 byte serial number | ||
| 83 | */ | ||
| 84 | DriverResult GetTemperature(u32& temperature); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Request joycon serial number from the device | ||
| 88 | * @returns 16 byte serial number | ||
| 89 | */ | ||
| 90 | DriverResult GetVersionNumber(FirmwareVersion& version); | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Sets home led behaviour | ||
| 94 | */ | ||
| 95 | DriverResult SetHomeLight(); | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Sets home led into a slow breathing state | ||
| 99 | */ | ||
| 100 | DriverResult SetLedBusy(); | ||
| 101 | |||
| 102 | /** | ||
| 103 | * Sets the 4 player leds on the joycon on a solid state | ||
| 104 | * @params bit flag containing the led state | ||
| 105 | */ | ||
| 106 | DriverResult SetLedPattern(u8 leds); | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Sets the 4 player leds on the joycon on a blinking state | ||
| 110 | * @returns bit flag containing the led state | ||
| 111 | */ | ||
| 112 | DriverResult SetLedBlinkPattern(u8 leds); | ||
| 113 | }; | ||
| 114 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp new file mode 100644 index 000000000..731fd5981 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/irs.cpp | |||
| @@ -0,0 +1,299 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <thread> | ||
| 5 | #include "common/logging/log.h" | ||
| 6 | #include "input_common/helpers/joycon_protocol/irs.h" | ||
| 7 | |||
| 8 | namespace InputCommon::Joycon { | ||
| 9 | |||
| 10 | IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle) | ||
| 11 | : JoyconCommonProtocol(std::move(handle)) {} | ||
| 12 | |||
| 13 | DriverResult IrsProtocol::EnableIrs() { | ||
| 14 | LOG_INFO(Input, "Enable IRS"); | ||
| 15 | ScopedSetBlocking sb(this); | ||
| 16 | DriverResult result{DriverResult::Success}; | ||
| 17 | |||
| 18 | if (result == DriverResult::Success) { | ||
| 19 | result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); | ||
| 20 | } | ||
| 21 | if (result == DriverResult::Success) { | ||
| 22 | result = EnableMCU(true); | ||
| 23 | } | ||
| 24 | if (result == DriverResult::Success) { | ||
| 25 | result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); | ||
| 26 | } | ||
| 27 | if (result == DriverResult::Success) { | ||
| 28 | const MCUConfig config{ | ||
| 29 | .command = MCUCommand::ConfigureMCU, | ||
| 30 | .sub_command = MCUSubCommand::SetMCUMode, | ||
| 31 | .mode = MCUMode::IR, | ||
| 32 | .crc = {}, | ||
| 33 | }; | ||
| 34 | |||
| 35 | result = ConfigureMCU(config); | ||
| 36 | } | ||
| 37 | if (result == DriverResult::Success) { | ||
| 38 | result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR); | ||
| 39 | } | ||
| 40 | if (result == DriverResult::Success) { | ||
| 41 | result = ConfigureIrs(); | ||
| 42 | } | ||
| 43 | if (result == DriverResult::Success) { | ||
| 44 | result = WriteRegistersStep1(); | ||
| 45 | } | ||
| 46 | if (result == DriverResult::Success) { | ||
| 47 | result = WriteRegistersStep2(); | ||
| 48 | } | ||
| 49 | |||
| 50 | is_enabled = true; | ||
| 51 | |||
| 52 | return result; | ||
| 53 | } | ||
| 54 | |||
| 55 | DriverResult IrsProtocol::DisableIrs() { | ||
| 56 | LOG_DEBUG(Input, "Disable IRS"); | ||
| 57 | ScopedSetBlocking sb(this); | ||
| 58 | DriverResult result{DriverResult::Success}; | ||
| 59 | |||
| 60 | if (result == DriverResult::Success) { | ||
| 61 | result = EnableMCU(false); | ||
| 62 | } | ||
| 63 | |||
| 64 | is_enabled = false; | ||
| 65 | |||
| 66 | return result; | ||
| 67 | } | ||
| 68 | |||
| 69 | DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) { | ||
| 70 | irs_mode = mode; | ||
| 71 | switch (format) { | ||
| 72 | case IrsResolution::Size320x240: | ||
| 73 | resolution_code = IrsResolutionCode::Size320x240; | ||
| 74 | fragments = IrsFragments::Size320x240; | ||
| 75 | resolution = IrsResolution::Size320x240; | ||
| 76 | break; | ||
| 77 | case IrsResolution::Size160x120: | ||
| 78 | resolution_code = IrsResolutionCode::Size160x120; | ||
| 79 | fragments = IrsFragments::Size160x120; | ||
| 80 | resolution = IrsResolution::Size160x120; | ||
| 81 | break; | ||
| 82 | case IrsResolution::Size80x60: | ||
| 83 | resolution_code = IrsResolutionCode::Size80x60; | ||
| 84 | fragments = IrsFragments::Size80x60; | ||
| 85 | resolution = IrsResolution::Size80x60; | ||
| 86 | break; | ||
| 87 | case IrsResolution::Size20x15: | ||
| 88 | resolution_code = IrsResolutionCode::Size20x15; | ||
| 89 | fragments = IrsFragments::Size20x15; | ||
| 90 | resolution = IrsResolution::Size20x15; | ||
| 91 | break; | ||
| 92 | case IrsResolution::Size40x30: | ||
| 93 | default: | ||
| 94 | resolution_code = IrsResolutionCode::Size40x30; | ||
| 95 | fragments = IrsFragments::Size40x30; | ||
| 96 | resolution = IrsResolution::Size40x30; | ||
| 97 | break; | ||
| 98 | } | ||
| 99 | |||
| 100 | // Restart feature | ||
| 101 | if (is_enabled) { | ||
| 102 | DisableIrs(); | ||
| 103 | return EnableIrs(); | ||
| 104 | } | ||
| 105 | |||
| 106 | return DriverResult::Success; | ||
| 107 | } | ||
| 108 | |||
| 109 | DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) { | ||
| 110 | const u8 next_packet_fragment = | ||
| 111 | static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1)); | ||
| 112 | |||
| 113 | if (buffer[0] == 0x31 && buffer[49] == 0x03) { | ||
| 114 | u8 new_packet_fragment = buffer[52]; | ||
| 115 | if (new_packet_fragment == next_packet_fragment) { | ||
| 116 | packet_fragment = next_packet_fragment; | ||
| 117 | memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300); | ||
| 118 | |||
| 119 | return RequestFrame(packet_fragment); | ||
| 120 | } | ||
| 121 | |||
| 122 | if (new_packet_fragment == packet_fragment) { | ||
| 123 | return RequestFrame(packet_fragment); | ||
| 124 | } | ||
| 125 | |||
| 126 | return ResendFrame(next_packet_fragment); | ||
| 127 | } | ||
| 128 | |||
| 129 | return RequestFrame(packet_fragment); | ||
| 130 | } | ||
| 131 | |||
| 132 | DriverResult IrsProtocol::ConfigureIrs() { | ||
| 133 | LOG_DEBUG(Input, "Configure IRS"); | ||
| 134 | constexpr std::size_t max_tries = 28; | ||
| 135 | SubCommandResponse output{}; | ||
| 136 | std::size_t tries = 0; | ||
| 137 | |||
| 138 | const IrsConfigure irs_configuration{ | ||
| 139 | .command = MCUCommand::ConfigureIR, | ||
| 140 | .sub_command = MCUSubCommand::SetDeviceMode, | ||
| 141 | .irs_mode = IrsMode::ImageTransfer, | ||
| 142 | .number_of_fragments = fragments, | ||
| 143 | .mcu_major_version = 0x0500, | ||
| 144 | .mcu_minor_version = 0x1800, | ||
| 145 | .crc = {}, | ||
| 146 | }; | ||
| 147 | buf_image.resize((static_cast<u8>(fragments) + 1) * 300); | ||
| 148 | |||
| 149 | std::array<u8, sizeof(IrsConfigure)> request_data{}; | ||
| 150 | memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure)); | ||
| 151 | request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); | ||
| 152 | do { | ||
| 153 | const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); | ||
| 154 | |||
| 155 | if (result != DriverResult::Success) { | ||
| 156 | return result; | ||
| 157 | } | ||
| 158 | if (tries++ >= max_tries) { | ||
| 159 | return DriverResult::WrongReply; | ||
| 160 | } | ||
| 161 | } while (output.command_data[0] != 0x0b); | ||
| 162 | |||
| 163 | return DriverResult::Success; | ||
| 164 | } | ||
| 165 | |||
| 166 | DriverResult IrsProtocol::WriteRegistersStep1() { | ||
| 167 | LOG_DEBUG(Input, "WriteRegistersStep1"); | ||
| 168 | DriverResult result{DriverResult::Success}; | ||
| 169 | constexpr std::size_t max_tries = 28; | ||
| 170 | SubCommandResponse output{}; | ||
| 171 | std::size_t tries = 0; | ||
| 172 | |||
| 173 | const IrsWriteRegisters irs_registers{ | ||
| 174 | .command = MCUCommand::ConfigureIR, | ||
| 175 | .sub_command = MCUSubCommand::WriteDeviceRegisters, | ||
| 176 | .number_of_registers = 0x9, | ||
| 177 | .registers = | ||
| 178 | { | ||
| 179 | IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)}, | ||
| 180 | {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)}, | ||
| 181 | {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)}, | ||
| 182 | {IrRegistersAddress::ExposureTime, 0x00}, | ||
| 183 | {IrRegistersAddress::Leds, static_cast<u8>(leds)}, | ||
| 184 | {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)}, | ||
| 185 | {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)}, | ||
| 186 | {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)}, | ||
| 187 | {IrRegistersAddress::WhitePixelThreshold, 0xc8}, | ||
| 188 | }, | ||
| 189 | .crc = {}, | ||
| 190 | }; | ||
| 191 | |||
| 192 | std::array<u8, sizeof(IrsWriteRegisters)> request_data{}; | ||
| 193 | memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); | ||
| 194 | request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); | ||
| 195 | |||
| 196 | std::array<u8, 38> mcu_request{0x02}; | ||
| 197 | mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); | ||
| 198 | mcu_request[37] = 0xFF; | ||
| 199 | |||
| 200 | if (result != DriverResult::Success) { | ||
| 201 | return result; | ||
| 202 | } | ||
| 203 | |||
| 204 | do { | ||
| 205 | result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); | ||
| 206 | |||
| 207 | // First time we need to set the report mode | ||
| 208 | if (result == DriverResult::Success && tries == 0) { | ||
| 209 | result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); | ||
| 210 | } | ||
| 211 | if (result == DriverResult::Success && tries == 0) { | ||
| 212 | GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output); | ||
| 213 | } | ||
| 214 | |||
| 215 | if (result != DriverResult::Success) { | ||
| 216 | return result; | ||
| 217 | } | ||
| 218 | if (tries++ >= max_tries) { | ||
| 219 | return DriverResult::WrongReply; | ||
| 220 | } | ||
| 221 | } while (!(output.command_data[0] == 0x13 && output.command_data[2] == 0x07) && | ||
| 222 | output.command_data[0] != 0x23); | ||
| 223 | |||
| 224 | return DriverResult::Success; | ||
| 225 | } | ||
| 226 | |||
| 227 | DriverResult IrsProtocol::WriteRegistersStep2() { | ||
| 228 | LOG_DEBUG(Input, "WriteRegistersStep2"); | ||
| 229 | constexpr std::size_t max_tries = 28; | ||
| 230 | SubCommandResponse output{}; | ||
| 231 | std::size_t tries = 0; | ||
| 232 | |||
| 233 | const IrsWriteRegisters irs_registers{ | ||
| 234 | .command = MCUCommand::ConfigureIR, | ||
| 235 | .sub_command = MCUSubCommand::WriteDeviceRegisters, | ||
| 236 | .number_of_registers = 0x8, | ||
| 237 | .registers = | ||
| 238 | { | ||
| 239 | IrsRegister{IrRegistersAddress::LedIntensitiyMSB, | ||
| 240 | static_cast<u8>(led_intensity >> 8)}, | ||
| 241 | {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)}, | ||
| 242 | {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)}, | ||
| 243 | {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)}, | ||
| 244 | {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)}, | ||
| 245 | {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)}, | ||
| 246 | {IrRegistersAddress::UpdateTime, 0x2d}, | ||
| 247 | {IrRegistersAddress::FinalizeConfig, 0x01}, | ||
| 248 | }, | ||
| 249 | .crc = {}, | ||
| 250 | }; | ||
| 251 | |||
| 252 | std::array<u8, sizeof(IrsWriteRegisters)> request_data{}; | ||
| 253 | memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); | ||
| 254 | request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); | ||
| 255 | do { | ||
| 256 | const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); | ||
| 257 | |||
| 258 | if (result != DriverResult::Success) { | ||
| 259 | return result; | ||
| 260 | } | ||
| 261 | if (tries++ >= max_tries) { | ||
| 262 | return DriverResult::WrongReply; | ||
| 263 | } | ||
| 264 | } while (output.command_data[0] != 0x13 && output.command_data[0] != 0x23); | ||
| 265 | |||
| 266 | return DriverResult::Success; | ||
| 267 | } | ||
| 268 | |||
| 269 | DriverResult IrsProtocol::RequestFrame(u8 frame) { | ||
| 270 | std::array<u8, 38> mcu_request{}; | ||
| 271 | mcu_request[3] = frame; | ||
| 272 | mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); | ||
| 273 | mcu_request[37] = 0xFF; | ||
| 274 | return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); | ||
| 275 | } | ||
| 276 | |||
| 277 | DriverResult IrsProtocol::ResendFrame(u8 frame) { | ||
| 278 | std::array<u8, 38> mcu_request{}; | ||
| 279 | mcu_request[1] = 0x1; | ||
| 280 | mcu_request[2] = frame; | ||
| 281 | mcu_request[3] = 0x0; | ||
| 282 | mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); | ||
| 283 | mcu_request[37] = 0xFF; | ||
| 284 | return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); | ||
| 285 | } | ||
| 286 | |||
| 287 | std::vector<u8> IrsProtocol::GetImage() const { | ||
| 288 | return buf_image; | ||
| 289 | } | ||
| 290 | |||
| 291 | IrsResolution IrsProtocol::GetIrsFormat() const { | ||
| 292 | return resolution; | ||
| 293 | } | ||
| 294 | |||
| 295 | bool IrsProtocol::IsEnabled() const { | ||
| 296 | return is_enabled; | ||
| 297 | } | ||
| 298 | |||
| 299 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h new file mode 100644 index 000000000..76dfa02ea --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/irs.h | |||
| @@ -0,0 +1,63 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <vector> | ||
| 12 | |||
| 13 | #include "input_common/helpers/joycon_protocol/common_protocol.h" | ||
| 14 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 15 | |||
| 16 | namespace InputCommon::Joycon { | ||
| 17 | |||
| 18 | class IrsProtocol final : private JoyconCommonProtocol { | ||
| 19 | public: | ||
| 20 | explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle); | ||
| 21 | |||
| 22 | DriverResult EnableIrs(); | ||
| 23 | |||
| 24 | DriverResult DisableIrs(); | ||
| 25 | |||
| 26 | DriverResult SetIrsConfig(IrsMode mode, IrsResolution format); | ||
| 27 | |||
| 28 | DriverResult RequestImage(std::span<u8> buffer); | ||
| 29 | |||
| 30 | std::vector<u8> GetImage() const; | ||
| 31 | |||
| 32 | IrsResolution GetIrsFormat() const; | ||
| 33 | |||
| 34 | bool IsEnabled() const; | ||
| 35 | |||
| 36 | private: | ||
| 37 | DriverResult ConfigureIrs(); | ||
| 38 | |||
| 39 | DriverResult WriteRegistersStep1(); | ||
| 40 | DriverResult WriteRegistersStep2(); | ||
| 41 | |||
| 42 | DriverResult RequestFrame(u8 frame); | ||
| 43 | DriverResult ResendFrame(u8 frame); | ||
| 44 | |||
| 45 | IrsMode irs_mode{IrsMode::ImageTransfer}; | ||
| 46 | IrsResolution resolution{IrsResolution::Size40x30}; | ||
| 47 | IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30}; | ||
| 48 | IrsFragments fragments{IrsFragments::Size40x30}; | ||
| 49 | IrLeds leds{IrLeds::BrightAndDim}; | ||
| 50 | IrExLedFilter led_filter{IrExLedFilter::Enabled}; | ||
| 51 | IrImageFlip image_flip{IrImageFlip::Normal}; | ||
| 52 | u8 digital_gain{0x01}; | ||
| 53 | u16 exposure{0x2490}; | ||
| 54 | u16 led_intensity{0x0f10}; | ||
| 55 | u32 denoise{0x012344}; | ||
| 56 | |||
| 57 | u8 packet_fragment{}; | ||
| 58 | std::vector<u8> buf_image; // 8bpp greyscale image. | ||
| 59 | |||
| 60 | bool is_enabled{}; | ||
| 61 | }; | ||
| 62 | |||
| 63 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h new file mode 100644 index 000000000..b91934990 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h | |||
| @@ -0,0 +1,697 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <array> | ||
| 12 | #include <functional> | ||
| 13 | #include <SDL_hidapi.h> | ||
| 14 | |||
| 15 | #include "common/bit_field.h" | ||
| 16 | #include "common/common_funcs.h" | ||
| 17 | #include "common/common_types.h" | ||
| 18 | |||
| 19 | namespace InputCommon::Joycon { | ||
| 20 | constexpr u32 MaxErrorCount = 50; | ||
| 21 | constexpr u32 MaxBufferSize = 368; | ||
| 22 | constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40}; | ||
| 23 | |||
| 24 | using MacAddress = std::array<u8, 6>; | ||
| 25 | using SerialNumber = std::array<u8, 15>; | ||
| 26 | |||
| 27 | enum class ControllerType : u8 { | ||
| 28 | None = 0x00, | ||
| 29 | Left = 0x01, | ||
| 30 | Right = 0x02, | ||
| 31 | Pro = 0x03, | ||
| 32 | Dual = 0x05, // TODO: Verify this id | ||
| 33 | LarkHvc1 = 0x07, | ||
| 34 | LarkHvc2 = 0x08, | ||
| 35 | LarkNesLeft = 0x09, | ||
| 36 | LarkNesRight = 0x0A, | ||
| 37 | Lucia = 0x0B, | ||
| 38 | Lagon = 0x0C, | ||
| 39 | Lager = 0x0D, | ||
| 40 | }; | ||
| 41 | |||
| 42 | enum class PadAxes { | ||
| 43 | LeftStickX, | ||
| 44 | LeftStickY, | ||
| 45 | RightStickX, | ||
| 46 | RightStickY, | ||
| 47 | Undefined, | ||
| 48 | }; | ||
| 49 | |||
| 50 | enum class PadMotion { | ||
| 51 | LeftMotion, | ||
| 52 | RightMotion, | ||
| 53 | Undefined, | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum class PadButton : u32 { | ||
| 57 | Down = 0x000001, | ||
| 58 | Up = 0x000002, | ||
| 59 | Right = 0x000004, | ||
| 60 | Left = 0x000008, | ||
| 61 | LeftSR = 0x000010, | ||
| 62 | LeftSL = 0x000020, | ||
| 63 | L = 0x000040, | ||
| 64 | ZL = 0x000080, | ||
| 65 | Y = 0x000100, | ||
| 66 | X = 0x000200, | ||
| 67 | B = 0x000400, | ||
| 68 | A = 0x000800, | ||
| 69 | RightSR = 0x001000, | ||
| 70 | RightSL = 0x002000, | ||
| 71 | R = 0x004000, | ||
| 72 | ZR = 0x008000, | ||
| 73 | Minus = 0x010000, | ||
| 74 | Plus = 0x020000, | ||
| 75 | StickR = 0x040000, | ||
| 76 | StickL = 0x080000, | ||
| 77 | Home = 0x100000, | ||
| 78 | Capture = 0x200000, | ||
| 79 | }; | ||
| 80 | |||
| 81 | enum class PasivePadButton : u32 { | ||
| 82 | Down_A = 0x0001, | ||
| 83 | Right_X = 0x0002, | ||
| 84 | Left_B = 0x0004, | ||
| 85 | Up_Y = 0x0008, | ||
| 86 | SL = 0x0010, | ||
| 87 | SR = 0x0020, | ||
| 88 | Minus = 0x0100, | ||
| 89 | Plus = 0x0200, | ||
| 90 | StickL = 0x0400, | ||
| 91 | StickR = 0x0800, | ||
| 92 | Home = 0x1000, | ||
| 93 | Capture = 0x2000, | ||
| 94 | L_R = 0x4000, | ||
| 95 | ZL_ZR = 0x8000, | ||
| 96 | }; | ||
| 97 | |||
| 98 | enum class OutputReport : u8 { | ||
| 99 | RUMBLE_AND_SUBCMD = 0x01, | ||
| 100 | FW_UPDATE_PKT = 0x03, | ||
| 101 | RUMBLE_ONLY = 0x10, | ||
| 102 | MCU_DATA = 0x11, | ||
| 103 | USB_CMD = 0x80, | ||
| 104 | }; | ||
| 105 | |||
| 106 | enum class FeatureReport : u8 { | ||
| 107 | Last_SUBCMD = 0x02, | ||
| 108 | OTA_GW_UPGRADE = 0x70, | ||
| 109 | SETUP_MEM_READ = 0x71, | ||
| 110 | MEM_READ = 0x72, | ||
| 111 | ERASE_MEM_SECTOR = 0x73, | ||
| 112 | MEM_WRITE = 0x74, | ||
| 113 | LAUNCH = 0x75, | ||
| 114 | }; | ||
| 115 | |||
| 116 | enum class SubCommand : u8 { | ||
| 117 | STATE = 0x00, | ||
| 118 | MANUAL_BT_PAIRING = 0x01, | ||
| 119 | REQ_DEV_INFO = 0x02, | ||
| 120 | SET_REPORT_MODE = 0x03, | ||
| 121 | TRIGGERS_ELAPSED = 0x04, | ||
| 122 | GET_PAGE_LIST_STATE = 0x05, | ||
| 123 | SET_HCI_STATE = 0x06, | ||
| 124 | RESET_PAIRING_INFO = 0x07, | ||
| 125 | LOW_POWER_MODE = 0x08, | ||
| 126 | SPI_FLASH_READ = 0x10, | ||
| 127 | SPI_FLASH_WRITE = 0x11, | ||
| 128 | SPI_SECTOR_ERASE = 0x12, | ||
| 129 | RESET_MCU = 0x20, | ||
| 130 | SET_MCU_CONFIG = 0x21, | ||
| 131 | SET_MCU_STATE = 0x22, | ||
| 132 | SET_PLAYER_LIGHTS = 0x30, | ||
| 133 | GET_PLAYER_LIGHTS = 0x31, | ||
| 134 | SET_HOME_LIGHT = 0x38, | ||
| 135 | ENABLE_IMU = 0x40, | ||
| 136 | SET_IMU_SENSITIVITY = 0x41, | ||
| 137 | WRITE_IMU_REG = 0x42, | ||
| 138 | READ_IMU_REG = 0x43, | ||
| 139 | ENABLE_VIBRATION = 0x48, | ||
| 140 | GET_REGULATED_VOLTAGE = 0x50, | ||
| 141 | SET_EXTERNAL_CONFIG = 0x58, | ||
| 142 | GET_EXTERNAL_DEVICE_INFO = 0x59, | ||
| 143 | ENABLE_EXTERNAL_POLLING = 0x5A, | ||
| 144 | DISABLE_EXTERNAL_POLLING = 0x5B, | ||
| 145 | SET_EXTERNAL_FORMAT_CONFIG = 0x5C, | ||
| 146 | }; | ||
| 147 | |||
| 148 | enum class UsbSubCommand : u8 { | ||
| 149 | CONN_STATUS = 0x01, | ||
| 150 | HADSHAKE = 0x02, | ||
| 151 | BAUDRATE_3M = 0x03, | ||
| 152 | NO_TIMEOUT = 0x04, | ||
| 153 | EN_TIMEOUT = 0x05, | ||
| 154 | RESET = 0x06, | ||
| 155 | PRE_HANDSHAKE = 0x91, | ||
| 156 | SEND_UART = 0x92, | ||
| 157 | }; | ||
| 158 | |||
| 159 | enum class CalibrationMagic : u8 { | ||
| 160 | USR_MAGIC_0 = 0xB2, | ||
| 161 | USR_MAGIC_1 = 0xA1, | ||
| 162 | }; | ||
| 163 | |||
| 164 | enum class SpiAddress : u16 { | ||
| 165 | MAGIC = 0x0000, | ||
| 166 | MAC_ADDRESS = 0x0015, | ||
| 167 | PAIRING_INFO = 0x2000, | ||
| 168 | SHIPMENT = 0x5000, | ||
| 169 | SERIAL_NUMBER = 0x6000, | ||
| 170 | DEVICE_TYPE = 0x6012, | ||
| 171 | FORMAT_VERSION = 0x601B, | ||
| 172 | FACT_IMU_DATA = 0x6020, | ||
| 173 | FACT_LEFT_DATA = 0x603d, | ||
| 174 | FACT_RIGHT_DATA = 0x6046, | ||
| 175 | COLOR_DATA = 0x6050, | ||
| 176 | DESIGN_VARIATION = 0x605C, | ||
| 177 | SENSOR_DATA = 0x6080, | ||
| 178 | USER_LEFT_MAGIC = 0x8010, | ||
| 179 | USER_LEFT_DATA = 0x8012, | ||
| 180 | USER_RIGHT_MAGIC = 0x801B, | ||
| 181 | USER_RIGHT_DATA = 0x801D, | ||
| 182 | USER_IMU_MAGIC = 0x8026, | ||
| 183 | USER_IMU_DATA = 0x8028, | ||
| 184 | }; | ||
| 185 | |||
| 186 | enum class ReportMode : u8 { | ||
| 187 | ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00, | ||
| 188 | ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01, | ||
| 189 | ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02, | ||
| 190 | ACTIVE_POLLING_IR_CAMERA_DATA = 0x03, | ||
| 191 | SUBCMD_REPLY = 0x21, | ||
| 192 | MCU_UPDATE_STATE = 0x23, | ||
| 193 | STANDARD_FULL_60HZ = 0x30, | ||
| 194 | NFC_IR_MODE_60HZ = 0x31, | ||
| 195 | SIMPLE_HID_MODE = 0x3F, | ||
| 196 | INPUT_USB_RESPONSE = 0x81, | ||
| 197 | }; | ||
| 198 | |||
| 199 | enum class GyroSensitivity : u8 { | ||
| 200 | DPS250, | ||
| 201 | DPS500, | ||
| 202 | DPS1000, | ||
| 203 | DPS2000, // Default | ||
| 204 | }; | ||
| 205 | |||
| 206 | enum class AccelerometerSensitivity : u8 { | ||
| 207 | G8, // Default | ||
| 208 | G4, | ||
| 209 | G2, | ||
| 210 | G16, | ||
| 211 | }; | ||
| 212 | |||
| 213 | enum class GyroPerformance : u8 { | ||
| 214 | HZ833, | ||
| 215 | HZ208, // Default | ||
| 216 | }; | ||
| 217 | |||
| 218 | enum class AccelerometerPerformance : u8 { | ||
| 219 | HZ200, | ||
| 220 | HZ100, // Default | ||
| 221 | }; | ||
| 222 | |||
| 223 | enum class MCUCommand : u8 { | ||
| 224 | ConfigureMCU = 0x21, | ||
| 225 | ConfigureIR = 0x23, | ||
| 226 | }; | ||
| 227 | |||
| 228 | enum class MCUSubCommand : u8 { | ||
| 229 | SetMCUMode = 0x0, | ||
| 230 | SetDeviceMode = 0x1, | ||
| 231 | ReadDeviceMode = 0x02, | ||
| 232 | WriteDeviceRegisters = 0x4, | ||
| 233 | }; | ||
| 234 | |||
| 235 | enum class MCUMode : u8 { | ||
| 236 | Suspend = 0, | ||
| 237 | Standby = 1, | ||
| 238 | Ringcon = 3, | ||
| 239 | NFC = 4, | ||
| 240 | IR = 5, | ||
| 241 | MaybeFWUpdate = 6, | ||
| 242 | }; | ||
| 243 | |||
| 244 | enum class MCURequest : u8 { | ||
| 245 | GetMCUStatus = 1, | ||
| 246 | GetNFCData = 2, | ||
| 247 | GetIRData = 3, | ||
| 248 | }; | ||
| 249 | |||
| 250 | enum class MCUReport : u8 { | ||
| 251 | Empty = 0x00, | ||
| 252 | StateReport = 0x01, | ||
| 253 | IRData = 0x03, | ||
| 254 | BusyInitializing = 0x0b, | ||
| 255 | IRStatus = 0x13, | ||
| 256 | IRRegisters = 0x1b, | ||
| 257 | NFCState = 0x2a, | ||
| 258 | NFCReadData = 0x3a, | ||
| 259 | EmptyAwaitingCmd = 0xff, | ||
| 260 | }; | ||
| 261 | |||
| 262 | enum class MCUPacketFlag : u8 { | ||
| 263 | MorePacketsRemaining = 0x00, | ||
| 264 | LastCommandPacket = 0x08, | ||
| 265 | }; | ||
| 266 | |||
| 267 | enum class NFCReadCommand : u8 { | ||
| 268 | CancelAll = 0x00, | ||
| 269 | StartPolling = 0x01, | ||
| 270 | StopPolling = 0x02, | ||
| 271 | StartWaitingRecieve = 0x04, | ||
| 272 | Ntag = 0x06, | ||
| 273 | Mifare = 0x0F, | ||
| 274 | }; | ||
| 275 | |||
| 276 | enum class NFCTagType : u8 { | ||
| 277 | AllTags = 0x00, | ||
| 278 | Ntag215 = 0x01, | ||
| 279 | }; | ||
| 280 | |||
| 281 | enum class NFCPages { | ||
| 282 | Block0 = 0, | ||
| 283 | Block45 = 45, | ||
| 284 | Block135 = 135, | ||
| 285 | Block231 = 231, | ||
| 286 | }; | ||
| 287 | |||
| 288 | enum class NFCStatus : u8 { | ||
| 289 | LastPackage = 0x04, | ||
| 290 | TagLost = 0x07, | ||
| 291 | }; | ||
| 292 | |||
| 293 | enum class IrsMode : u8 { | ||
| 294 | None = 0x02, | ||
| 295 | Moment = 0x03, | ||
| 296 | Dpd = 0x04, | ||
| 297 | Clustering = 0x06, | ||
| 298 | ImageTransfer = 0x07, | ||
| 299 | Silhouette = 0x08, | ||
| 300 | TeraImage = 0x09, | ||
| 301 | SilhouetteTeraImage = 0x0A, | ||
| 302 | }; | ||
| 303 | |||
| 304 | enum class IrsResolution { | ||
| 305 | Size320x240, | ||
| 306 | Size160x120, | ||
| 307 | Size80x60, | ||
| 308 | Size40x30, | ||
| 309 | Size20x15, | ||
| 310 | None, | ||
| 311 | }; | ||
| 312 | |||
| 313 | enum class IrsResolutionCode : u8 { | ||
| 314 | Size320x240 = 0x00, // Full pixel array | ||
| 315 | Size160x120 = 0x50, // Sensor Binning [2 X 2] | ||
| 316 | Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2] | ||
| 317 | Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4] | ||
| 318 | Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4] | ||
| 319 | }; | ||
| 320 | |||
| 321 | // Size of image divided by 300 | ||
| 322 | enum class IrsFragments : u8 { | ||
| 323 | Size20x15 = 0x00, | ||
| 324 | Size40x30 = 0x03, | ||
| 325 | Size80x60 = 0x0f, | ||
| 326 | Size160x120 = 0x3f, | ||
| 327 | Size320x240 = 0xFF, | ||
| 328 | }; | ||
| 329 | |||
| 330 | enum class IrLeds : u8 { | ||
| 331 | BrightAndDim = 0x00, | ||
| 332 | Bright = 0x20, | ||
| 333 | Dim = 0x10, | ||
| 334 | None = 0x30, | ||
| 335 | }; | ||
| 336 | |||
| 337 | enum class IrExLedFilter : u8 { | ||
| 338 | Disabled = 0x00, | ||
| 339 | Enabled = 0x03, | ||
| 340 | }; | ||
| 341 | |||
| 342 | enum class IrImageFlip : u8 { | ||
| 343 | Normal = 0x00, | ||
| 344 | Inverted = 0x02, | ||
| 345 | }; | ||
| 346 | |||
| 347 | enum class IrRegistersAddress : u16 { | ||
| 348 | UpdateTime = 0x0400, | ||
| 349 | FinalizeConfig = 0x0700, | ||
| 350 | LedFilter = 0x0e00, | ||
| 351 | Leds = 0x1000, | ||
| 352 | LedIntensitiyMSB = 0x1100, | ||
| 353 | LedIntensitiyLSB = 0x1200, | ||
| 354 | ImageFlip = 0x2d00, | ||
| 355 | Resolution = 0x2e00, | ||
| 356 | DigitalGainLSB = 0x2e01, | ||
| 357 | DigitalGainMSB = 0x2f01, | ||
| 358 | ExposureLSB = 0x3001, | ||
| 359 | ExposureMSB = 0x3101, | ||
| 360 | ExposureTime = 0x3201, | ||
| 361 | WhitePixelThreshold = 0x4301, | ||
| 362 | DenoiseSmoothing = 0x6701, | ||
| 363 | DenoiseEdge = 0x6801, | ||
| 364 | DenoiseColor = 0x6901, | ||
| 365 | }; | ||
| 366 | |||
| 367 | enum class ExternalDeviceId : u16 { | ||
| 368 | RingController = 0x2000, | ||
| 369 | Starlink = 0x2800, | ||
| 370 | }; | ||
| 371 | |||
| 372 | enum class DriverResult { | ||
| 373 | Success, | ||
| 374 | WrongReply, | ||
| 375 | Timeout, | ||
| 376 | InvalidParameters, | ||
| 377 | UnsupportedControllerType, | ||
| 378 | HandleInUse, | ||
| 379 | ErrorReadingData, | ||
| 380 | ErrorWritingData, | ||
| 381 | NoDeviceDetected, | ||
| 382 | InvalidHandle, | ||
| 383 | NotSupported, | ||
| 384 | Disabled, | ||
| 385 | Unknown, | ||
| 386 | }; | ||
| 387 | |||
| 388 | struct MotionSensorCalibration { | ||
| 389 | s16 offset; | ||
| 390 | s16 scale; | ||
| 391 | }; | ||
| 392 | |||
| 393 | struct MotionCalibration { | ||
| 394 | std::array<MotionSensorCalibration, 3> accelerometer; | ||
| 395 | std::array<MotionSensorCalibration, 3> gyro; | ||
| 396 | }; | ||
| 397 | |||
| 398 | // Basic motion data containing data from the sensors and a timestamp in microseconds | ||
| 399 | struct MotionData { | ||
| 400 | float gyro_x{}; | ||
| 401 | float gyro_y{}; | ||
| 402 | float gyro_z{}; | ||
| 403 | float accel_x{}; | ||
| 404 | float accel_y{}; | ||
| 405 | float accel_z{}; | ||
| 406 | u64 delta_timestamp{}; | ||
| 407 | }; | ||
| 408 | |||
| 409 | // Output from SPI read command containing user calibration magic | ||
| 410 | struct MagicSpiCalibration { | ||
| 411 | CalibrationMagic first; | ||
| 412 | CalibrationMagic second; | ||
| 413 | }; | ||
| 414 | static_assert(sizeof(MagicSpiCalibration) == 0x2, "MagicSpiCalibration is an invalid size"); | ||
| 415 | |||
| 416 | // Output from SPI read command containing left joystick calibration | ||
| 417 | struct JoystickLeftSpiCalibration { | ||
| 418 | std::array<u8, 3> max; | ||
| 419 | std::array<u8, 3> center; | ||
| 420 | std::array<u8, 3> min; | ||
| 421 | }; | ||
| 422 | static_assert(sizeof(JoystickLeftSpiCalibration) == 0x9, | ||
| 423 | "JoystickLeftSpiCalibration is an invalid size"); | ||
| 424 | |||
| 425 | // Output from SPI read command containing right joystick calibration | ||
| 426 | struct JoystickRightSpiCalibration { | ||
| 427 | std::array<u8, 3> center; | ||
| 428 | std::array<u8, 3> min; | ||
| 429 | std::array<u8, 3> max; | ||
| 430 | }; | ||
| 431 | static_assert(sizeof(JoystickRightSpiCalibration) == 0x9, | ||
| 432 | "JoystickRightSpiCalibration is an invalid size"); | ||
| 433 | |||
| 434 | struct JoyStickAxisCalibration { | ||
| 435 | u16 max; | ||
| 436 | u16 min; | ||
| 437 | u16 center; | ||
| 438 | }; | ||
| 439 | |||
| 440 | struct JoyStickCalibration { | ||
| 441 | JoyStickAxisCalibration x; | ||
| 442 | JoyStickAxisCalibration y; | ||
| 443 | }; | ||
| 444 | |||
| 445 | struct ImuSpiCalibration { | ||
| 446 | std::array<s16, 3> accelerometer_offset; | ||
| 447 | std::array<s16, 3> accelerometer_scale; | ||
| 448 | std::array<s16, 3> gyroscope_offset; | ||
| 449 | std::array<s16, 3> gyroscope_scale; | ||
| 450 | }; | ||
| 451 | static_assert(sizeof(ImuSpiCalibration) == 0x18, "ImuSpiCalibration is an invalid size"); | ||
| 452 | |||
| 453 | struct RingCalibration { | ||
| 454 | s16 default_value; | ||
| 455 | s16 max_value; | ||
| 456 | s16 min_value; | ||
| 457 | }; | ||
| 458 | |||
| 459 | struct Color { | ||
| 460 | u32 body; | ||
| 461 | u32 buttons; | ||
| 462 | u32 left_grip; | ||
| 463 | u32 right_grip; | ||
| 464 | }; | ||
| 465 | |||
| 466 | struct Battery { | ||
| 467 | union { | ||
| 468 | u8 raw{}; | ||
| 469 | |||
| 470 | BitField<0, 4, u8> unknown; | ||
| 471 | BitField<4, 1, u8> charging; | ||
| 472 | BitField<5, 3, u8> status; | ||
| 473 | }; | ||
| 474 | }; | ||
| 475 | |||
| 476 | struct VibrationValue { | ||
| 477 | f32 low_amplitude; | ||
| 478 | f32 low_frequency; | ||
| 479 | f32 high_amplitude; | ||
| 480 | f32 high_frequency; | ||
| 481 | }; | ||
| 482 | |||
| 483 | struct JoyconHandle { | ||
| 484 | SDL_hid_device* handle = nullptr; | ||
| 485 | u8 packet_counter{}; | ||
| 486 | }; | ||
| 487 | |||
| 488 | struct MCUConfig { | ||
| 489 | MCUCommand command; | ||
| 490 | MCUSubCommand sub_command; | ||
| 491 | MCUMode mode; | ||
| 492 | INSERT_PADDING_BYTES(0x22); | ||
| 493 | u8 crc; | ||
| 494 | }; | ||
| 495 | static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size"); | ||
| 496 | |||
| 497 | #pragma pack(push, 1) | ||
| 498 | struct InputReportPassive { | ||
| 499 | ReportMode report_mode; | ||
| 500 | u16 button_input; | ||
| 501 | u8 stick_state; | ||
| 502 | std::array<u8, 10> unknown_data; | ||
| 503 | }; | ||
| 504 | static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size"); | ||
| 505 | |||
| 506 | struct InputReportActive { | ||
| 507 | ReportMode report_mode; | ||
| 508 | u8 packet_id; | ||
| 509 | Battery battery_status; | ||
| 510 | std::array<u8, 3> button_input; | ||
| 511 | std::array<u8, 3> left_stick_state; | ||
| 512 | std::array<u8, 3> right_stick_state; | ||
| 513 | u8 vibration_code; | ||
| 514 | std::array<s16, 6 * 2> motion_input; | ||
| 515 | INSERT_PADDING_BYTES(0x2); | ||
| 516 | s16 ring_input; | ||
| 517 | }; | ||
| 518 | static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size"); | ||
| 519 | |||
| 520 | struct InputReportNfcIr { | ||
| 521 | ReportMode report_mode; | ||
| 522 | u8 packet_id; | ||
| 523 | Battery battery_status; | ||
| 524 | std::array<u8, 3> button_input; | ||
| 525 | std::array<u8, 3> left_stick_state; | ||
| 526 | std::array<u8, 3> right_stick_state; | ||
| 527 | u8 vibration_code; | ||
| 528 | std::array<s16, 6 * 2> motion_input; | ||
| 529 | INSERT_PADDING_BYTES(0x4); | ||
| 530 | }; | ||
| 531 | static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size"); | ||
| 532 | #pragma pack(pop) | ||
| 533 | |||
| 534 | struct NFCReadBlock { | ||
| 535 | u8 start; | ||
| 536 | u8 end; | ||
| 537 | }; | ||
| 538 | static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size"); | ||
| 539 | |||
| 540 | struct NFCReadBlockCommand { | ||
| 541 | u8 block_count{}; | ||
| 542 | std::array<NFCReadBlock, 4> blocks{}; | ||
| 543 | }; | ||
| 544 | static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size"); | ||
| 545 | |||
| 546 | struct NFCReadCommandData { | ||
| 547 | u8 unknown; | ||
| 548 | u8 uuid_length; | ||
| 549 | u8 unknown_2; | ||
| 550 | std::array<u8, 6> uid; | ||
| 551 | NFCTagType tag_type; | ||
| 552 | NFCReadBlockCommand read_block; | ||
| 553 | }; | ||
| 554 | static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size"); | ||
| 555 | |||
| 556 | struct NFCPollingCommandData { | ||
| 557 | u8 enable_mifare; | ||
| 558 | u8 unknown_1; | ||
| 559 | u8 unknown_2; | ||
| 560 | u8 unknown_3; | ||
| 561 | u8 unknown_4; | ||
| 562 | }; | ||
| 563 | static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size"); | ||
| 564 | |||
| 565 | struct NFCRequestState { | ||
| 566 | MCUSubCommand sub_command; | ||
| 567 | NFCReadCommand command_argument; | ||
| 568 | u8 packet_id; | ||
| 569 | INSERT_PADDING_BYTES(0x1); | ||
| 570 | MCUPacketFlag packet_flag; | ||
| 571 | u8 data_length; | ||
| 572 | union { | ||
| 573 | std::array<u8, 0x1F> raw_data; | ||
| 574 | NFCReadCommandData nfc_read; | ||
| 575 | NFCPollingCommandData nfc_polling; | ||
| 576 | }; | ||
| 577 | u8 crc; | ||
| 578 | }; | ||
| 579 | static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); | ||
| 580 | |||
| 581 | struct IrsConfigure { | ||
| 582 | MCUCommand command; | ||
| 583 | MCUSubCommand sub_command; | ||
| 584 | IrsMode irs_mode; | ||
| 585 | IrsFragments number_of_fragments; | ||
| 586 | u16 mcu_major_version; | ||
| 587 | u16 mcu_minor_version; | ||
| 588 | INSERT_PADDING_BYTES(0x1D); | ||
| 589 | u8 crc; | ||
| 590 | }; | ||
| 591 | static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size"); | ||
| 592 | |||
| 593 | #pragma pack(push, 1) | ||
| 594 | struct IrsRegister { | ||
| 595 | IrRegistersAddress address; | ||
| 596 | u8 value; | ||
| 597 | }; | ||
| 598 | static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size"); | ||
| 599 | |||
| 600 | struct IrsWriteRegisters { | ||
| 601 | MCUCommand command; | ||
| 602 | MCUSubCommand sub_command; | ||
| 603 | u8 number_of_registers; | ||
| 604 | std::array<IrsRegister, 9> registers; | ||
| 605 | INSERT_PADDING_BYTES(0x7); | ||
| 606 | u8 crc; | ||
| 607 | }; | ||
| 608 | static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size"); | ||
| 609 | #pragma pack(pop) | ||
| 610 | |||
| 611 | struct FirmwareVersion { | ||
| 612 | u8 major; | ||
| 613 | u8 minor; | ||
| 614 | }; | ||
| 615 | static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size"); | ||
| 616 | |||
| 617 | struct DeviceInfo { | ||
| 618 | FirmwareVersion firmware; | ||
| 619 | std::array<u8, 2> unknown_1; | ||
| 620 | MacAddress mac_address; | ||
| 621 | std::array<u8, 2> unknown_2; | ||
| 622 | }; | ||
| 623 | static_assert(sizeof(DeviceInfo) == 0xC, "DeviceInfo is an invalid size"); | ||
| 624 | |||
| 625 | struct MotionStatus { | ||
| 626 | bool is_enabled; | ||
| 627 | u64 delta_time; | ||
| 628 | GyroSensitivity gyro_sensitivity; | ||
| 629 | AccelerometerSensitivity accelerometer_sensitivity; | ||
| 630 | }; | ||
| 631 | |||
| 632 | struct RingStatus { | ||
| 633 | bool is_enabled; | ||
| 634 | s16 default_value; | ||
| 635 | s16 max_value; | ||
| 636 | s16 min_value; | ||
| 637 | }; | ||
| 638 | |||
| 639 | struct VibrationPacket { | ||
| 640 | OutputReport output_report; | ||
| 641 | u8 packet_counter; | ||
| 642 | std::array<u8, 0x8> vibration_data; | ||
| 643 | }; | ||
| 644 | static_assert(sizeof(VibrationPacket) == 0xA, "VibrationPacket is an invalid size"); | ||
| 645 | |||
| 646 | struct SubCommandPacket { | ||
| 647 | OutputReport output_report; | ||
| 648 | u8 packet_counter; | ||
| 649 | INSERT_PADDING_BYTES(0x8); // This contains vibration data | ||
| 650 | SubCommand sub_command; | ||
| 651 | std::array<u8, 0x26> command_data; | ||
| 652 | }; | ||
| 653 | static_assert(sizeof(SubCommandPacket) == 0x31, "SubCommandPacket is an invalid size"); | ||
| 654 | |||
| 655 | #pragma pack(push, 1) | ||
| 656 | struct ReadSpiPacket { | ||
| 657 | SpiAddress spi_address; | ||
| 658 | INSERT_PADDING_BYTES(0x2); | ||
| 659 | u8 size; | ||
| 660 | }; | ||
| 661 | static_assert(sizeof(ReadSpiPacket) == 0x5, "ReadSpiPacket is an invalid size"); | ||
| 662 | |||
| 663 | struct SubCommandResponse { | ||
| 664 | InputReportPassive input_report; | ||
| 665 | SubCommand sub_command; | ||
| 666 | union { | ||
| 667 | std::array<u8, 0x30> command_data; | ||
| 668 | SpiAddress spi_address; // Reply from SPI_FLASH_READ subcommand | ||
| 669 | ExternalDeviceId external_device_id; // Reply from GET_EXTERNAL_DEVICE_INFO subcommand | ||
| 670 | DeviceInfo device_info; // Reply from REQ_DEV_INFO subcommand | ||
| 671 | }; | ||
| 672 | u8 crc; // This is never used | ||
| 673 | }; | ||
| 674 | static_assert(sizeof(SubCommandResponse) == 0x40, "SubCommandResponse is an invalid size"); | ||
| 675 | #pragma pack(pop) | ||
| 676 | |||
| 677 | struct MCUCommandResponse { | ||
| 678 | InputReportNfcIr input_report; | ||
| 679 | INSERT_PADDING_BYTES(0x8); | ||
| 680 | MCUReport mcu_report; | ||
| 681 | std::array<u8, 0x13D> mcu_data; | ||
| 682 | u8 crc; | ||
| 683 | }; | ||
| 684 | static_assert(sizeof(MCUCommandResponse) == 0x170, "MCUCommandResponse is an invalid size"); | ||
| 685 | |||
| 686 | struct JoyconCallbacks { | ||
| 687 | std::function<void(Battery)> on_battery_data; | ||
| 688 | std::function<void(Color)> on_color_data; | ||
| 689 | std::function<void(int, bool)> on_button_data; | ||
| 690 | std::function<void(int, f32)> on_stick_data; | ||
| 691 | std::function<void(int, const MotionData&)> on_motion_data; | ||
| 692 | std::function<void(f32)> on_ring_data; | ||
| 693 | std::function<void(const std::vector<u8>&)> on_amiibo_data; | ||
| 694 | std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data; | ||
| 695 | }; | ||
| 696 | |||
| 697 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp new file mode 100644 index 000000000..eeba82986 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/nfc.cpp | |||
| @@ -0,0 +1,406 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <thread> | ||
| 5 | #include "common/logging/log.h" | ||
| 6 | #include "input_common/helpers/joycon_protocol/nfc.h" | ||
| 7 | |||
| 8 | namespace InputCommon::Joycon { | ||
| 9 | |||
| 10 | NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle) | ||
| 11 | : JoyconCommonProtocol(std::move(handle)) {} | ||
| 12 | |||
| 13 | DriverResult NfcProtocol::EnableNfc() { | ||
| 14 | LOG_INFO(Input, "Enable NFC"); | ||
| 15 | ScopedSetBlocking sb(this); | ||
| 16 | DriverResult result{DriverResult::Success}; | ||
| 17 | |||
| 18 | if (result == DriverResult::Success) { | ||
| 19 | result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); | ||
| 20 | } | ||
| 21 | if (result == DriverResult::Success) { | ||
| 22 | result = EnableMCU(true); | ||
| 23 | } | ||
| 24 | if (result == DriverResult::Success) { | ||
| 25 | result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); | ||
| 26 | } | ||
| 27 | if (result == DriverResult::Success) { | ||
| 28 | const MCUConfig config{ | ||
| 29 | .command = MCUCommand::ConfigureMCU, | ||
| 30 | .sub_command = MCUSubCommand::SetMCUMode, | ||
| 31 | .mode = MCUMode::NFC, | ||
| 32 | .crc = {}, | ||
| 33 | }; | ||
| 34 | |||
| 35 | result = ConfigureMCU(config); | ||
| 36 | } | ||
| 37 | |||
| 38 | return result; | ||
| 39 | } | ||
| 40 | |||
| 41 | DriverResult NfcProtocol::DisableNfc() { | ||
| 42 | LOG_DEBUG(Input, "Disable NFC"); | ||
| 43 | ScopedSetBlocking sb(this); | ||
| 44 | DriverResult result{DriverResult::Success}; | ||
| 45 | |||
| 46 | if (result == DriverResult::Success) { | ||
| 47 | result = EnableMCU(false); | ||
| 48 | } | ||
| 49 | |||
| 50 | is_enabled = false; | ||
| 51 | |||
| 52 | return result; | ||
| 53 | } | ||
| 54 | |||
| 55 | DriverResult NfcProtocol::StartNFCPollingMode() { | ||
| 56 | LOG_DEBUG(Input, "Start NFC pooling Mode"); | ||
| 57 | ScopedSetBlocking sb(this); | ||
| 58 | DriverResult result{DriverResult::Success}; | ||
| 59 | TagFoundData tag_data{}; | ||
| 60 | |||
| 61 | if (result == DriverResult::Success) { | ||
| 62 | result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC); | ||
| 63 | } | ||
| 64 | if (result == DriverResult::Success) { | ||
| 65 | result = WaitUntilNfcIsReady(); | ||
| 66 | } | ||
| 67 | if (result == DriverResult::Success) { | ||
| 68 | is_enabled = true; | ||
| 69 | } | ||
| 70 | |||
| 71 | return result; | ||
| 72 | } | ||
| 73 | |||
| 74 | DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) { | ||
| 75 | LOG_DEBUG(Input, "Start NFC pooling Mode"); | ||
| 76 | ScopedSetBlocking sb(this); | ||
| 77 | DriverResult result{DriverResult::Success}; | ||
| 78 | TagFoundData tag_data{}; | ||
| 79 | |||
| 80 | if (result == DriverResult::Success) { | ||
| 81 | result = StartPolling(tag_data); | ||
| 82 | } | ||
| 83 | if (result == DriverResult::Success) { | ||
| 84 | result = ReadTag(tag_data); | ||
| 85 | } | ||
| 86 | if (result == DriverResult::Success) { | ||
| 87 | result = WaitUntilNfcIsReady(); | ||
| 88 | } | ||
| 89 | if (result == DriverResult::Success) { | ||
| 90 | result = StartPolling(tag_data); | ||
| 91 | } | ||
| 92 | if (result == DriverResult::Success) { | ||
| 93 | result = GetAmiiboData(data); | ||
| 94 | } | ||
| 95 | |||
| 96 | return result; | ||
| 97 | } | ||
| 98 | |||
| 99 | bool NfcProtocol::HasAmiibo() { | ||
| 100 | ScopedSetBlocking sb(this); | ||
| 101 | DriverResult result{DriverResult::Success}; | ||
| 102 | TagFoundData tag_data{}; | ||
| 103 | |||
| 104 | if (result == DriverResult::Success) { | ||
| 105 | result = StartPolling(tag_data); | ||
| 106 | } | ||
| 107 | |||
| 108 | return result == DriverResult::Success; | ||
| 109 | } | ||
| 110 | |||
| 111 | DriverResult NfcProtocol::WaitUntilNfcIsReady() { | ||
| 112 | constexpr std::size_t timeout_limit = 10; | ||
| 113 | MCUCommandResponse output{}; | ||
| 114 | std::size_t tries = 0; | ||
| 115 | |||
| 116 | do { | ||
| 117 | auto result = SendStartWaitingRecieveRequest(output); | ||
| 118 | |||
| 119 | if (result != DriverResult::Success) { | ||
| 120 | return result; | ||
| 121 | } | ||
| 122 | if (tries++ > timeout_limit) { | ||
| 123 | return DriverResult::Timeout; | ||
| 124 | } | ||
| 125 | } while (output.mcu_report != MCUReport::NFCState || | ||
| 126 | (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 || | ||
| 127 | output.mcu_data[5] != 0x31 || output.mcu_data[6] != 0x00); | ||
| 128 | |||
| 129 | return DriverResult::Success; | ||
| 130 | } | ||
| 131 | |||
| 132 | DriverResult NfcProtocol::StartPolling(TagFoundData& data) { | ||
| 133 | LOG_DEBUG(Input, "Start Polling for tag"); | ||
| 134 | constexpr std::size_t timeout_limit = 7; | ||
| 135 | MCUCommandResponse output{}; | ||
| 136 | std::size_t tries = 0; | ||
| 137 | |||
| 138 | do { | ||
| 139 | const auto result = SendStartPollingRequest(output); | ||
| 140 | if (result != DriverResult::Success) { | ||
| 141 | return result; | ||
| 142 | } | ||
| 143 | if (tries++ > timeout_limit) { | ||
| 144 | return DriverResult::Timeout; | ||
| 145 | } | ||
| 146 | } while (output.mcu_report != MCUReport::NFCState || | ||
| 147 | (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 || | ||
| 148 | output.mcu_data[6] != 0x09); | ||
| 149 | |||
| 150 | data.type = output.mcu_data[12]; | ||
| 151 | data.uuid.resize(output.mcu_data[14]); | ||
| 152 | memcpy(data.uuid.data(), output.mcu_data.data() + 15, data.uuid.size()); | ||
| 153 | |||
| 154 | return DriverResult::Success; | ||
| 155 | } | ||
| 156 | |||
| 157 | DriverResult NfcProtocol::ReadTag(const TagFoundData& data) { | ||
| 158 | constexpr std::size_t timeout_limit = 10; | ||
| 159 | MCUCommandResponse output{}; | ||
| 160 | std::size_t tries = 0; | ||
| 161 | |||
| 162 | std::string uuid_string; | ||
| 163 | for (auto& content : data.uuid) { | ||
| 164 | uuid_string += fmt::format(" {:02x}", content); | ||
| 165 | } | ||
| 166 | |||
| 167 | LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string); | ||
| 168 | |||
| 169 | tries = 0; | ||
| 170 | NFCPages ntag_pages = NFCPages::Block0; | ||
| 171 | // Read Tag data | ||
| 172 | while (true) { | ||
| 173 | auto result = SendReadAmiiboRequest(output, ntag_pages); | ||
| 174 | const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); | ||
| 175 | |||
| 176 | if (result != DriverResult::Success) { | ||
| 177 | return result; | ||
| 178 | } | ||
| 179 | |||
| 180 | if ((output.mcu_report == MCUReport::NFCReadData || | ||
| 181 | output.mcu_report == MCUReport::NFCState) && | ||
| 182 | nfc_status == NFCStatus::TagLost) { | ||
| 183 | return DriverResult::ErrorReadingData; | ||
| 184 | } | ||
| 185 | |||
| 186 | if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07 && | ||
| 187 | output.mcu_data[2] == 0x01) { | ||
| 188 | if (data.type != 2) { | ||
| 189 | continue; | ||
| 190 | } | ||
| 191 | switch (output.mcu_data[24]) { | ||
| 192 | case 0: | ||
| 193 | ntag_pages = NFCPages::Block135; | ||
| 194 | break; | ||
| 195 | case 3: | ||
| 196 | ntag_pages = NFCPages::Block45; | ||
| 197 | break; | ||
| 198 | case 4: | ||
| 199 | ntag_pages = NFCPages::Block231; | ||
| 200 | break; | ||
| 201 | default: | ||
| 202 | return DriverResult::ErrorReadingData; | ||
| 203 | } | ||
| 204 | continue; | ||
| 205 | } | ||
| 206 | |||
| 207 | if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { | ||
| 208 | // finished | ||
| 209 | SendStopPollingRequest(output); | ||
| 210 | return DriverResult::Success; | ||
| 211 | } | ||
| 212 | |||
| 213 | // Ignore other state reports | ||
| 214 | if (output.mcu_report == MCUReport::NFCState) { | ||
| 215 | continue; | ||
| 216 | } | ||
| 217 | |||
| 218 | if (tries++ > timeout_limit) { | ||
| 219 | return DriverResult::Timeout; | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | return DriverResult::Success; | ||
| 224 | } | ||
| 225 | |||
| 226 | DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) { | ||
| 227 | constexpr std::size_t timeout_limit = 10; | ||
| 228 | MCUCommandResponse output{}; | ||
| 229 | std::size_t tries = 0; | ||
| 230 | |||
| 231 | NFCPages ntag_pages = NFCPages::Block135; | ||
| 232 | std::size_t ntag_buffer_pos = 0; | ||
| 233 | // Read Tag data | ||
| 234 | while (true) { | ||
| 235 | auto result = SendReadAmiiboRequest(output, ntag_pages); | ||
| 236 | const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); | ||
| 237 | |||
| 238 | if (result != DriverResult::Success) { | ||
| 239 | return result; | ||
| 240 | } | ||
| 241 | |||
| 242 | if ((output.mcu_report == MCUReport::NFCReadData || | ||
| 243 | output.mcu_report == MCUReport::NFCState) && | ||
| 244 | nfc_status == NFCStatus::TagLost) { | ||
| 245 | return DriverResult::ErrorReadingData; | ||
| 246 | } | ||
| 247 | |||
| 248 | if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07) { | ||
| 249 | std::size_t payload_size = (output.mcu_data[4] << 8 | output.mcu_data[5]) & 0x7FF; | ||
| 250 | if (output.mcu_data[2] == 0x01) { | ||
| 251 | memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 66, | ||
| 252 | payload_size - 60); | ||
| 253 | ntag_buffer_pos += payload_size - 60; | ||
| 254 | } else { | ||
| 255 | memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 6, | ||
| 256 | payload_size); | ||
| 257 | } | ||
| 258 | continue; | ||
| 259 | } | ||
| 260 | |||
| 261 | if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { | ||
| 262 | LOG_INFO(Input, "Finished reading amiibo"); | ||
| 263 | return DriverResult::Success; | ||
| 264 | } | ||
| 265 | |||
| 266 | // Ignore other state reports | ||
| 267 | if (output.mcu_report == MCUReport::NFCState) { | ||
| 268 | continue; | ||
| 269 | } | ||
| 270 | |||
| 271 | if (tries++ > timeout_limit) { | ||
| 272 | return DriverResult::Timeout; | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | return DriverResult::Success; | ||
| 277 | } | ||
| 278 | |||
| 279 | DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) { | ||
| 280 | NFCRequestState request{ | ||
| 281 | .sub_command = MCUSubCommand::ReadDeviceMode, | ||
| 282 | .command_argument = NFCReadCommand::StartPolling, | ||
| 283 | .packet_id = 0x0, | ||
| 284 | .packet_flag = MCUPacketFlag::LastCommandPacket, | ||
| 285 | .data_length = sizeof(NFCPollingCommandData), | ||
| 286 | .nfc_polling = | ||
| 287 | { | ||
| 288 | .enable_mifare = 0x01, | ||
| 289 | .unknown_1 = 0x00, | ||
| 290 | .unknown_2 = 0x00, | ||
| 291 | .unknown_3 = 0x2c, | ||
| 292 | .unknown_4 = 0x01, | ||
| 293 | }, | ||
| 294 | .crc = {}, | ||
| 295 | }; | ||
| 296 | |||
| 297 | std::array<u8, sizeof(NFCRequestState)> request_data{}; | ||
| 298 | memcpy(request_data.data(), &request, sizeof(NFCRequestState)); | ||
| 299 | request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); | ||
| 300 | return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); | ||
| 301 | } | ||
| 302 | |||
| 303 | DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) { | ||
| 304 | NFCRequestState request{ | ||
| 305 | .sub_command = MCUSubCommand::ReadDeviceMode, | ||
| 306 | .command_argument = NFCReadCommand::StopPolling, | ||
| 307 | .packet_id = 0x0, | ||
| 308 | .packet_flag = MCUPacketFlag::LastCommandPacket, | ||
| 309 | .data_length = 0, | ||
| 310 | .raw_data = {}, | ||
| 311 | .crc = {}, | ||
| 312 | }; | ||
| 313 | |||
| 314 | std::array<u8, sizeof(NFCRequestState)> request_data{}; | ||
| 315 | memcpy(request_data.data(), &request, sizeof(NFCRequestState)); | ||
| 316 | request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); | ||
| 317 | return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); | ||
| 318 | } | ||
| 319 | |||
| 320 | DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& output) { | ||
| 321 | NFCRequestState request{ | ||
| 322 | .sub_command = MCUSubCommand::ReadDeviceMode, | ||
| 323 | .command_argument = NFCReadCommand::StartWaitingRecieve, | ||
| 324 | .packet_id = 0x0, | ||
| 325 | .packet_flag = MCUPacketFlag::LastCommandPacket, | ||
| 326 | .data_length = 0, | ||
| 327 | .raw_data = {}, | ||
| 328 | .crc = {}, | ||
| 329 | }; | ||
| 330 | |||
| 331 | std::vector<u8> request_data(sizeof(NFCRequestState)); | ||
| 332 | memcpy(request_data.data(), &request, sizeof(NFCRequestState)); | ||
| 333 | request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); | ||
| 334 | return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); | ||
| 335 | } | ||
| 336 | |||
| 337 | DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages) { | ||
| 338 | NFCRequestState request{ | ||
| 339 | .sub_command = MCUSubCommand::ReadDeviceMode, | ||
| 340 | .command_argument = NFCReadCommand::Ntag, | ||
| 341 | .packet_id = 0x0, | ||
| 342 | .packet_flag = MCUPacketFlag::LastCommandPacket, | ||
| 343 | .data_length = sizeof(NFCReadCommandData), | ||
| 344 | .nfc_read = | ||
| 345 | { | ||
| 346 | .unknown = 0xd0, | ||
| 347 | .uuid_length = 0x07, | ||
| 348 | .unknown_2 = 0x00, | ||
| 349 | .uid = {}, | ||
| 350 | .tag_type = NFCTagType::AllTags, | ||
| 351 | .read_block = GetReadBlockCommand(ntag_pages), | ||
| 352 | }, | ||
| 353 | .crc = {}, | ||
| 354 | }; | ||
| 355 | |||
| 356 | std::array<u8, sizeof(NFCRequestState)> request_data{}; | ||
| 357 | memcpy(request_data.data(), &request, sizeof(NFCRequestState)); | ||
| 358 | request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); | ||
| 359 | return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); | ||
| 360 | } | ||
| 361 | |||
| 362 | NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const { | ||
| 363 | switch (pages) { | ||
| 364 | case NFCPages::Block0: | ||
| 365 | return { | ||
| 366 | .block_count = 1, | ||
| 367 | }; | ||
| 368 | case NFCPages::Block45: | ||
| 369 | return { | ||
| 370 | .block_count = 1, | ||
| 371 | .blocks = | ||
| 372 | { | ||
| 373 | NFCReadBlock{0x00, 0x2C}, | ||
| 374 | }, | ||
| 375 | }; | ||
| 376 | case NFCPages::Block135: | ||
| 377 | return { | ||
| 378 | .block_count = 3, | ||
| 379 | .blocks = | ||
| 380 | { | ||
| 381 | NFCReadBlock{0x00, 0x3b}, | ||
| 382 | {0x3c, 0x77}, | ||
| 383 | {0x78, 0x86}, | ||
| 384 | }, | ||
| 385 | }; | ||
| 386 | case NFCPages::Block231: | ||
| 387 | return { | ||
| 388 | .block_count = 4, | ||
| 389 | .blocks = | ||
| 390 | { | ||
| 391 | NFCReadBlock{0x00, 0x3b}, | ||
| 392 | {0x3c, 0x77}, | ||
| 393 | {0x78, 0x83}, | ||
| 394 | {0xb4, 0xe6}, | ||
| 395 | }, | ||
| 396 | }; | ||
| 397 | default: | ||
| 398 | return {}; | ||
| 399 | }; | ||
| 400 | } | ||
| 401 | |||
| 402 | bool NfcProtocol::IsEnabled() const { | ||
| 403 | return is_enabled; | ||
| 404 | } | ||
| 405 | |||
| 406 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h new file mode 100644 index 000000000..11e263e07 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/nfc.h | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <vector> | ||
| 12 | |||
| 13 | #include "input_common/helpers/joycon_protocol/common_protocol.h" | ||
| 14 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 15 | |||
| 16 | namespace InputCommon::Joycon { | ||
| 17 | |||
| 18 | class NfcProtocol final : private JoyconCommonProtocol { | ||
| 19 | public: | ||
| 20 | explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle); | ||
| 21 | |||
| 22 | DriverResult EnableNfc(); | ||
| 23 | |||
| 24 | DriverResult DisableNfc(); | ||
| 25 | |||
| 26 | DriverResult StartNFCPollingMode(); | ||
| 27 | |||
| 28 | DriverResult ScanAmiibo(std::vector<u8>& data); | ||
| 29 | |||
| 30 | bool HasAmiibo(); | ||
| 31 | |||
| 32 | bool IsEnabled() const; | ||
| 33 | |||
| 34 | private: | ||
| 35 | struct TagFoundData { | ||
| 36 | u8 type; | ||
| 37 | std::vector<u8> uuid; | ||
| 38 | }; | ||
| 39 | |||
| 40 | DriverResult WaitUntilNfcIsReady(); | ||
| 41 | |||
| 42 | DriverResult StartPolling(TagFoundData& data); | ||
| 43 | |||
| 44 | DriverResult ReadTag(const TagFoundData& data); | ||
| 45 | |||
| 46 | DriverResult GetAmiiboData(std::vector<u8>& data); | ||
| 47 | |||
| 48 | DriverResult SendStartPollingRequest(MCUCommandResponse& output); | ||
| 49 | |||
| 50 | DriverResult SendStopPollingRequest(MCUCommandResponse& output); | ||
| 51 | |||
| 52 | DriverResult SendStartWaitingRecieveRequest(MCUCommandResponse& output); | ||
| 53 | |||
| 54 | DriverResult SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages); | ||
| 55 | |||
| 56 | NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const; | ||
| 57 | |||
| 58 | bool is_enabled{}; | ||
| 59 | }; | ||
| 60 | |||
| 61 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp new file mode 100644 index 000000000..9bb15e935 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/poller.cpp | |||
| @@ -0,0 +1,337 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/logging/log.h" | ||
| 5 | #include "input_common/helpers/joycon_protocol/poller.h" | ||
| 6 | |||
| 7 | namespace InputCommon::Joycon { | ||
| 8 | |||
| 9 | JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_, | ||
| 10 | JoyStickCalibration right_stick_calibration_, | ||
| 11 | MotionCalibration motion_calibration_) | ||
| 12 | : device_type{device_type_}, left_stick_calibration{left_stick_calibration_}, | ||
| 13 | right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {} | ||
| 14 | |||
| 15 | void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) { | ||
| 16 | callbacks = std::move(callbacks_); | ||
| 17 | } | ||
| 18 | |||
| 19 | void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status, | ||
| 20 | const RingStatus& ring_status) { | ||
| 21 | InputReportActive data{}; | ||
| 22 | memcpy(&data, buffer.data(), sizeof(InputReportActive)); | ||
| 23 | |||
| 24 | switch (device_type) { | ||
| 25 | case Joycon::ControllerType::Left: | ||
| 26 | UpdateActiveLeftPadInput(data, motion_status); | ||
| 27 | break; | ||
| 28 | case Joycon::ControllerType::Right: | ||
| 29 | UpdateActiveRightPadInput(data, motion_status); | ||
| 30 | break; | ||
| 31 | case Joycon::ControllerType::Pro: | ||
| 32 | UpdateActiveProPadInput(data, motion_status); | ||
| 33 | break; | ||
| 34 | default: | ||
| 35 | break; | ||
| 36 | } | ||
| 37 | |||
| 38 | if (ring_status.is_enabled) { | ||
| 39 | UpdateRing(data.ring_input, ring_status); | ||
| 40 | } | ||
| 41 | |||
| 42 | callbacks.on_battery_data(data.battery_status); | ||
| 43 | } | ||
| 44 | |||
| 45 | void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) { | ||
| 46 | InputReportPassive data{}; | ||
| 47 | memcpy(&data, buffer.data(), sizeof(InputReportPassive)); | ||
| 48 | |||
| 49 | switch (device_type) { | ||
| 50 | case Joycon::ControllerType::Left: | ||
| 51 | UpdatePasiveLeftPadInput(data); | ||
| 52 | break; | ||
| 53 | case Joycon::ControllerType::Right: | ||
| 54 | UpdatePasiveRightPadInput(data); | ||
| 55 | break; | ||
| 56 | case Joycon::ControllerType::Pro: | ||
| 57 | UpdatePasiveProPadInput(data); | ||
| 58 | break; | ||
| 59 | default: | ||
| 60 | break; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) { | ||
| 65 | // This mode is compatible with the active mode | ||
| 66 | ReadActiveMode(buffer, motion_status, {}); | ||
| 67 | } | ||
| 68 | |||
| 69 | void JoyconPoller::UpdateColor(const Color& color) { | ||
| 70 | callbacks.on_color_data(color); | ||
| 71 | } | ||
| 72 | |||
| 73 | void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) { | ||
| 74 | callbacks.on_amiibo_data(amiibo_data); | ||
| 75 | } | ||
| 76 | |||
| 77 | void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) { | ||
| 78 | callbacks.on_camera_data(camera_data, format); | ||
| 79 | } | ||
| 80 | |||
| 81 | void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) { | ||
| 82 | float normalized_value = static_cast<float>(value - ring_status.default_value); | ||
| 83 | if (normalized_value > 0) { | ||
| 84 | normalized_value = normalized_value / | ||
| 85 | static_cast<float>(ring_status.max_value - ring_status.default_value); | ||
| 86 | } | ||
| 87 | if (normalized_value < 0) { | ||
| 88 | normalized_value = normalized_value / | ||
| 89 | static_cast<float>(ring_status.default_value - ring_status.min_value); | ||
| 90 | } | ||
| 91 | callbacks.on_ring_data(normalized_value); | ||
| 92 | } | ||
| 93 | |||
| 94 | void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input, | ||
| 95 | const MotionStatus& motion_status) { | ||
| 96 | static constexpr std::array<Joycon::PadButton, 11> left_buttons{ | ||
| 97 | Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right, | ||
| 98 | Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR, | ||
| 99 | Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus, | ||
| 100 | Joycon::PadButton::Capture, Joycon::PadButton::StickL, | ||
| 101 | }; | ||
| 102 | |||
| 103 | const u32 raw_button = | ||
| 104 | static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16)); | ||
| 105 | for (std::size_t i = 0; i < left_buttons.size(); ++i) { | ||
| 106 | const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0; | ||
| 107 | const int button = static_cast<int>(left_buttons[i]); | ||
| 108 | callbacks.on_button_data(button, button_status); | ||
| 109 | } | ||
| 110 | |||
| 111 | const u16 raw_left_axis_x = | ||
| 112 | static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8)); | ||
| 113 | const u16 raw_left_axis_y = | ||
| 114 | static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4)); | ||
| 115 | const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x); | ||
| 116 | const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y); | ||
| 117 | callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x); | ||
| 118 | callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y); | ||
| 119 | |||
| 120 | if (motion_status.is_enabled) { | ||
| 121 | auto left_motion = GetMotionInput(input, motion_status); | ||
| 122 | // Rotate motion axis to the correct direction | ||
| 123 | left_motion.accel_y = -left_motion.accel_y; | ||
| 124 | left_motion.accel_z = -left_motion.accel_z; | ||
| 125 | left_motion.gyro_x = -left_motion.gyro_x; | ||
| 126 | callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input, | ||
| 131 | const MotionStatus& motion_status) { | ||
| 132 | static constexpr std::array<Joycon::PadButton, 11> right_buttons{ | ||
| 133 | Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B, | ||
| 134 | Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR, | ||
| 135 | Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus, | ||
| 136 | Joycon::PadButton::Home, Joycon::PadButton::StickR, | ||
| 137 | }; | ||
| 138 | |||
| 139 | const u32 raw_button = | ||
| 140 | static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16)); | ||
| 141 | for (std::size_t i = 0; i < right_buttons.size(); ++i) { | ||
| 142 | const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0; | ||
| 143 | const int button = static_cast<int>(right_buttons[i]); | ||
| 144 | callbacks.on_button_data(button, button_status); | ||
| 145 | } | ||
| 146 | |||
| 147 | const u16 raw_right_axis_x = | ||
| 148 | static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8)); | ||
| 149 | const u16 raw_right_axis_y = | ||
| 150 | static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4)); | ||
| 151 | const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x); | ||
| 152 | const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y); | ||
| 153 | callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x); | ||
| 154 | callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y); | ||
| 155 | |||
| 156 | if (motion_status.is_enabled) { | ||
| 157 | auto right_motion = GetMotionInput(input, motion_status); | ||
| 158 | // Rotate motion axis to the correct direction | ||
| 159 | right_motion.accel_x = -right_motion.accel_x; | ||
| 160 | right_motion.accel_y = -right_motion.accel_y; | ||
| 161 | right_motion.gyro_z = -right_motion.gyro_z; | ||
| 162 | callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input, | ||
| 167 | const MotionStatus& motion_status) { | ||
| 168 | static constexpr std::array<Joycon::PadButton, 18> pro_buttons{ | ||
| 169 | Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right, | ||
| 170 | Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL, | ||
| 171 | Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y, | ||
| 172 | Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A, | ||
| 173 | Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus, | ||
| 174 | Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR, | ||
| 175 | }; | ||
| 176 | |||
| 177 | const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) | | ||
| 178 | (input.button_input[1] << 16)); | ||
| 179 | for (std::size_t i = 0; i < pro_buttons.size(); ++i) { | ||
| 180 | const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0; | ||
| 181 | const int button = static_cast<int>(pro_buttons[i]); | ||
| 182 | callbacks.on_button_data(button, button_status); | ||
| 183 | } | ||
| 184 | |||
| 185 | const u16 raw_left_axis_x = | ||
| 186 | static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8)); | ||
| 187 | const u16 raw_left_axis_y = | ||
| 188 | static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4)); | ||
| 189 | const u16 raw_right_axis_x = | ||
| 190 | static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8)); | ||
| 191 | const u16 raw_right_axis_y = | ||
| 192 | static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4)); | ||
| 193 | |||
| 194 | const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x); | ||
| 195 | const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y); | ||
| 196 | const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x); | ||
| 197 | const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y); | ||
| 198 | callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x); | ||
| 199 | callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y); | ||
| 200 | callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x); | ||
| 201 | callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y); | ||
| 202 | |||
| 203 | if (motion_status.is_enabled) { | ||
| 204 | auto pro_motion = GetMotionInput(input, motion_status); | ||
| 205 | pro_motion.gyro_x = -pro_motion.gyro_x; | ||
| 206 | pro_motion.accel_y = -pro_motion.accel_y; | ||
| 207 | pro_motion.accel_z = -pro_motion.accel_z; | ||
| 208 | callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion); | ||
| 209 | callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) { | ||
| 214 | static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{ | ||
| 215 | Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, | ||
| 216 | Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, | ||
| 217 | Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, | ||
| 218 | Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, | ||
| 219 | Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture, | ||
| 220 | Joycon::PasivePadButton::StickL, | ||
| 221 | }; | ||
| 222 | |||
| 223 | for (auto left_button : left_buttons) { | ||
| 224 | const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0; | ||
| 225 | const int button = static_cast<int>(left_button); | ||
| 226 | callbacks.on_button_data(button, button_status); | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) { | ||
| 231 | static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{ | ||
| 232 | Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, | ||
| 233 | Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, | ||
| 234 | Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, | ||
| 235 | Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, | ||
| 236 | Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home, | ||
| 237 | Joycon::PasivePadButton::StickR, | ||
| 238 | }; | ||
| 239 | |||
| 240 | for (auto right_button : right_buttons) { | ||
| 241 | const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0; | ||
| 242 | const int button = static_cast<int>(right_button); | ||
| 243 | callbacks.on_button_data(button, button_status); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) { | ||
| 248 | static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{ | ||
| 249 | Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, | ||
| 250 | Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, | ||
| 251 | Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, | ||
| 252 | Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, | ||
| 253 | Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus, | ||
| 254 | Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home, | ||
| 255 | Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR, | ||
| 256 | }; | ||
| 257 | |||
| 258 | for (auto pro_button : pro_buttons) { | ||
| 259 | const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0; | ||
| 260 | const int button = static_cast<int>(pro_button); | ||
| 261 | callbacks.on_button_data(button, button_status); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const { | ||
| 266 | const f32 value = static_cast<f32>(raw_value - calibration.center); | ||
| 267 | if (value > 0.0f) { | ||
| 268 | return value / calibration.max; | ||
| 269 | } | ||
| 270 | return value / calibration.min; | ||
| 271 | } | ||
| 272 | |||
| 273 | f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal, | ||
| 274 | AccelerometerSensitivity sensitivity) const { | ||
| 275 | const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4; | ||
| 276 | switch (sensitivity) { | ||
| 277 | case Joycon::AccelerometerSensitivity::G2: | ||
| 278 | return value / 4.0f; | ||
| 279 | case Joycon::AccelerometerSensitivity::G4: | ||
| 280 | return value / 2.0f; | ||
| 281 | case Joycon::AccelerometerSensitivity::G8: | ||
| 282 | return value; | ||
| 283 | case Joycon::AccelerometerSensitivity::G16: | ||
| 284 | return value * 2.0f; | ||
| 285 | } | ||
| 286 | return value; | ||
| 287 | } | ||
| 288 | |||
| 289 | f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal, | ||
| 290 | GyroSensitivity sensitivity) const { | ||
| 291 | const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f; | ||
| 292 | switch (sensitivity) { | ||
| 293 | case Joycon::GyroSensitivity::DPS250: | ||
| 294 | return value / 8.0f; | ||
| 295 | case Joycon::GyroSensitivity::DPS500: | ||
| 296 | return value / 4.0f; | ||
| 297 | case Joycon::GyroSensitivity::DPS1000: | ||
| 298 | return value / 2.0f; | ||
| 299 | case Joycon::GyroSensitivity::DPS2000: | ||
| 300 | return value; | ||
| 301 | } | ||
| 302 | return value; | ||
| 303 | } | ||
| 304 | |||
| 305 | s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis, | ||
| 306 | const InputReportActive& input) const { | ||
| 307 | return input.motion_input[(sensor * 3) + axis]; | ||
| 308 | } | ||
| 309 | |||
| 310 | MotionData JoyconPoller::GetMotionInput(const InputReportActive& input, | ||
| 311 | const MotionStatus& motion_status) const { | ||
| 312 | MotionData motion{}; | ||
| 313 | const auto& accel_cal = motion_calibration.accelerometer; | ||
| 314 | const auto& gyro_cal = motion_calibration.gyro; | ||
| 315 | const s16 raw_accel_x = input.motion_input[1]; | ||
| 316 | const s16 raw_accel_y = input.motion_input[0]; | ||
| 317 | const s16 raw_accel_z = input.motion_input[2]; | ||
| 318 | const s16 raw_gyro_x = input.motion_input[4]; | ||
| 319 | const s16 raw_gyro_y = input.motion_input[3]; | ||
| 320 | const s16 raw_gyro_z = input.motion_input[5]; | ||
| 321 | |||
| 322 | motion.delta_timestamp = motion_status.delta_time; | ||
| 323 | motion.accel_x = | ||
| 324 | GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity); | ||
| 325 | motion.accel_y = | ||
| 326 | GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity); | ||
| 327 | motion.accel_z = | ||
| 328 | GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity); | ||
| 329 | motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity); | ||
| 330 | motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity); | ||
| 331 | motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity); | ||
| 332 | |||
| 333 | // TODO(German77): Return all three samples data | ||
| 334 | return motion; | ||
| 335 | } | ||
| 336 | |||
| 337 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h new file mode 100644 index 000000000..354d41dad --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/poller.h | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <functional> | ||
| 12 | #include <span> | ||
| 13 | |||
| 14 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 15 | |||
| 16 | namespace InputCommon::Joycon { | ||
| 17 | |||
| 18 | // Handles input packages and triggers the corresponding input events | ||
| 19 | class JoyconPoller { | ||
| 20 | public: | ||
| 21 | JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_, | ||
| 22 | JoyStickCalibration right_stick_calibration_, | ||
| 23 | MotionCalibration motion_calibration_); | ||
| 24 | |||
| 25 | void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_); | ||
| 26 | |||
| 27 | /// Handles data from passive packages | ||
| 28 | void ReadPassiveMode(std::span<u8> buffer); | ||
| 29 | |||
| 30 | /// Handles data from active packages | ||
| 31 | void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status, | ||
| 32 | const RingStatus& ring_status); | ||
| 33 | |||
| 34 | /// Handles data from nfc or ir packages | ||
| 35 | void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status); | ||
| 36 | |||
| 37 | void UpdateColor(const Color& color); | ||
| 38 | void UpdateRing(s16 value, const RingStatus& ring_status); | ||
| 39 | void UpdateAmiibo(const std::vector<u8>& amiibo_data); | ||
| 40 | void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format); | ||
| 41 | |||
| 42 | private: | ||
| 43 | void UpdateActiveLeftPadInput(const InputReportActive& input, | ||
| 44 | const MotionStatus& motion_status); | ||
| 45 | void UpdateActiveRightPadInput(const InputReportActive& input, | ||
| 46 | const MotionStatus& motion_status); | ||
| 47 | void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status); | ||
| 48 | |||
| 49 | void UpdatePasiveLeftPadInput(const InputReportPassive& buffer); | ||
| 50 | void UpdatePasiveRightPadInput(const InputReportPassive& buffer); | ||
| 51 | void UpdatePasiveProPadInput(const InputReportPassive& buffer); | ||
| 52 | |||
| 53 | /// Returns a calibrated joystick axis from raw axis data | ||
| 54 | f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const; | ||
| 55 | |||
| 56 | /// Returns a calibrated accelerometer axis from raw motion data | ||
| 57 | f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal, | ||
| 58 | AccelerometerSensitivity sensitivity) const; | ||
| 59 | |||
| 60 | /// Returns a calibrated gyro axis from raw motion data | ||
| 61 | f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal, | ||
| 62 | GyroSensitivity sensitivity) const; | ||
| 63 | |||
| 64 | /// Returns a raw motion value from a buffer | ||
| 65 | s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const; | ||
| 66 | |||
| 67 | /// Returns motion data from a buffer | ||
| 68 | MotionData GetMotionInput(const InputReportActive& input, | ||
| 69 | const MotionStatus& motion_status) const; | ||
| 70 | |||
| 71 | ControllerType device_type{}; | ||
| 72 | |||
| 73 | // Device calibration | ||
| 74 | JoyStickCalibration left_stick_calibration{}; | ||
| 75 | JoyStickCalibration right_stick_calibration{}; | ||
| 76 | MotionCalibration motion_calibration{}; | ||
| 77 | |||
| 78 | Joycon::JoyconCallbacks callbacks{}; | ||
| 79 | }; | ||
| 80 | |||
| 81 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp new file mode 100644 index 000000000..190cef812 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/logging/log.h" | ||
| 5 | #include "input_common/helpers/joycon_protocol/ringcon.h" | ||
| 6 | |||
| 7 | namespace InputCommon::Joycon { | ||
| 8 | |||
| 9 | RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle) | ||
| 10 | : JoyconCommonProtocol(std::move(handle)) {} | ||
| 11 | |||
| 12 | DriverResult RingConProtocol::EnableRingCon() { | ||
| 13 | LOG_DEBUG(Input, "Enable Ringcon"); | ||
| 14 | ScopedSetBlocking sb(this); | ||
| 15 | DriverResult result{DriverResult::Success}; | ||
| 16 | |||
| 17 | if (result == DriverResult::Success) { | ||
| 18 | result = SetReportMode(ReportMode::STANDARD_FULL_60HZ); | ||
| 19 | } | ||
| 20 | if (result == DriverResult::Success) { | ||
| 21 | result = EnableMCU(true); | ||
| 22 | } | ||
| 23 | if (result == DriverResult::Success) { | ||
| 24 | const MCUConfig config{ | ||
| 25 | .command = MCUCommand::ConfigureMCU, | ||
| 26 | .sub_command = MCUSubCommand::SetDeviceMode, | ||
| 27 | .mode = MCUMode::Standby, | ||
| 28 | .crc = {}, | ||
| 29 | }; | ||
| 30 | result = ConfigureMCU(config); | ||
| 31 | } | ||
| 32 | |||
| 33 | return result; | ||
| 34 | } | ||
| 35 | |||
| 36 | DriverResult RingConProtocol::DisableRingCon() { | ||
| 37 | LOG_DEBUG(Input, "Disable RingCon"); | ||
| 38 | ScopedSetBlocking sb(this); | ||
| 39 | DriverResult result{DriverResult::Success}; | ||
| 40 | |||
| 41 | if (result == DriverResult::Success) { | ||
| 42 | result = EnableMCU(false); | ||
| 43 | } | ||
| 44 | |||
| 45 | is_enabled = false; | ||
| 46 | |||
| 47 | return result; | ||
| 48 | } | ||
| 49 | |||
| 50 | DriverResult RingConProtocol::StartRingconPolling() { | ||
| 51 | LOG_DEBUG(Input, "Enable Ringcon"); | ||
| 52 | ScopedSetBlocking sb(this); | ||
| 53 | DriverResult result{DriverResult::Success}; | ||
| 54 | bool is_connected = false; | ||
| 55 | |||
| 56 | if (result == DriverResult::Success) { | ||
| 57 | result = IsRingConnected(is_connected); | ||
| 58 | } | ||
| 59 | if (result == DriverResult::Success && is_connected) { | ||
| 60 | LOG_INFO(Input, "Ringcon detected"); | ||
| 61 | result = ConfigureRing(); | ||
| 62 | } | ||
| 63 | if (result == DriverResult::Success) { | ||
| 64 | is_enabled = true; | ||
| 65 | } | ||
| 66 | |||
| 67 | return result; | ||
| 68 | } | ||
| 69 | |||
| 70 | DriverResult RingConProtocol::IsRingConnected(bool& is_connected) { | ||
| 71 | LOG_DEBUG(Input, "IsRingConnected"); | ||
| 72 | constexpr std::size_t max_tries = 28; | ||
| 73 | SubCommandResponse output{}; | ||
| 74 | std::size_t tries = 0; | ||
| 75 | is_connected = false; | ||
| 76 | |||
| 77 | do { | ||
| 78 | const auto result = SendSubCommand(SubCommand::GET_EXTERNAL_DEVICE_INFO, {}, output); | ||
| 79 | |||
| 80 | if (result != DriverResult::Success) { | ||
| 81 | return result; | ||
| 82 | } | ||
| 83 | |||
| 84 | if (tries++ >= max_tries) { | ||
| 85 | return DriverResult::NoDeviceDetected; | ||
| 86 | } | ||
| 87 | } while (output.external_device_id != ExternalDeviceId::RingController); | ||
| 88 | |||
| 89 | is_connected = true; | ||
| 90 | return DriverResult::Success; | ||
| 91 | } | ||
| 92 | |||
| 93 | DriverResult RingConProtocol::ConfigureRing() { | ||
| 94 | LOG_DEBUG(Input, "ConfigureRing"); | ||
| 95 | |||
| 96 | static constexpr std::array<u8, 37> ring_config{ | ||
| 97 | 0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36, | ||
| 98 | 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00, | ||
| 99 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36}; | ||
| 100 | |||
| 101 | const DriverResult result = SendSubCommand(SubCommand::SET_EXTERNAL_FORMAT_CONFIG, ring_config); | ||
| 102 | |||
| 103 | if (result != DriverResult::Success) { | ||
| 104 | return result; | ||
| 105 | } | ||
| 106 | |||
| 107 | static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02}; | ||
| 108 | return SendSubCommand(SubCommand::ENABLE_EXTERNAL_POLLING, ringcon_data); | ||
| 109 | } | ||
| 110 | |||
| 111 | bool RingConProtocol::IsEnabled() const { | ||
| 112 | return is_enabled; | ||
| 113 | } | ||
| 114 | |||
| 115 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h new file mode 100644 index 000000000..6e858f3fc --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/ringcon.h | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <vector> | ||
| 12 | |||
| 13 | #include "input_common/helpers/joycon_protocol/common_protocol.h" | ||
| 14 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 15 | |||
| 16 | namespace InputCommon::Joycon { | ||
| 17 | |||
| 18 | class RingConProtocol final : private JoyconCommonProtocol { | ||
| 19 | public: | ||
| 20 | explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle); | ||
| 21 | |||
| 22 | DriverResult EnableRingCon(); | ||
| 23 | |||
| 24 | DriverResult DisableRingCon(); | ||
| 25 | |||
| 26 | DriverResult StartRingconPolling(); | ||
| 27 | |||
| 28 | bool IsEnabled() const; | ||
| 29 | |||
| 30 | private: | ||
| 31 | DriverResult IsRingConnected(bool& is_connected); | ||
| 32 | |||
| 33 | DriverResult ConfigureRing(); | ||
| 34 | |||
| 35 | bool is_enabled{}; | ||
| 36 | }; | ||
| 37 | |||
| 38 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp new file mode 100644 index 000000000..63b60c946 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/rumble.cpp | |||
| @@ -0,0 +1,299 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cmath> | ||
| 6 | |||
| 7 | #include "common/logging/log.h" | ||
| 8 | #include "input_common/helpers/joycon_protocol/rumble.h" | ||
| 9 | |||
| 10 | namespace InputCommon::Joycon { | ||
| 11 | |||
| 12 | RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle) | ||
| 13 | : JoyconCommonProtocol(std::move(handle)) {} | ||
| 14 | |||
| 15 | DriverResult RumbleProtocol::EnableRumble(bool is_enabled) { | ||
| 16 | LOG_DEBUG(Input, "Enable Rumble"); | ||
| 17 | ScopedSetBlocking sb(this); | ||
| 18 | const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)}; | ||
| 19 | return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer); | ||
| 20 | } | ||
| 21 | |||
| 22 | DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) { | ||
| 23 | std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{}; | ||
| 24 | |||
| 25 | if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) { | ||
| 26 | return SendVibrationReport(DefaultVibrationBuffer); | ||
| 27 | } | ||
| 28 | |||
| 29 | // Protect joycons from damage from strong vibrations | ||
| 30 | const f32 clamp_amplitude = | ||
| 31 | 1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude); | ||
| 32 | |||
| 33 | const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency); | ||
| 34 | const u8 encoded_high_amplitude = | ||
| 35 | EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude); | ||
| 36 | const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency); | ||
| 37 | const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude); | ||
| 38 | |||
| 39 | buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF); | ||
| 40 | buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01)); | ||
| 41 | buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80)); | ||
| 42 | buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF); | ||
| 43 | |||
| 44 | // Duplicate rumble for now | ||
| 45 | buffer[4] = buffer[0]; | ||
| 46 | buffer[5] = buffer[1]; | ||
| 47 | buffer[6] = buffer[2]; | ||
| 48 | buffer[7] = buffer[3]; | ||
| 49 | |||
| 50 | return SendVibrationReport(buffer); | ||
| 51 | } | ||
| 52 | |||
| 53 | u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const { | ||
| 54 | const u8 new_frequency = | ||
| 55 | static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f)); | ||
| 56 | return static_cast<u16>((new_frequency - 0x60) * 4); | ||
| 57 | } | ||
| 58 | |||
| 59 | u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const { | ||
| 60 | const u8 new_frequency = | ||
| 61 | static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f)); | ||
| 62 | return static_cast<u8>(new_frequency - 0x40); | ||
| 63 | } | ||
| 64 | |||
| 65 | u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const { | ||
| 66 | // More information about these values can be found here: | ||
| 67 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md | ||
| 68 | |||
| 69 | static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{ | ||
| 70 | std::pair<f32, int>{0.0f, 0x0}, | ||
| 71 | {0.01f, 0x2}, | ||
| 72 | {0.012f, 0x4}, | ||
| 73 | {0.014f, 0x6}, | ||
| 74 | {0.017f, 0x8}, | ||
| 75 | {0.02f, 0x0a}, | ||
| 76 | {0.024f, 0x0c}, | ||
| 77 | {0.028f, 0x0e}, | ||
| 78 | {0.033f, 0x10}, | ||
| 79 | {0.04f, 0x12}, | ||
| 80 | {0.047f, 0x14}, | ||
| 81 | {0.056f, 0x16}, | ||
| 82 | {0.067f, 0x18}, | ||
| 83 | {0.08f, 0x1a}, | ||
| 84 | {0.095f, 0x1c}, | ||
| 85 | {0.112f, 0x1e}, | ||
| 86 | {0.117f, 0x20}, | ||
| 87 | {0.123f, 0x22}, | ||
| 88 | {0.128f, 0x24}, | ||
| 89 | {0.134f, 0x26}, | ||
| 90 | {0.14f, 0x28}, | ||
| 91 | {0.146f, 0x2a}, | ||
| 92 | {0.152f, 0x2c}, | ||
| 93 | {0.159f, 0x2e}, | ||
| 94 | {0.166f, 0x30}, | ||
| 95 | {0.173f, 0x32}, | ||
| 96 | {0.181f, 0x34}, | ||
| 97 | {0.189f, 0x36}, | ||
| 98 | {0.198f, 0x38}, | ||
| 99 | {0.206f, 0x3a}, | ||
| 100 | {0.215f, 0x3c}, | ||
| 101 | {0.225f, 0x3e}, | ||
| 102 | {0.23f, 0x40}, | ||
| 103 | {0.235f, 0x42}, | ||
| 104 | {0.24f, 0x44}, | ||
| 105 | {0.245f, 0x46}, | ||
| 106 | {0.251f, 0x48}, | ||
| 107 | {0.256f, 0x4a}, | ||
| 108 | {0.262f, 0x4c}, | ||
| 109 | {0.268f, 0x4e}, | ||
| 110 | {0.273f, 0x50}, | ||
| 111 | {0.279f, 0x52}, | ||
| 112 | {0.286f, 0x54}, | ||
| 113 | {0.292f, 0x56}, | ||
| 114 | {0.298f, 0x58}, | ||
| 115 | {0.305f, 0x5a}, | ||
| 116 | {0.311f, 0x5c}, | ||
| 117 | {0.318f, 0x5e}, | ||
| 118 | {0.325f, 0x60}, | ||
| 119 | {0.332f, 0x62}, | ||
| 120 | {0.34f, 0x64}, | ||
| 121 | {0.347f, 0x66}, | ||
| 122 | {0.355f, 0x68}, | ||
| 123 | {0.362f, 0x6a}, | ||
| 124 | {0.37f, 0x6c}, | ||
| 125 | {0.378f, 0x6e}, | ||
| 126 | {0.387f, 0x70}, | ||
| 127 | {0.395f, 0x72}, | ||
| 128 | {0.404f, 0x74}, | ||
| 129 | {0.413f, 0x76}, | ||
| 130 | {0.422f, 0x78}, | ||
| 131 | {0.431f, 0x7a}, | ||
| 132 | {0.44f, 0x7c}, | ||
| 133 | {0.45f, 0x7e}, | ||
| 134 | {0.46f, 0x80}, | ||
| 135 | {0.47f, 0x82}, | ||
| 136 | {0.48f, 0x84}, | ||
| 137 | {0.491f, 0x86}, | ||
| 138 | {0.501f, 0x88}, | ||
| 139 | {0.512f, 0x8a}, | ||
| 140 | {0.524f, 0x8c}, | ||
| 141 | {0.535f, 0x8e}, | ||
| 142 | {0.547f, 0x90}, | ||
| 143 | {0.559f, 0x92}, | ||
| 144 | {0.571f, 0x94}, | ||
| 145 | {0.584f, 0x96}, | ||
| 146 | {0.596f, 0x98}, | ||
| 147 | {0.609f, 0x9a}, | ||
| 148 | {0.623f, 0x9c}, | ||
| 149 | {0.636f, 0x9e}, | ||
| 150 | {0.65f, 0xa0}, | ||
| 151 | {0.665f, 0xa2}, | ||
| 152 | {0.679f, 0xa4}, | ||
| 153 | {0.694f, 0xa6}, | ||
| 154 | {0.709f, 0xa8}, | ||
| 155 | {0.725f, 0xaa}, | ||
| 156 | {0.741f, 0xac}, | ||
| 157 | {0.757f, 0xae}, | ||
| 158 | {0.773f, 0xb0}, | ||
| 159 | {0.79f, 0xb2}, | ||
| 160 | {0.808f, 0xb4}, | ||
| 161 | {0.825f, 0xb6}, | ||
| 162 | {0.843f, 0xb8}, | ||
| 163 | {0.862f, 0xba}, | ||
| 164 | {0.881f, 0xbc}, | ||
| 165 | {0.9f, 0xbe}, | ||
| 166 | {0.92f, 0xc0}, | ||
| 167 | {0.94f, 0xc2}, | ||
| 168 | {0.96f, 0xc4}, | ||
| 169 | {0.981f, 0xc6}, | ||
| 170 | {1.003f, 0xc8}, | ||
| 171 | }; | ||
| 172 | |||
| 173 | for (const auto& [amplitude_value, code] : high_fequency_amplitude) { | ||
| 174 | if (amplitude <= amplitude_value) { | ||
| 175 | return static_cast<u8>(code); | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second); | ||
| 180 | } | ||
| 181 | |||
| 182 | u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const { | ||
| 183 | // More information about these values can be found here: | ||
| 184 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md | ||
| 185 | |||
| 186 | static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{ | ||
| 187 | std::pair<f32, int>{0.0f, 0x0040}, | ||
| 188 | {0.01f, 0x8040}, | ||
| 189 | {0.012f, 0x0041}, | ||
| 190 | {0.014f, 0x8041}, | ||
| 191 | {0.017f, 0x0042}, | ||
| 192 | {0.02f, 0x8042}, | ||
| 193 | {0.024f, 0x0043}, | ||
| 194 | {0.028f, 0x8043}, | ||
| 195 | {0.033f, 0x0044}, | ||
| 196 | {0.04f, 0x8044}, | ||
| 197 | {0.047f, 0x0045}, | ||
| 198 | {0.056f, 0x8045}, | ||
| 199 | {0.067f, 0x0046}, | ||
| 200 | {0.08f, 0x8046}, | ||
| 201 | {0.095f, 0x0047}, | ||
| 202 | {0.112f, 0x8047}, | ||
| 203 | {0.117f, 0x0048}, | ||
| 204 | {0.123f, 0x8048}, | ||
| 205 | {0.128f, 0x0049}, | ||
| 206 | {0.134f, 0x8049}, | ||
| 207 | {0.14f, 0x004a}, | ||
| 208 | {0.146f, 0x804a}, | ||
| 209 | {0.152f, 0x004b}, | ||
| 210 | {0.159f, 0x804b}, | ||
| 211 | {0.166f, 0x004c}, | ||
| 212 | {0.173f, 0x804c}, | ||
| 213 | {0.181f, 0x004d}, | ||
| 214 | {0.189f, 0x804d}, | ||
| 215 | {0.198f, 0x004e}, | ||
| 216 | {0.206f, 0x804e}, | ||
| 217 | {0.215f, 0x004f}, | ||
| 218 | {0.225f, 0x804f}, | ||
| 219 | {0.23f, 0x0050}, | ||
| 220 | {0.235f, 0x8050}, | ||
| 221 | {0.24f, 0x0051}, | ||
| 222 | {0.245f, 0x8051}, | ||
| 223 | {0.251f, 0x0052}, | ||
| 224 | {0.256f, 0x8052}, | ||
| 225 | {0.262f, 0x0053}, | ||
| 226 | {0.268f, 0x8053}, | ||
| 227 | {0.273f, 0x0054}, | ||
| 228 | {0.279f, 0x8054}, | ||
| 229 | {0.286f, 0x0055}, | ||
| 230 | {0.292f, 0x8055}, | ||
| 231 | {0.298f, 0x0056}, | ||
| 232 | {0.305f, 0x8056}, | ||
| 233 | {0.311f, 0x0057}, | ||
| 234 | {0.318f, 0x8057}, | ||
| 235 | {0.325f, 0x0058}, | ||
| 236 | {0.332f, 0x8058}, | ||
| 237 | {0.34f, 0x0059}, | ||
| 238 | {0.347f, 0x8059}, | ||
| 239 | {0.355f, 0x005a}, | ||
| 240 | {0.362f, 0x805a}, | ||
| 241 | {0.37f, 0x005b}, | ||
| 242 | {0.378f, 0x805b}, | ||
| 243 | {0.387f, 0x005c}, | ||
| 244 | {0.395f, 0x805c}, | ||
| 245 | {0.404f, 0x005d}, | ||
| 246 | {0.413f, 0x805d}, | ||
| 247 | {0.422f, 0x005e}, | ||
| 248 | {0.431f, 0x805e}, | ||
| 249 | {0.44f, 0x005f}, | ||
| 250 | {0.45f, 0x805f}, | ||
| 251 | {0.46f, 0x0060}, | ||
| 252 | {0.47f, 0x8060}, | ||
| 253 | {0.48f, 0x0061}, | ||
| 254 | {0.491f, 0x8061}, | ||
| 255 | {0.501f, 0x0062}, | ||
| 256 | {0.512f, 0x8062}, | ||
| 257 | {0.524f, 0x0063}, | ||
| 258 | {0.535f, 0x8063}, | ||
| 259 | {0.547f, 0x0064}, | ||
| 260 | {0.559f, 0x8064}, | ||
| 261 | {0.571f, 0x0065}, | ||
| 262 | {0.584f, 0x8065}, | ||
| 263 | {0.596f, 0x0066}, | ||
| 264 | {0.609f, 0x8066}, | ||
| 265 | {0.623f, 0x0067}, | ||
| 266 | {0.636f, 0x8067}, | ||
| 267 | {0.65f, 0x0068}, | ||
| 268 | {0.665f, 0x8068}, | ||
| 269 | {0.679f, 0x0069}, | ||
| 270 | {0.694f, 0x8069}, | ||
| 271 | {0.709f, 0x006a}, | ||
| 272 | {0.725f, 0x806a}, | ||
| 273 | {0.741f, 0x006b}, | ||
| 274 | {0.757f, 0x806b}, | ||
| 275 | {0.773f, 0x006c}, | ||
| 276 | {0.79f, 0x806c}, | ||
| 277 | {0.808f, 0x006d}, | ||
| 278 | {0.825f, 0x806d}, | ||
| 279 | {0.843f, 0x006e}, | ||
| 280 | {0.862f, 0x806e}, | ||
| 281 | {0.881f, 0x006f}, | ||
| 282 | {0.9f, 0x806f}, | ||
| 283 | {0.92f, 0x0070}, | ||
| 284 | {0.94f, 0x8070}, | ||
| 285 | {0.96f, 0x0071}, | ||
| 286 | {0.981f, 0x8071}, | ||
| 287 | {1.003f, 0x0072}, | ||
| 288 | }; | ||
| 289 | |||
| 290 | for (const auto& [amplitude_value, code] : high_fequency_amplitude) { | ||
| 291 | if (amplitude <= amplitude_value) { | ||
| 292 | return static_cast<u16>(code); | ||
| 293 | } | ||
| 294 | } | ||
| 295 | |||
| 296 | return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second); | ||
| 297 | } | ||
| 298 | |||
| 299 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h new file mode 100644 index 000000000..6c12b7925 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/rumble.h | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse | ||
| 5 | // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c | ||
| 6 | // https://github.com/CTCaer/jc_toolkit | ||
| 7 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <vector> | ||
| 12 | |||
| 13 | #include "input_common/helpers/joycon_protocol/common_protocol.h" | ||
| 14 | #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||
| 15 | |||
| 16 | namespace InputCommon::Joycon { | ||
| 17 | |||
| 18 | class RumbleProtocol final : private JoyconCommonProtocol { | ||
| 19 | public: | ||
| 20 | explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle); | ||
| 21 | |||
| 22 | DriverResult EnableRumble(bool is_enabled); | ||
| 23 | |||
| 24 | DriverResult SendVibration(const VibrationValue& vibration); | ||
| 25 | |||
| 26 | private: | ||
| 27 | u16 EncodeHighFrequency(f32 frequency) const; | ||
| 28 | u8 EncodeLowFrequency(f32 frequency) const; | ||
| 29 | u8 EncodeHighAmplitude(f32 amplitude) const; | ||
| 30 | u16 EncodeLowAmplitude(f32 amplitude) const; | ||
| 31 | }; | ||
| 32 | |||
| 33 | } // namespace InputCommon::Joycon | ||
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp index 82aa6ac2f..a6be6dac1 100644 --- a/src/input_common/helpers/stick_from_buttons.cpp +++ b/src/input_common/helpers/stick_from_buttons.cpp | |||
| @@ -11,13 +11,21 @@ namespace InputCommon { | |||
| 11 | 11 | ||
| 12 | class Stick final : public Common::Input::InputDevice { | 12 | class Stick final : public Common::Input::InputDevice { |
| 13 | public: | 13 | public: |
| 14 | // Some games such as EARTH DEFENSE FORCE: WORLD BROTHERS | ||
| 15 | // do not play nicely with the theoretical maximum range. | ||
| 16 | // Using a value one lower from the maximum emulates real stick behavior. | ||
| 17 | static constexpr float MAX_RANGE = 32766.0f / 32767.0f; | ||
| 18 | static constexpr float TAU = Common::PI * 2.0f; | ||
| 19 | // Use wider angle to ease the transition. | ||
| 20 | static constexpr float APERTURE = TAU * 0.15f; | ||
| 21 | |||
| 14 | using Button = std::unique_ptr<Common::Input::InputDevice>; | 22 | using Button = std::unique_ptr<Common::Input::InputDevice>; |
| 15 | 23 | ||
| 16 | Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, | 24 | Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_, |
| 17 | float modifier_scale_, float modifier_angle_) | 25 | float modifier_scale_, float modifier_angle_) |
| 18 | : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), | 26 | : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), |
| 19 | right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), | 27 | right(std::move(right_)), modifier(std::move(modifier_)), updater(std::move(updater_)), |
| 20 | modifier_angle(modifier_angle_) { | 28 | modifier_scale(modifier_scale_), modifier_angle(modifier_angle_) { |
| 21 | up->SetCallback({ | 29 | up->SetCallback({ |
| 22 | .on_change = | 30 | .on_change = |
| 23 | [this](const Common::Input::CallbackStatus& callback_) { | 31 | [this](const Common::Input::CallbackStatus& callback_) { |
| @@ -48,35 +56,31 @@ public: | |||
| 48 | UpdateModButtonStatus(callback_); | 56 | UpdateModButtonStatus(callback_); |
| 49 | }, | 57 | }, |
| 50 | }); | 58 | }); |
| 59 | updater->SetCallback({ | ||
| 60 | .on_change = [this](const Common::Input::CallbackStatus& callback_) { SoftUpdate(); }, | ||
| 61 | }); | ||
| 51 | last_x_axis_value = 0.0f; | 62 | last_x_axis_value = 0.0f; |
| 52 | last_y_axis_value = 0.0f; | 63 | last_y_axis_value = 0.0f; |
| 53 | } | 64 | } |
| 54 | 65 | ||
| 55 | bool IsAngleGreater(float old_angle, float new_angle) const { | 66 | bool IsAngleGreater(float old_angle, float new_angle) const { |
| 56 | constexpr float TAU = Common::PI * 2.0f; | 67 | const float top_limit = new_angle + APERTURE; |
| 57 | // Use wider angle to ease the transition. | ||
| 58 | constexpr float aperture = TAU * 0.15f; | ||
| 59 | const float top_limit = new_angle + aperture; | ||
| 60 | return (old_angle > new_angle && old_angle <= top_limit) || | 68 | return (old_angle > new_angle && old_angle <= top_limit) || |
| 61 | (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); | 69 | (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); |
| 62 | } | 70 | } |
| 63 | 71 | ||
| 64 | bool IsAngleSmaller(float old_angle, float new_angle) const { | 72 | bool IsAngleSmaller(float old_angle, float new_angle) const { |
| 65 | constexpr float TAU = Common::PI * 2.0f; | 73 | const float bottom_limit = new_angle - APERTURE; |
| 66 | // Use wider angle to ease the transition. | ||
| 67 | constexpr float aperture = TAU * 0.15f; | ||
| 68 | const float bottom_limit = new_angle - aperture; | ||
| 69 | return (old_angle >= bottom_limit && old_angle < new_angle) || | 74 | return (old_angle >= bottom_limit && old_angle < new_angle) || |
| 70 | (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); | 75 | (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); |
| 71 | } | 76 | } |
| 72 | 77 | ||
| 73 | float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const { | 78 | float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const { |
| 74 | constexpr float TAU = Common::PI * 2.0f; | ||
| 75 | float new_angle = angle; | 79 | float new_angle = angle; |
| 76 | 80 | ||
| 77 | auto time_difference = static_cast<float>( | 81 | auto time_difference = static_cast<float>( |
| 78 | std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); | 82 | std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count()); |
| 79 | time_difference /= 1000.0f * 1000.0f; | 83 | time_difference /= 1000.0f; |
| 80 | if (time_difference > 0.5f) { | 84 | if (time_difference > 0.5f) { |
| 81 | time_difference = 0.5f; | 85 | time_difference = 0.5f; |
| 82 | } | 86 | } |
| @@ -193,8 +197,6 @@ public: | |||
| 193 | } | 197 | } |
| 194 | 198 | ||
| 195 | void UpdateStatus() { | 199 | void UpdateStatus() { |
| 196 | const float coef = modifier_status.value ? modifier_scale : 1.0f; | ||
| 197 | |||
| 198 | bool r = right_status; | 200 | bool r = right_status; |
| 199 | bool l = left_status; | 201 | bool l = left_status; |
| 200 | bool u = up_status; | 202 | bool u = up_status; |
| @@ -212,7 +214,7 @@ public: | |||
| 212 | 214 | ||
| 213 | // Move if a key is pressed | 215 | // Move if a key is pressed |
| 214 | if (r || l || u || d) { | 216 | if (r || l || u || d) { |
| 215 | amplitude = coef; | 217 | amplitude = modifier_status.value ? modifier_scale : MAX_RANGE; |
| 216 | } else { | 218 | } else { |
| 217 | amplitude = 0; | 219 | amplitude = 0; |
| 218 | } | 220 | } |
| @@ -248,7 +250,7 @@ public: | |||
| 248 | modifier->ForceUpdate(); | 250 | modifier->ForceUpdate(); |
| 249 | } | 251 | } |
| 250 | 252 | ||
| 251 | void SoftUpdate() override { | 253 | void SoftUpdate() { |
| 252 | Common::Input::CallbackStatus status{ | 254 | Common::Input::CallbackStatus status{ |
| 253 | .type = Common::Input::InputType::Stick, | 255 | .type = Common::Input::InputType::Stick, |
| 254 | .stick_status = GetStatus(), | 256 | .stick_status = GetStatus(), |
| @@ -266,30 +268,17 @@ public: | |||
| 266 | Common::Input::StickStatus status{}; | 268 | Common::Input::StickStatus status{}; |
| 267 | status.x.properties = properties; | 269 | status.x.properties = properties; |
| 268 | status.y.properties = properties; | 270 | status.y.properties = properties; |
| 271 | |||
| 269 | if (Settings::values.emulate_analog_keyboard) { | 272 | if (Settings::values.emulate_analog_keyboard) { |
| 270 | const auto now = std::chrono::steady_clock::now(); | 273 | const auto now = std::chrono::steady_clock::now(); |
| 271 | float angle_ = GetAngle(now); | 274 | const float angle_ = GetAngle(now); |
| 272 | status.x.raw_value = std::cos(angle_) * amplitude; | 275 | status.x.raw_value = std::cos(angle_) * amplitude; |
| 273 | status.y.raw_value = std::sin(angle_) * amplitude; | 276 | status.y.raw_value = std::sin(angle_) * amplitude; |
| 274 | return status; | 277 | return status; |
| 275 | } | 278 | } |
| 276 | constexpr float SQRT_HALF = 0.707106781f; | 279 | |
| 277 | int x = 0, y = 0; | 280 | status.x.raw_value = std::cos(goal_angle) * amplitude; |
| 278 | if (right_status) { | 281 | status.y.raw_value = std::sin(goal_angle) * amplitude; |
| 279 | ++x; | ||
| 280 | } | ||
| 281 | if (left_status) { | ||
| 282 | --x; | ||
| 283 | } | ||
| 284 | if (up_status) { | ||
| 285 | ++y; | ||
| 286 | } | ||
| 287 | if (down_status) { | ||
| 288 | --y; | ||
| 289 | } | ||
| 290 | const float coef = modifier_status.value ? modifier_scale : 1.0f; | ||
| 291 | status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF); | ||
| 292 | status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF); | ||
| 293 | return status; | 282 | return status; |
| 294 | } | 283 | } |
| 295 | 284 | ||
| @@ -308,6 +297,7 @@ private: | |||
| 308 | Button left; | 297 | Button left; |
| 309 | Button right; | 298 | Button right; |
| 310 | Button modifier; | 299 | Button modifier; |
| 300 | Button updater; | ||
| 311 | float modifier_scale{}; | 301 | float modifier_scale{}; |
| 312 | float modifier_angle{}; | 302 | float modifier_angle{}; |
| 313 | float angle{}; | 303 | float angle{}; |
| @@ -331,11 +321,12 @@ std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create( | |||
| 331 | auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine)); | 321 | auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine)); |
| 332 | auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine)); | 322 | auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine)); |
| 333 | auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine)); | 323 | auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine)); |
| 324 | auto updater = Common::Input::CreateInputDeviceFromString("engine:updater,button:0"); | ||
| 334 | auto modifier_scale = params.Get("modifier_scale", 0.5f); | 325 | auto modifier_scale = params.Get("modifier_scale", 0.5f); |
| 335 | auto modifier_angle = params.Get("modifier_angle", 5.5f); | 326 | auto modifier_angle = params.Get("modifier_angle", 5.5f); |
| 336 | return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left), | 327 | return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left), |
| 337 | std::move(right), std::move(modifier), modifier_scale, | 328 | std::move(right), std::move(modifier), std::move(updater), |
| 338 | modifier_angle); | 329 | modifier_scale, modifier_angle); |
| 339 | } | 330 | } |
| 340 | 331 | ||
| 341 | } // namespace InputCommon | 332 | } // namespace InputCommon |
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp index 61cfd0911..91aa96aa7 100644 --- a/src/input_common/input_engine.cpp +++ b/src/input_common/input_engine.cpp | |||
| @@ -79,6 +79,17 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat | |||
| 79 | TriggerOnBatteryChange(identifier, value); | 79 | TriggerOnBatteryChange(identifier, value); |
| 80 | } | 80 | } |
| 81 | 81 | ||
| 82 | void InputEngine::SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value) { | ||
| 83 | { | ||
| 84 | std::scoped_lock lock{mutex}; | ||
| 85 | ControllerData& controller = controller_list.at(identifier); | ||
| 86 | if (!configuring) { | ||
| 87 | controller.color = value; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | TriggerOnColorChange(identifier, value); | ||
| 91 | } | ||
| 92 | |||
| 82 | void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) { | 93 | void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) { |
| 83 | { | 94 | { |
| 84 | std::scoped_lock lock{mutex}; | 95 | std::scoped_lock lock{mutex}; |
| @@ -176,6 +187,18 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif | |||
| 176 | return controller.battery; | 187 | return controller.battery; |
| 177 | } | 188 | } |
| 178 | 189 | ||
| 190 | Common::Input::BodyColorStatus InputEngine::GetColor(const PadIdentifier& identifier) const { | ||
| 191 | std::scoped_lock lock{mutex}; | ||
| 192 | const auto controller_iter = controller_list.find(identifier); | ||
| 193 | if (controller_iter == controller_list.cend()) { | ||
| 194 | LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), | ||
| 195 | identifier.pad, identifier.port); | ||
| 196 | return {}; | ||
| 197 | } | ||
| 198 | const ControllerData& controller = controller_iter->second; | ||
| 199 | return controller.color; | ||
| 200 | } | ||
| 201 | |||
| 179 | BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const { | 202 | BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const { |
| 180 | std::scoped_lock lock{mutex}; | 203 | std::scoped_lock lock{mutex}; |
| 181 | const auto controller_iter = controller_list.find(identifier); | 204 | const auto controller_iter = controller_list.find(identifier); |
| @@ -328,6 +351,20 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier, | |||
| 328 | } | 351 | } |
| 329 | } | 352 | } |
| 330 | 353 | ||
| 354 | void InputEngine::TriggerOnColorChange(const PadIdentifier& identifier, | ||
| 355 | [[maybe_unused]] Common::Input::BodyColorStatus value) { | ||
| 356 | std::scoped_lock lock{mutex_callback}; | ||
| 357 | for (const auto& poller_pair : callback_list) { | ||
| 358 | const InputIdentifier& poller = poller_pair.second; | ||
| 359 | if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Color, 0)) { | ||
| 360 | continue; | ||
| 361 | } | ||
| 362 | if (poller.callback.on_change) { | ||
| 363 | poller.callback.on_change(); | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 331 | void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion, | 368 | void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion, |
| 332 | const BasicMotion& value) { | 369 | const BasicMotion& value) { |
| 333 | std::scoped_lock lock{mutex_callback}; | 370 | std::scoped_lock lock{mutex_callback}; |
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 6cbcf5207..50b5a3dc8 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h | |||
| @@ -40,6 +40,7 @@ enum class EngineInputType { | |||
| 40 | Battery, | 40 | Battery, |
| 41 | Button, | 41 | Button, |
| 42 | Camera, | 42 | Camera, |
| 43 | Color, | ||
| 43 | HatButton, | 44 | HatButton, |
| 44 | Motion, | 45 | Motion, |
| 45 | Nfc, | 46 | Nfc, |
| @@ -104,14 +105,17 @@ public: | |||
| 104 | void EndConfiguration(); | 105 | void EndConfiguration(); |
| 105 | 106 | ||
| 106 | // Sets a led pattern for a controller | 107 | // Sets a led pattern for a controller |
| 107 | virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier, | 108 | virtual Common::Input::DriverResult SetLeds( |
| 108 | [[maybe_unused]] const Common::Input::LedStatus& led_status) {} | 109 | [[maybe_unused]] const PadIdentifier& identifier, |
| 110 | [[maybe_unused]] const Common::Input::LedStatus& led_status) { | ||
| 111 | return Common::Input::DriverResult::NotSupported; | ||
| 112 | } | ||
| 109 | 113 | ||
| 110 | // Sets rumble to a controller | 114 | // Sets rumble to a controller |
| 111 | virtual Common::Input::VibrationError SetVibration( | 115 | virtual Common::Input::DriverResult SetVibration( |
| 112 | [[maybe_unused]] const PadIdentifier& identifier, | 116 | [[maybe_unused]] const PadIdentifier& identifier, |
| 113 | [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { | 117 | [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { |
| 114 | return Common::Input::VibrationError::NotSupported; | 118 | return Common::Input::DriverResult::NotSupported; |
| 115 | } | 119 | } |
| 116 | 120 | ||
| 117 | // Returns true if device supports vibrations | 121 | // Returns true if device supports vibrations |
| @@ -120,17 +124,17 @@ public: | |||
| 120 | } | 124 | } |
| 121 | 125 | ||
| 122 | // Sets polling mode to a controller | 126 | // Sets polling mode to a controller |
| 123 | virtual Common::Input::PollingError SetPollingMode( | 127 | virtual Common::Input::DriverResult SetPollingMode( |
| 124 | [[maybe_unused]] const PadIdentifier& identifier, | 128 | [[maybe_unused]] const PadIdentifier& identifier, |
| 125 | [[maybe_unused]] const Common::Input::PollingMode polling_mode) { | 129 | [[maybe_unused]] const Common::Input::PollingMode polling_mode) { |
| 126 | return Common::Input::PollingError::NotSupported; | 130 | return Common::Input::DriverResult::NotSupported; |
| 127 | } | 131 | } |
| 128 | 132 | ||
| 129 | // Sets camera format to a controller | 133 | // Sets camera format to a controller |
| 130 | virtual Common::Input::CameraError SetCameraFormat( | 134 | virtual Common::Input::DriverResult SetCameraFormat( |
| 131 | [[maybe_unused]] const PadIdentifier& identifier, | 135 | [[maybe_unused]] const PadIdentifier& identifier, |
| 132 | [[maybe_unused]] Common::Input::CameraFormat camera_format) { | 136 | [[maybe_unused]] Common::Input::CameraFormat camera_format) { |
| 133 | return Common::Input::CameraError::NotSupported; | 137 | return Common::Input::DriverResult::NotSupported; |
| 134 | } | 138 | } |
| 135 | 139 | ||
| 136 | // Returns success if nfc is supported | 140 | // Returns success if nfc is supported |
| @@ -199,6 +203,7 @@ public: | |||
| 199 | bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const; | 203 | bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const; |
| 200 | f32 GetAxis(const PadIdentifier& identifier, int axis) const; | 204 | f32 GetAxis(const PadIdentifier& identifier, int axis) const; |
| 201 | Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; | 205 | Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; |
| 206 | Common::Input::BodyColorStatus GetColor(const PadIdentifier& identifier) const; | ||
| 202 | BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; | 207 | BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; |
| 203 | Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; | 208 | Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; |
| 204 | Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const; | 209 | Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const; |
| @@ -212,6 +217,7 @@ protected: | |||
| 212 | void SetHatButton(const PadIdentifier& identifier, int button, u8 value); | 217 | void SetHatButton(const PadIdentifier& identifier, int button, u8 value); |
| 213 | void SetAxis(const PadIdentifier& identifier, int axis, f32 value); | 218 | void SetAxis(const PadIdentifier& identifier, int axis, f32 value); |
| 214 | void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); | 219 | void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); |
| 220 | void SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value); | ||
| 215 | void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); | 221 | void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); |
| 216 | void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); | 222 | void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); |
| 217 | void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value); | 223 | void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value); |
| @@ -227,6 +233,7 @@ private: | |||
| 227 | std::unordered_map<int, float> axes; | 233 | std::unordered_map<int, float> axes; |
| 228 | std::unordered_map<int, BasicMotion> motions; | 234 | std::unordered_map<int, BasicMotion> motions; |
| 229 | Common::Input::BatteryLevel battery{}; | 235 | Common::Input::BatteryLevel battery{}; |
| 236 | Common::Input::BodyColorStatus color{}; | ||
| 230 | Common::Input::CameraStatus camera{}; | 237 | Common::Input::CameraStatus camera{}; |
| 231 | Common::Input::NfcStatus nfc{}; | 238 | Common::Input::NfcStatus nfc{}; |
| 232 | }; | 239 | }; |
| @@ -235,6 +242,8 @@ private: | |||
| 235 | void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value); | 242 | void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value); |
| 236 | void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value); | 243 | void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value); |
| 237 | void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); | 244 | void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); |
| 245 | void TriggerOnColorChange(const PadIdentifier& identifier, | ||
| 246 | Common::Input::BodyColorStatus value); | ||
| 238 | void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, | 247 | void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, |
| 239 | const BasicMotion& value); | 248 | const BasicMotion& value); |
| 240 | void TriggerOnCameraChange(const PadIdentifier& identifier, | 249 | void TriggerOnCameraChange(const PadIdentifier& identifier, |
diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp index edd5287c1..6990a86b9 100644 --- a/src/input_common/input_mapping.cpp +++ b/src/input_common/input_mapping.cpp | |||
| @@ -76,7 +76,7 @@ void MappingFactory::RegisterButton(const MappingData& data) { | |||
| 76 | break; | 76 | break; |
| 77 | case EngineInputType::Analog: | 77 | case EngineInputType::Analog: |
| 78 | // Ignore mouse axis when mapping buttons | 78 | // Ignore mouse axis when mapping buttons |
| 79 | if (data.engine == "mouse") { | 79 | if (data.engine == "mouse" && data.index != 4) { |
| 80 | return; | 80 | return; |
| 81 | } | 81 | } |
| 82 | new_input.Set("axis", data.index); | 82 | new_input.Set("axis", data.index); |
| @@ -194,6 +194,10 @@ bool MappingFactory::IsDriverValid(const MappingData& data) const { | |||
| 194 | if (data.engine == "keyboard" && data.pad.port != 0) { | 194 | if (data.engine == "keyboard" && data.pad.port != 0) { |
| 195 | return false; | 195 | return false; |
| 196 | } | 196 | } |
| 197 | // Only port 0 can be mapped on the mouse | ||
| 198 | if (data.engine == "mouse" && data.pad.port != 0) { | ||
| 199 | return false; | ||
| 200 | } | ||
| 197 | // To prevent mapping with two devices we disable any UDP except motion | 201 | // To prevent mapping with two devices we disable any UDP except motion |
| 198 | if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" && | 202 | if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" && |
| 199 | data.type != EngineInputType::Motion) { | 203 | data.type != EngineInputType::Motion) { |
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index fb8be42e2..8c6a6521a 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp | |||
| @@ -16,10 +16,10 @@ public: | |||
| 16 | 16 | ||
| 17 | class InputFromButton final : public Common::Input::InputDevice { | 17 | class InputFromButton final : public Common::Input::InputDevice { |
| 18 | public: | 18 | public: |
| 19 | explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, | 19 | explicit InputFromButton(PadIdentifier identifier_, int button_, bool turbo_, bool toggle_, |
| 20 | InputEngine* input_engine_) | 20 | bool inverted_, InputEngine* input_engine_) |
| 21 | : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), | 21 | : identifier(identifier_), button(button_), turbo(turbo_), toggle(toggle_), |
| 22 | input_engine(input_engine_) { | 22 | inverted(inverted_), input_engine(input_engine_) { |
| 23 | UpdateCallback engine_callback{[this]() { OnChange(); }}; | 23 | UpdateCallback engine_callback{[this]() { OnChange(); }}; |
| 24 | const InputIdentifier input_identifier{ | 24 | const InputIdentifier input_identifier{ |
| 25 | .identifier = identifier, | 25 | .identifier = identifier, |
| @@ -40,6 +40,7 @@ public: | |||
| 40 | .value = input_engine->GetButton(identifier, button), | 40 | .value = input_engine->GetButton(identifier, button), |
| 41 | .inverted = inverted, | 41 | .inverted = inverted, |
| 42 | .toggle = toggle, | 42 | .toggle = toggle, |
| 43 | .turbo = turbo, | ||
| 43 | }; | 44 | }; |
| 44 | } | 45 | } |
| 45 | 46 | ||
| @@ -68,6 +69,7 @@ public: | |||
| 68 | private: | 69 | private: |
| 69 | const PadIdentifier identifier; | 70 | const PadIdentifier identifier; |
| 70 | const int button; | 71 | const int button; |
| 72 | const bool turbo; | ||
| 71 | const bool toggle; | 73 | const bool toggle; |
| 72 | const bool inverted; | 74 | const bool inverted; |
| 73 | int callback_key; | 75 | int callback_key; |
| @@ -77,10 +79,10 @@ private: | |||
| 77 | 79 | ||
| 78 | class InputFromHatButton final : public Common::Input::InputDevice { | 80 | class InputFromHatButton final : public Common::Input::InputDevice { |
| 79 | public: | 81 | public: |
| 80 | explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_, | 82 | explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool turbo_, |
| 81 | bool inverted_, InputEngine* input_engine_) | 83 | bool toggle_, bool inverted_, InputEngine* input_engine_) |
| 82 | : identifier(identifier_), button(button_), direction(direction_), toggle(toggle_), | 84 | : identifier(identifier_), button(button_), direction(direction_), turbo(turbo_), |
| 83 | inverted(inverted_), input_engine(input_engine_) { | 85 | toggle(toggle_), inverted(inverted_), input_engine(input_engine_) { |
| 84 | UpdateCallback engine_callback{[this]() { OnChange(); }}; | 86 | UpdateCallback engine_callback{[this]() { OnChange(); }}; |
| 85 | const InputIdentifier input_identifier{ | 87 | const InputIdentifier input_identifier{ |
| 86 | .identifier = identifier, | 88 | .identifier = identifier, |
| @@ -101,6 +103,7 @@ public: | |||
| 101 | .value = input_engine->GetHatButton(identifier, button, direction), | 103 | .value = input_engine->GetHatButton(identifier, button, direction), |
| 102 | .inverted = inverted, | 104 | .inverted = inverted, |
| 103 | .toggle = toggle, | 105 | .toggle = toggle, |
| 106 | .turbo = turbo, | ||
| 104 | }; | 107 | }; |
| 105 | } | 108 | } |
| 106 | 109 | ||
| @@ -130,6 +133,7 @@ private: | |||
| 130 | const PadIdentifier identifier; | 133 | const PadIdentifier identifier; |
| 131 | const int button; | 134 | const int button; |
| 132 | const u8 direction; | 135 | const u8 direction; |
| 136 | const bool turbo; | ||
| 133 | const bool toggle; | 137 | const bool toggle; |
| 134 | const bool inverted; | 138 | const bool inverted; |
| 135 | int callback_key; | 139 | int callback_key; |
| @@ -498,6 +502,58 @@ private: | |||
| 498 | InputEngine* input_engine; | 502 | InputEngine* input_engine; |
| 499 | }; | 503 | }; |
| 500 | 504 | ||
| 505 | class InputFromColor final : public Common::Input::InputDevice { | ||
| 506 | public: | ||
| 507 | explicit InputFromColor(PadIdentifier identifier_, InputEngine* input_engine_) | ||
| 508 | : identifier(identifier_), input_engine(input_engine_) { | ||
| 509 | UpdateCallback engine_callback{[this]() { OnChange(); }}; | ||
| 510 | const InputIdentifier input_identifier{ | ||
| 511 | .identifier = identifier, | ||
| 512 | .type = EngineInputType::Color, | ||
| 513 | .index = 0, | ||
| 514 | .callback = engine_callback, | ||
| 515 | }; | ||
| 516 | last_color_value = {}; | ||
| 517 | callback_key = input_engine->SetCallback(input_identifier); | ||
| 518 | } | ||
| 519 | |||
| 520 | ~InputFromColor() override { | ||
| 521 | input_engine->DeleteCallback(callback_key); | ||
| 522 | } | ||
| 523 | |||
| 524 | Common::Input::BodyColorStatus GetStatus() const { | ||
| 525 | return input_engine->GetColor(identifier); | ||
| 526 | } | ||
| 527 | |||
| 528 | void ForceUpdate() override { | ||
| 529 | const Common::Input::CallbackStatus status{ | ||
| 530 | .type = Common::Input::InputType::Color, | ||
| 531 | .color_status = GetStatus(), | ||
| 532 | }; | ||
| 533 | |||
| 534 | last_color_value = status.color_status; | ||
| 535 | TriggerOnChange(status); | ||
| 536 | } | ||
| 537 | |||
| 538 | void OnChange() { | ||
| 539 | const Common::Input::CallbackStatus status{ | ||
| 540 | .type = Common::Input::InputType::Color, | ||
| 541 | .color_status = GetStatus(), | ||
| 542 | }; | ||
| 543 | |||
| 544 | if (status.color_status.body != last_color_value.body) { | ||
| 545 | last_color_value = status.color_status; | ||
| 546 | TriggerOnChange(status); | ||
| 547 | } | ||
| 548 | } | ||
| 549 | |||
| 550 | private: | ||
| 551 | const PadIdentifier identifier; | ||
| 552 | int callback_key; | ||
| 553 | Common::Input::BodyColorStatus last_color_value; | ||
| 554 | InputEngine* input_engine; | ||
| 555 | }; | ||
| 556 | |||
| 501 | class InputFromMotion final : public Common::Input::InputDevice { | 557 | class InputFromMotion final : public Common::Input::InputDevice { |
| 502 | public: | 558 | public: |
| 503 | explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_, | 559 | explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_, |
| @@ -754,11 +810,11 @@ public: | |||
| 754 | explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) | 810 | explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) |
| 755 | : identifier(identifier_), input_engine(input_engine_) {} | 811 | : identifier(identifier_), input_engine(input_engine_) {} |
| 756 | 812 | ||
| 757 | void SetLED(const Common::Input::LedStatus& led_status) override { | 813 | Common::Input::DriverResult SetLED(const Common::Input::LedStatus& led_status) override { |
| 758 | input_engine->SetLeds(identifier, led_status); | 814 | return input_engine->SetLeds(identifier, led_status); |
| 759 | } | 815 | } |
| 760 | 816 | ||
| 761 | Common::Input::VibrationError SetVibration( | 817 | Common::Input::DriverResult SetVibration( |
| 762 | const Common::Input::VibrationStatus& vibration_status) override { | 818 | const Common::Input::VibrationStatus& vibration_status) override { |
| 763 | return input_engine->SetVibration(identifier, vibration_status); | 819 | return input_engine->SetVibration(identifier, vibration_status); |
| 764 | } | 820 | } |
| @@ -767,11 +823,12 @@ public: | |||
| 767 | return input_engine->IsVibrationEnabled(identifier); | 823 | return input_engine->IsVibrationEnabled(identifier); |
| 768 | } | 824 | } |
| 769 | 825 | ||
| 770 | Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override { | 826 | Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode) override { |
| 771 | return input_engine->SetPollingMode(identifier, polling_mode); | 827 | return input_engine->SetPollingMode(identifier, polling_mode); |
| 772 | } | 828 | } |
| 773 | 829 | ||
| 774 | Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override { | 830 | Common::Input::DriverResult SetCameraFormat( |
| 831 | Common::Input::CameraFormat camera_format) override { | ||
| 775 | return input_engine->SetCameraFormat(identifier, camera_format); | 832 | return input_engine->SetCameraFormat(identifier, camera_format); |
| 776 | } | 833 | } |
| 777 | 834 | ||
| @@ -800,14 +857,15 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice( | |||
| 800 | const auto keyboard_key = params.Get("code", 0); | 857 | const auto keyboard_key = params.Get("code", 0); |
| 801 | const auto toggle = params.Get("toggle", false) != 0; | 858 | const auto toggle = params.Get("toggle", false) != 0; |
| 802 | const auto inverted = params.Get("inverted", false) != 0; | 859 | const auto inverted = params.Get("inverted", false) != 0; |
| 860 | const auto turbo = params.Get("turbo", false) != 0; | ||
| 803 | input_engine->PreSetController(identifier); | 861 | input_engine->PreSetController(identifier); |
| 804 | input_engine->PreSetButton(identifier, button_id); | 862 | input_engine->PreSetButton(identifier, button_id); |
| 805 | input_engine->PreSetButton(identifier, keyboard_key); | 863 | input_engine->PreSetButton(identifier, keyboard_key); |
| 806 | if (keyboard_key != 0) { | 864 | if (keyboard_key != 0) { |
| 807 | return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted, | 865 | return std::make_unique<InputFromButton>(identifier, keyboard_key, turbo, toggle, inverted, |
| 808 | input_engine.get()); | 866 | input_engine.get()); |
| 809 | } | 867 | } |
| 810 | return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted, | 868 | return std::make_unique<InputFromButton>(identifier, button_id, turbo, toggle, inverted, |
| 811 | input_engine.get()); | 869 | input_engine.get()); |
| 812 | } | 870 | } |
| 813 | 871 | ||
| @@ -823,11 +881,12 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice( | |||
| 823 | const auto direction = input_engine->GetHatButtonId(params.Get("direction", "")); | 881 | const auto direction = input_engine->GetHatButtonId(params.Get("direction", "")); |
| 824 | const auto toggle = params.Get("toggle", false) != 0; | 882 | const auto toggle = params.Get("toggle", false) != 0; |
| 825 | const auto inverted = params.Get("inverted", false) != 0; | 883 | const auto inverted = params.Get("inverted", false) != 0; |
| 884 | const auto turbo = params.Get("turbo", false) != 0; | ||
| 826 | 885 | ||
| 827 | input_engine->PreSetController(identifier); | 886 | input_engine->PreSetController(identifier); |
| 828 | input_engine->PreSetHatButton(identifier, button_id); | 887 | input_engine->PreSetHatButton(identifier, button_id); |
| 829 | return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted, | 888 | return std::make_unique<InputFromHatButton>(identifier, button_id, direction, turbo, toggle, |
| 830 | input_engine.get()); | 889 | inverted, input_engine.get()); |
| 831 | } | 890 | } |
| 832 | 891 | ||
| 833 | std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice( | 892 | std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice( |
| @@ -966,6 +1025,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice( | |||
| 966 | return std::make_unique<InputFromBattery>(identifier, input_engine.get()); | 1025 | return std::make_unique<InputFromBattery>(identifier, input_engine.get()); |
| 967 | } | 1026 | } |
| 968 | 1027 | ||
| 1028 | std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateColorDevice( | ||
| 1029 | const Common::ParamPackage& params) { | ||
| 1030 | const PadIdentifier identifier = { | ||
| 1031 | .guid = Common::UUID{params.Get("guid", "")}, | ||
| 1032 | .port = static_cast<std::size_t>(params.Get("port", 0)), | ||
| 1033 | .pad = static_cast<std::size_t>(params.Get("pad", 0)), | ||
| 1034 | }; | ||
| 1035 | |||
| 1036 | input_engine->PreSetController(identifier); | ||
| 1037 | return std::make_unique<InputFromColor>(identifier, input_engine.get()); | ||
| 1038 | } | ||
| 1039 | |||
| 969 | std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice( | 1040 | std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice( |
| 970 | Common::ParamPackage params) { | 1041 | Common::ParamPackage params) { |
| 971 | const PadIdentifier identifier = { | 1042 | const PadIdentifier identifier = { |
| @@ -1053,6 +1124,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create( | |||
| 1053 | if (params.Has("battery")) { | 1124 | if (params.Has("battery")) { |
| 1054 | return CreateBatteryDevice(params); | 1125 | return CreateBatteryDevice(params); |
| 1055 | } | 1126 | } |
| 1127 | if (params.Has("color")) { | ||
| 1128 | return CreateColorDevice(params); | ||
| 1129 | } | ||
| 1056 | if (params.Has("camera")) { | 1130 | if (params.Has("camera")) { |
| 1057 | return CreateCameraDevice(params); | 1131 | return CreateCameraDevice(params); |
| 1058 | } | 1132 | } |
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h index d7db13ce4..e097e254c 100644 --- a/src/input_common/input_poller.h +++ b/src/input_common/input_poller.h | |||
| @@ -191,6 +191,17 @@ private: | |||
| 191 | const Common::ParamPackage& params); | 191 | const Common::ParamPackage& params); |
| 192 | 192 | ||
| 193 | /** | 193 | /** |
| 194 | * Creates a color device from the parameters given. | ||
| 195 | * @param params contains parameters for creating the device: | ||
| 196 | * - "guid": text string for identifying controllers | ||
| 197 | * - "port": port of the connected device | ||
| 198 | * - "pad": slot of the connected controller | ||
| 199 | * @returns a unique input device with the parameters specified | ||
| 200 | */ | ||
| 201 | std::unique_ptr<Common::Input::InputDevice> CreateColorDevice( | ||
| 202 | const Common::ParamPackage& params); | ||
| 203 | |||
| 204 | /** | ||
| 194 | * Creates a motion device from the parameters given. | 205 | * Creates a motion device from the parameters given. |
| 195 | * @param params contains parameters for creating the device: | 206 | * @param params contains parameters for creating the device: |
| 196 | * - "axis_x": the controller horizontal axis id to bind with the input | 207 | * - "axis_x": the controller horizontal axis id to bind with the input |
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 4dc92f482..c77fc04ee 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp | |||
| @@ -23,11 +23,34 @@ | |||
| 23 | #include "input_common/drivers/gc_adapter.h" | 23 | #include "input_common/drivers/gc_adapter.h" |
| 24 | #endif | 24 | #endif |
| 25 | #ifdef HAVE_SDL2 | 25 | #ifdef HAVE_SDL2 |
| 26 | #include "input_common/drivers/joycon.h" | ||
| 26 | #include "input_common/drivers/sdl_driver.h" | 27 | #include "input_common/drivers/sdl_driver.h" |
| 27 | #endif | 28 | #endif |
| 28 | 29 | ||
| 29 | namespace InputCommon { | 30 | namespace InputCommon { |
| 30 | 31 | ||
| 32 | /// Dummy engine to get periodic updates | ||
| 33 | class UpdateEngine final : public InputEngine { | ||
| 34 | public: | ||
| 35 | explicit UpdateEngine(std::string input_engine_) : InputEngine(std::move(input_engine_)) { | ||
| 36 | PreSetController(identifier); | ||
| 37 | } | ||
| 38 | |||
| 39 | void PumpEvents() { | ||
| 40 | SetButton(identifier, 0, last_state); | ||
| 41 | last_state = !last_state; | ||
| 42 | } | ||
| 43 | |||
| 44 | private: | ||
| 45 | static constexpr PadIdentifier identifier = { | ||
| 46 | .guid = Common::UUID{}, | ||
| 47 | .port = 0, | ||
| 48 | .pad = 0, | ||
| 49 | }; | ||
| 50 | |||
| 51 | bool last_state{}; | ||
| 52 | }; | ||
| 53 | |||
| 31 | struct InputSubsystem::Impl { | 54 | struct InputSubsystem::Impl { |
| 32 | template <typename Engine> | 55 | template <typename Engine> |
| 33 | void RegisterEngine(std::string name, std::shared_ptr<Engine>& engine) { | 56 | void RegisterEngine(std::string name, std::shared_ptr<Engine>& engine) { |
| @@ -45,6 +68,7 @@ struct InputSubsystem::Impl { | |||
| 45 | void Initialize() { | 68 | void Initialize() { |
| 46 | mapping_factory = std::make_shared<MappingFactory>(); | 69 | mapping_factory = std::make_shared<MappingFactory>(); |
| 47 | 70 | ||
| 71 | RegisterEngine("updater", update_engine); | ||
| 48 | RegisterEngine("keyboard", keyboard); | 72 | RegisterEngine("keyboard", keyboard); |
| 49 | RegisterEngine("mouse", mouse); | 73 | RegisterEngine("mouse", mouse); |
| 50 | RegisterEngine("touch", touch_screen); | 74 | RegisterEngine("touch", touch_screen); |
| @@ -58,6 +82,7 @@ struct InputSubsystem::Impl { | |||
| 58 | RegisterEngine("virtual_gamepad", virtual_gamepad); | 82 | RegisterEngine("virtual_gamepad", virtual_gamepad); |
| 59 | #ifdef HAVE_SDL2 | 83 | #ifdef HAVE_SDL2 |
| 60 | RegisterEngine("sdl", sdl); | 84 | RegisterEngine("sdl", sdl); |
| 85 | RegisterEngine("joycon", joycon); | ||
| 61 | #endif | 86 | #endif |
| 62 | 87 | ||
| 63 | Common::Input::RegisterInputFactory("touch_from_button", | 88 | Common::Input::RegisterInputFactory("touch_from_button", |
| @@ -74,6 +99,7 @@ struct InputSubsystem::Impl { | |||
| 74 | } | 99 | } |
| 75 | 100 | ||
| 76 | void Shutdown() { | 101 | void Shutdown() { |
| 102 | UnregisterEngine(update_engine); | ||
| 77 | UnregisterEngine(keyboard); | 103 | UnregisterEngine(keyboard); |
| 78 | UnregisterEngine(mouse); | 104 | UnregisterEngine(mouse); |
| 79 | UnregisterEngine(touch_screen); | 105 | UnregisterEngine(touch_screen); |
| @@ -87,6 +113,7 @@ struct InputSubsystem::Impl { | |||
| 87 | UnregisterEngine(virtual_gamepad); | 113 | UnregisterEngine(virtual_gamepad); |
| 88 | #ifdef HAVE_SDL2 | 114 | #ifdef HAVE_SDL2 |
| 89 | UnregisterEngine(sdl); | 115 | UnregisterEngine(sdl); |
| 116 | UnregisterEngine(joycon); | ||
| 90 | #endif | 117 | #endif |
| 91 | 118 | ||
| 92 | Common::Input::UnregisterInputFactory("touch_from_button"); | 119 | Common::Input::UnregisterInputFactory("touch_from_button"); |
| @@ -109,6 +136,8 @@ struct InputSubsystem::Impl { | |||
| 109 | auto udp_devices = udp_client->GetInputDevices(); | 136 | auto udp_devices = udp_client->GetInputDevices(); |
| 110 | devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); | 137 | devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); |
| 111 | #ifdef HAVE_SDL2 | 138 | #ifdef HAVE_SDL2 |
| 139 | auto joycon_devices = joycon->GetInputDevices(); | ||
| 140 | devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end()); | ||
| 112 | auto sdl_devices = sdl->GetInputDevices(); | 141 | auto sdl_devices = sdl->GetInputDevices(); |
| 113 | devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); | 142 | devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); |
| 114 | #endif | 143 | #endif |
| @@ -140,6 +169,9 @@ struct InputSubsystem::Impl { | |||
| 140 | if (engine == sdl->GetEngineName()) { | 169 | if (engine == sdl->GetEngineName()) { |
| 141 | return sdl; | 170 | return sdl; |
| 142 | } | 171 | } |
| 172 | if (engine == joycon->GetEngineName()) { | ||
| 173 | return joycon; | ||
| 174 | } | ||
| 143 | #endif | 175 | #endif |
| 144 | return nullptr; | 176 | return nullptr; |
| 145 | } | 177 | } |
| @@ -223,6 +255,9 @@ struct InputSubsystem::Impl { | |||
| 223 | if (engine == sdl->GetEngineName()) { | 255 | if (engine == sdl->GetEngineName()) { |
| 224 | return true; | 256 | return true; |
| 225 | } | 257 | } |
| 258 | if (engine == joycon->GetEngineName()) { | ||
| 259 | return true; | ||
| 260 | } | ||
| 226 | #endif | 261 | #endif |
| 227 | return false; | 262 | return false; |
| 228 | } | 263 | } |
| @@ -236,6 +271,7 @@ struct InputSubsystem::Impl { | |||
| 236 | udp_client->BeginConfiguration(); | 271 | udp_client->BeginConfiguration(); |
| 237 | #ifdef HAVE_SDL2 | 272 | #ifdef HAVE_SDL2 |
| 238 | sdl->BeginConfiguration(); | 273 | sdl->BeginConfiguration(); |
| 274 | joycon->BeginConfiguration(); | ||
| 239 | #endif | 275 | #endif |
| 240 | } | 276 | } |
| 241 | 277 | ||
| @@ -248,10 +284,12 @@ struct InputSubsystem::Impl { | |||
| 248 | udp_client->EndConfiguration(); | 284 | udp_client->EndConfiguration(); |
| 249 | #ifdef HAVE_SDL2 | 285 | #ifdef HAVE_SDL2 |
| 250 | sdl->EndConfiguration(); | 286 | sdl->EndConfiguration(); |
| 287 | joycon->EndConfiguration(); | ||
| 251 | #endif | 288 | #endif |
| 252 | } | 289 | } |
| 253 | 290 | ||
| 254 | void PumpEvents() const { | 291 | void PumpEvents() const { |
| 292 | update_engine->PumpEvents(); | ||
| 255 | #ifdef HAVE_SDL2 | 293 | #ifdef HAVE_SDL2 |
| 256 | sdl->PumpEvents(); | 294 | sdl->PumpEvents(); |
| 257 | #endif | 295 | #endif |
| @@ -263,6 +301,7 @@ struct InputSubsystem::Impl { | |||
| 263 | 301 | ||
| 264 | std::shared_ptr<MappingFactory> mapping_factory; | 302 | std::shared_ptr<MappingFactory> mapping_factory; |
| 265 | 303 | ||
| 304 | std::shared_ptr<UpdateEngine> update_engine; | ||
| 266 | std::shared_ptr<Keyboard> keyboard; | 305 | std::shared_ptr<Keyboard> keyboard; |
| 267 | std::shared_ptr<Mouse> mouse; | 306 | std::shared_ptr<Mouse> mouse; |
| 268 | std::shared_ptr<TouchScreen> touch_screen; | 307 | std::shared_ptr<TouchScreen> touch_screen; |
| @@ -278,6 +317,7 @@ struct InputSubsystem::Impl { | |||
| 278 | 317 | ||
| 279 | #ifdef HAVE_SDL2 | 318 | #ifdef HAVE_SDL2 |
| 280 | std::shared_ptr<SDLDriver> sdl; | 319 | std::shared_ptr<SDLDriver> sdl; |
| 320 | std::shared_ptr<Joycons> joycon; | ||
| 281 | #endif | 321 | #endif |
| 282 | }; | 322 | }; |
| 283 | 323 | ||