diff options
Diffstat (limited to 'src/input_common/helpers/joycon_driver.cpp')
| -rw-r--r-- | src/input_common/helpers/joycon_driver.cpp | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp new file mode 100644 index 000000000..ec984a647 --- /dev/null +++ b/src/input_common/helpers/joycon_driver.cpp | |||
| @@ -0,0 +1,710 @@ | |||
| 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/scope_exit.h" | ||
| 6 | #include "common/swap.h" | ||
| 7 | #include "common/thread.h" | ||
| 8 | #include "input_common/helpers/joycon_driver.h" | ||
| 9 | #include "input_common/helpers/joycon_protocol/calibration.h" | ||
| 10 | #include "input_common/helpers/joycon_protocol/generic_functions.h" | ||
| 11 | #include "input_common/helpers/joycon_protocol/irs.h" | ||
| 12 | #include "input_common/helpers/joycon_protocol/nfc.h" | ||
| 13 | #include "input_common/helpers/joycon_protocol/poller.h" | ||
| 14 | #include "input_common/helpers/joycon_protocol/ringcon.h" | ||
| 15 | #include "input_common/helpers/joycon_protocol/rumble.h" | ||
| 16 | |||
| 17 | namespace InputCommon::Joycon { | ||
| 18 | JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} { | ||
| 19 | hidapi_handle = std::make_shared<JoyconHandle>(); | ||
| 20 | } | ||
| 21 | |||
| 22 | JoyconDriver::~JoyconDriver() { | ||
| 23 | Stop(); | ||
| 24 | } | ||
| 25 | |||
| 26 | void JoyconDriver::Stop() { | ||
| 27 | is_connected = false; | ||
| 28 | input_thread = {}; | ||
| 29 | } | ||
| 30 | |||
| 31 | DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) { | ||
| 32 | std::scoped_lock lock{mutex}; | ||
| 33 | |||
| 34 | handle_device_type = ControllerType::None; | ||
| 35 | GetDeviceType(device_info, handle_device_type); | ||
| 36 | if (handle_device_type == ControllerType::None) { | ||
| 37 | return DriverResult::UnsupportedControllerType; | ||
| 38 | } | ||
| 39 | |||
| 40 | hidapi_handle->handle = | ||
| 41 | SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); | ||
| 42 | std::memcpy(&handle_serial_number, device_info->serial_number, 15); | ||
| 43 | if (!hidapi_handle->handle) { | ||
| 44 | LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", | ||
| 45 | device_info->vendor_id, device_info->product_id); | ||
| 46 | return DriverResult::HandleInUse; | ||
| 47 | } | ||
| 48 | SDL_hid_set_nonblocking(hidapi_handle->handle, 1); | ||
| 49 | return DriverResult::Success; | ||
| 50 | } | ||
| 51 | |||
| 52 | DriverResult JoyconDriver::InitializeDevice() { | ||
| 53 | if (!hidapi_handle->handle) { | ||
| 54 | return DriverResult::InvalidHandle; | ||
| 55 | } | ||
| 56 | std::scoped_lock lock{mutex}; | ||
| 57 | disable_input_thread = true; | ||
| 58 | |||
| 59 | // Reset Counters | ||
| 60 | error_counter = 0; | ||
| 61 | hidapi_handle->packet_counter = 0; | ||
| 62 | |||
| 63 | // Reset external device status | ||
| 64 | starlink_connected = false; | ||
| 65 | ring_connected = false; | ||
| 66 | amiibo_detected = false; | ||
| 67 | |||
| 68 | // Set HW default configuration | ||
| 69 | vibration_enabled = true; | ||
| 70 | motion_enabled = true; | ||
| 71 | hidbus_enabled = false; | ||
| 72 | nfc_enabled = false; | ||
| 73 | passive_enabled = false; | ||
| 74 | irs_enabled = false; | ||
| 75 | input_only_device = false; | ||
| 76 | gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; | ||
| 77 | gyro_performance = Joycon::GyroPerformance::HZ833; | ||
| 78 | accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; | ||
| 79 | accelerometer_performance = Joycon::AccelerometerPerformance::HZ100; | ||
| 80 | |||
| 81 | // Initialize HW Protocols | ||
| 82 | calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle); | ||
| 83 | generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle); | ||
| 84 | irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle); | ||
| 85 | nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle); | ||
| 86 | ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle); | ||
| 87 | rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle); | ||
| 88 | |||
| 89 | // Get fixed joycon info | ||
| 90 | if (generic_protocol->GetVersionNumber(version) != DriverResult::Success) { | ||
| 91 | // If this command fails the device doesn't accept configuration commands | ||
| 92 | input_only_device = true; | ||
| 93 | } | ||
| 94 | |||
| 95 | if (!input_only_device) { | ||
| 96 | generic_protocol->SetLowPowerMode(false); | ||
| 97 | generic_protocol->GetColor(color); | ||
| 98 | if (handle_device_type == ControllerType::Pro) { | ||
| 99 | // Some 3rd party controllers aren't pro controllers | ||
| 100 | generic_protocol->GetControllerType(device_type); | ||
| 101 | } else { | ||
| 102 | device_type = handle_device_type; | ||
| 103 | } | ||
| 104 | generic_protocol->GetSerialNumber(serial_number); | ||
| 105 | } | ||
| 106 | |||
| 107 | supported_features = GetSupportedFeatures(); | ||
| 108 | |||
| 109 | // Get Calibration data | ||
| 110 | calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration); | ||
| 111 | calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration); | ||
| 112 | calibration_protocol->GetImuCalibration(motion_calibration); | ||
| 113 | |||
| 114 | // Set led status | ||
| 115 | generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port)); | ||
| 116 | |||
| 117 | // Apply HW configuration | ||
| 118 | SetPollingMode(); | ||
| 119 | |||
| 120 | // Initialize joycon poller | ||
| 121 | joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration, | ||
| 122 | right_stick_calibration, motion_calibration); | ||
| 123 | |||
| 124 | // Start polling for data | ||
| 125 | is_connected = true; | ||
| 126 | if (!input_thread_running) { | ||
| 127 | input_thread = | ||
| 128 | std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); }); | ||
| 129 | } | ||
| 130 | |||
| 131 | disable_input_thread = false; | ||
| 132 | return DriverResult::Success; | ||
| 133 | } | ||
| 134 | |||
| 135 | void JoyconDriver::InputThread(std::stop_token stop_token) { | ||
| 136 | LOG_INFO(Input, "Joycon Adapter input thread started"); | ||
| 137 | Common::SetCurrentThreadName("JoyconInput"); | ||
| 138 | input_thread_running = true; | ||
| 139 | |||
| 140 | // Max update rate is 5ms, ensure we are always able to read a bit faster | ||
| 141 | constexpr int ThreadDelay = 2; | ||
| 142 | std::vector<u8> buffer(MaxBufferSize); | ||
| 143 | |||
| 144 | while (!stop_token.stop_requested()) { | ||
| 145 | int status = 0; | ||
| 146 | |||
| 147 | if (!IsInputThreadValid()) { | ||
| 148 | input_thread.request_stop(); | ||
| 149 | continue; | ||
| 150 | } | ||
| 151 | |||
| 152 | // By disabling the input thread we can ensure custom commands will succeed as no package is | ||
| 153 | // skipped | ||
| 154 | if (!disable_input_thread) { | ||
| 155 | status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(), | ||
| 156 | ThreadDelay); | ||
| 157 | } else { | ||
| 158 | std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay)); | ||
| 159 | } | ||
| 160 | |||
| 161 | if (IsPayloadCorrect(status, buffer)) { | ||
| 162 | OnNewData(buffer); | ||
| 163 | } | ||
| 164 | |||
| 165 | std::this_thread::yield(); | ||
| 166 | } | ||
| 167 | |||
| 168 | is_connected = false; | ||
| 169 | input_thread_running = false; | ||
| 170 | LOG_INFO(Input, "Joycon Adapter input thread stopped"); | ||
| 171 | } | ||
| 172 | |||
| 173 | void JoyconDriver::OnNewData(std::span<u8> buffer) { | ||
| 174 | const auto report_mode = static_cast<ReportMode>(buffer[0]); | ||
| 175 | |||
| 176 | // Packages can be a little bit inconsistent. Average the delta time to provide a smoother | ||
| 177 | // motion experience | ||
| 178 | switch (report_mode) { | ||
| 179 | case ReportMode::STANDARD_FULL_60HZ: | ||
| 180 | case ReportMode::NFC_IR_MODE_60HZ: | ||
| 181 | case ReportMode::SIMPLE_HID_MODE: { | ||
| 182 | const auto now = std::chrono::steady_clock::now(); | ||
| 183 | const auto new_delta_time = static_cast<u64>( | ||
| 184 | std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); | ||
| 185 | delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10; | ||
| 186 | last_update = now; | ||
| 187 | joycon_poller->UpdateColor(color); | ||
| 188 | break; | ||
| 189 | } | ||
| 190 | default: | ||
| 191 | break; | ||
| 192 | } | ||
| 193 | |||
| 194 | const MotionStatus motion_status{ | ||
| 195 | .is_enabled = motion_enabled, | ||
| 196 | .delta_time = delta_time, | ||
| 197 | .gyro_sensitivity = gyro_sensitivity, | ||
| 198 | .accelerometer_sensitivity = accelerometer_sensitivity, | ||
| 199 | }; | ||
| 200 | |||
| 201 | // TODO: Remove this when calibration is properly loaded and not calculated | ||
| 202 | if (ring_connected && report_mode == ReportMode::STANDARD_FULL_60HZ) { | ||
| 203 | InputReportActive data{}; | ||
| 204 | memcpy(&data, buffer.data(), sizeof(InputReportActive)); | ||
| 205 | calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input); | ||
| 206 | } | ||
| 207 | |||
| 208 | const RingStatus ring_status{ | ||
| 209 | .is_enabled = ring_connected, | ||
| 210 | .default_value = ring_calibration.default_value, | ||
| 211 | .max_value = ring_calibration.max_value, | ||
| 212 | .min_value = ring_calibration.min_value, | ||
| 213 | }; | ||
| 214 | |||
| 215 | if (irs_protocol->IsEnabled()) { | ||
| 216 | irs_protocol->RequestImage(buffer); | ||
| 217 | joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); | ||
| 218 | } | ||
| 219 | |||
| 220 | if (nfc_protocol->IsPolling()) { | ||
| 221 | if (amiibo_detected) { | ||
| 222 | if (!nfc_protocol->HasAmiibo()) { | ||
| 223 | joycon_poller->UpdateAmiibo({}); | ||
| 224 | amiibo_detected = false; | ||
| 225 | return; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | if (!amiibo_detected) { | ||
| 230 | Joycon::TagInfo tag_info; | ||
| 231 | const auto result = nfc_protocol->GetTagInfo(tag_info); | ||
| 232 | if (result == DriverResult::Success) { | ||
| 233 | joycon_poller->UpdateAmiibo(tag_info); | ||
| 234 | amiibo_detected = true; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | switch (report_mode) { | ||
| 240 | case ReportMode::STANDARD_FULL_60HZ: | ||
| 241 | joycon_poller->ReadActiveMode(buffer, motion_status, ring_status); | ||
| 242 | break; | ||
| 243 | case ReportMode::NFC_IR_MODE_60HZ: | ||
| 244 | joycon_poller->ReadNfcIRMode(buffer, motion_status); | ||
| 245 | break; | ||
| 246 | case ReportMode::SIMPLE_HID_MODE: | ||
| 247 | joycon_poller->ReadPassiveMode(buffer); | ||
| 248 | break; | ||
| 249 | case ReportMode::SUBCMD_REPLY: | ||
| 250 | LOG_DEBUG(Input, "Unhandled command reply"); | ||
| 251 | break; | ||
| 252 | default: | ||
| 253 | LOG_ERROR(Input, "Report mode not Implemented {}", report_mode); | ||
| 254 | break; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | DriverResult JoyconDriver::SetPollingMode() { | ||
| 259 | SCOPE_EXIT({ disable_input_thread = false; }); | ||
| 260 | disable_input_thread = true; | ||
| 261 | |||
| 262 | rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); | ||
| 263 | |||
| 264 | if (motion_enabled && supported_features.motion) { | ||
| 265 | generic_protocol->EnableImu(true); | ||
| 266 | generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance, | ||
| 267 | accelerometer_sensitivity, accelerometer_performance); | ||
| 268 | } else { | ||
| 269 | generic_protocol->EnableImu(false); | ||
| 270 | } | ||
| 271 | |||
| 272 | if (input_only_device) { | ||
| 273 | return DriverResult::NotSupported; | ||
| 274 | } | ||
| 275 | |||
| 276 | if (irs_protocol->IsEnabled()) { | ||
| 277 | irs_protocol->DisableIrs(); | ||
| 278 | } | ||
| 279 | |||
| 280 | if (nfc_protocol->IsEnabled()) { | ||
| 281 | amiibo_detected = false; | ||
| 282 | nfc_protocol->DisableNfc(); | ||
| 283 | } | ||
| 284 | |||
| 285 | if (ring_protocol->IsEnabled()) { | ||
| 286 | ring_connected = false; | ||
| 287 | ring_protocol->DisableRingCon(); | ||
| 288 | } | ||
| 289 | |||
| 290 | if (irs_enabled && supported_features.irs) { | ||
| 291 | auto result = irs_protocol->EnableIrs(); | ||
| 292 | if (result == DriverResult::Success) { | ||
| 293 | return result; | ||
| 294 | } | ||
| 295 | irs_protocol->DisableIrs(); | ||
| 296 | LOG_ERROR(Input, "Error enabling IRS"); | ||
| 297 | return result; | ||
| 298 | } | ||
| 299 | |||
| 300 | if (nfc_enabled && supported_features.nfc) { | ||
| 301 | auto result = nfc_protocol->EnableNfc(); | ||
| 302 | if (result == DriverResult::Success) { | ||
| 303 | return result; | ||
| 304 | } | ||
| 305 | nfc_protocol->DisableNfc(); | ||
| 306 | LOG_ERROR(Input, "Error enabling NFC"); | ||
| 307 | return result; | ||
| 308 | } | ||
| 309 | |||
| 310 | if (hidbus_enabled && supported_features.hidbus) { | ||
| 311 | auto result = ring_protocol->EnableRingCon(); | ||
| 312 | if (result == DriverResult::Success) { | ||
| 313 | result = ring_protocol->StartRingconPolling(); | ||
| 314 | } | ||
| 315 | if (result == DriverResult::Success) { | ||
| 316 | ring_connected = true; | ||
| 317 | return result; | ||
| 318 | } | ||
| 319 | ring_connected = false; | ||
| 320 | ring_protocol->DisableRingCon(); | ||
| 321 | LOG_ERROR(Input, "Error enabling Ringcon"); | ||
| 322 | return result; | ||
| 323 | } | ||
| 324 | |||
| 325 | if (passive_enabled && supported_features.passive) { | ||
| 326 | const auto result = generic_protocol->EnablePassiveMode(); | ||
| 327 | if (result == DriverResult::Success) { | ||
| 328 | return result; | ||
| 329 | } | ||
| 330 | LOG_ERROR(Input, "Error enabling passive mode"); | ||
| 331 | } | ||
| 332 | |||
| 333 | // Default Mode | ||
| 334 | const auto result = generic_protocol->EnableActiveMode(); | ||
| 335 | if (result != DriverResult::Success) { | ||
| 336 | LOG_ERROR(Input, "Error enabling active mode"); | ||
| 337 | } | ||
| 338 | // Switch calls this function after enabling active mode | ||
| 339 | generic_protocol->TriggersElapsed(); | ||
| 340 | |||
| 341 | return result; | ||
| 342 | } | ||
| 343 | |||
| 344 | JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() { | ||
| 345 | SupportedFeatures features{ | ||
| 346 | .passive = true, | ||
| 347 | .motion = true, | ||
| 348 | .vibration = true, | ||
| 349 | }; | ||
| 350 | |||
| 351 | if (input_only_device) { | ||
| 352 | return features; | ||
| 353 | } | ||
| 354 | |||
| 355 | if (device_type == ControllerType::Right) { | ||
| 356 | features.nfc = true; | ||
| 357 | features.irs = true; | ||
| 358 | features.hidbus = true; | ||
| 359 | } | ||
| 360 | |||
| 361 | if (device_type == ControllerType::Pro) { | ||
| 362 | features.nfc = true; | ||
| 363 | } | ||
| 364 | return features; | ||
| 365 | } | ||
| 366 | |||
| 367 | bool JoyconDriver::IsInputThreadValid() const { | ||
| 368 | if (!is_connected.load()) { | ||
| 369 | return false; | ||
| 370 | } | ||
| 371 | if (hidapi_handle->handle == nullptr) { | ||
| 372 | return false; | ||
| 373 | } | ||
| 374 | // Controller is not responding. Terminate connection | ||
| 375 | if (error_counter > MaxErrorCount) { | ||
| 376 | return false; | ||
| 377 | } | ||
| 378 | return true; | ||
| 379 | } | ||
| 380 | |||
| 381 | bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) { | ||
| 382 | if (status <= -1) { | ||
| 383 | error_counter++; | ||
| 384 | return false; | ||
| 385 | } | ||
| 386 | // There's no new data | ||
| 387 | if (status == 0) { | ||
| 388 | return false; | ||
| 389 | } | ||
| 390 | // No reply ever starts with zero | ||
| 391 | if (buffer[0] == 0x00) { | ||
| 392 | error_counter++; | ||
| 393 | return false; | ||
| 394 | } | ||
| 395 | error_counter = 0; | ||
| 396 | return true; | ||
| 397 | } | ||
| 398 | |||
| 399 | DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) { | ||
| 400 | std::scoped_lock lock{mutex}; | ||
| 401 | if (disable_input_thread) { | ||
| 402 | return DriverResult::HandleInUse; | ||
| 403 | } | ||
| 404 | return rumble_protocol->SendVibration(vibration); | ||
| 405 | } | ||
| 406 | |||
| 407 | DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { | ||
| 408 | std::scoped_lock lock{mutex}; | ||
| 409 | if (disable_input_thread) { | ||
| 410 | return DriverResult::HandleInUse; | ||
| 411 | } | ||
| 412 | return generic_protocol->SetLedPattern(led_pattern); | ||
| 413 | } | ||
| 414 | |||
| 415 | DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) { | ||
| 416 | std::scoped_lock lock{mutex}; | ||
| 417 | if (disable_input_thread) { | ||
| 418 | return DriverResult::HandleInUse; | ||
| 419 | } | ||
| 420 | disable_input_thread = true; | ||
| 421 | const auto result = irs_protocol->SetIrsConfig(mode_, format_); | ||
| 422 | disable_input_thread = false; | ||
| 423 | return result; | ||
| 424 | } | ||
| 425 | |||
| 426 | DriverResult JoyconDriver::SetPassiveMode() { | ||
| 427 | std::scoped_lock lock{mutex}; | ||
| 428 | motion_enabled = false; | ||
| 429 | hidbus_enabled = false; | ||
| 430 | nfc_enabled = false; | ||
| 431 | passive_enabled = true; | ||
| 432 | irs_enabled = false; | ||
| 433 | return SetPollingMode(); | ||
| 434 | } | ||
| 435 | |||
| 436 | DriverResult JoyconDriver::SetActiveMode() { | ||
| 437 | if (is_ring_disabled_by_irs) { | ||
| 438 | is_ring_disabled_by_irs = false; | ||
| 439 | SetActiveMode(); | ||
| 440 | return SetRingConMode(); | ||
| 441 | } | ||
| 442 | |||
| 443 | std::scoped_lock lock{mutex}; | ||
| 444 | motion_enabled = true; | ||
| 445 | hidbus_enabled = false; | ||
| 446 | nfc_enabled = false; | ||
| 447 | passive_enabled = false; | ||
| 448 | irs_enabled = false; | ||
| 449 | return SetPollingMode(); | ||
| 450 | } | ||
| 451 | |||
| 452 | DriverResult JoyconDriver::SetIrMode() { | ||
| 453 | std::scoped_lock lock{mutex}; | ||
| 454 | |||
| 455 | if (!supported_features.irs) { | ||
| 456 | return DriverResult::NotSupported; | ||
| 457 | } | ||
| 458 | |||
| 459 | if (ring_connected) { | ||
| 460 | is_ring_disabled_by_irs = true; | ||
| 461 | } | ||
| 462 | |||
| 463 | motion_enabled = false; | ||
| 464 | hidbus_enabled = false; | ||
| 465 | nfc_enabled = false; | ||
| 466 | passive_enabled = false; | ||
| 467 | irs_enabled = true; | ||
| 468 | return SetPollingMode(); | ||
| 469 | } | ||
| 470 | |||
| 471 | DriverResult JoyconDriver::SetNfcMode() { | ||
| 472 | std::scoped_lock lock{mutex}; | ||
| 473 | |||
| 474 | if (!supported_features.nfc) { | ||
| 475 | return DriverResult::NotSupported; | ||
| 476 | } | ||
| 477 | |||
| 478 | motion_enabled = true; | ||
| 479 | hidbus_enabled = false; | ||
| 480 | nfc_enabled = true; | ||
| 481 | passive_enabled = false; | ||
| 482 | irs_enabled = false; | ||
| 483 | return SetPollingMode(); | ||
| 484 | } | ||
| 485 | |||
| 486 | DriverResult JoyconDriver::SetRingConMode() { | ||
| 487 | std::scoped_lock lock{mutex}; | ||
| 488 | |||
| 489 | if (!supported_features.hidbus) { | ||
| 490 | return DriverResult::NotSupported; | ||
| 491 | } | ||
| 492 | |||
| 493 | motion_enabled = true; | ||
| 494 | hidbus_enabled = true; | ||
| 495 | nfc_enabled = false; | ||
| 496 | passive_enabled = false; | ||
| 497 | irs_enabled = false; | ||
| 498 | |||
| 499 | const auto result = SetPollingMode(); | ||
| 500 | |||
| 501 | if (!ring_connected) { | ||
| 502 | return DriverResult::NoDeviceDetected; | ||
| 503 | } | ||
| 504 | |||
| 505 | return result; | ||
| 506 | } | ||
| 507 | |||
| 508 | DriverResult JoyconDriver::StartNfcPolling() { | ||
| 509 | std::scoped_lock lock{mutex}; | ||
| 510 | |||
| 511 | if (!supported_features.nfc) { | ||
| 512 | return DriverResult::NotSupported; | ||
| 513 | } | ||
| 514 | if (!nfc_protocol->IsEnabled()) { | ||
| 515 | return DriverResult::Disabled; | ||
| 516 | } | ||
| 517 | |||
| 518 | disable_input_thread = true; | ||
| 519 | const auto result = nfc_protocol->StartNFCPollingMode(); | ||
| 520 | disable_input_thread = false; | ||
| 521 | |||
| 522 | return result; | ||
| 523 | } | ||
| 524 | |||
| 525 | DriverResult JoyconDriver::StopNfcPolling() { | ||
| 526 | std::scoped_lock lock{mutex}; | ||
| 527 | |||
| 528 | if (!supported_features.nfc) { | ||
| 529 | return DriverResult::NotSupported; | ||
| 530 | } | ||
| 531 | if (!nfc_protocol->IsEnabled()) { | ||
| 532 | return DriverResult::Disabled; | ||
| 533 | } | ||
| 534 | |||
| 535 | disable_input_thread = true; | ||
| 536 | const auto result = nfc_protocol->StopNFCPollingMode(); | ||
| 537 | disable_input_thread = false; | ||
| 538 | |||
| 539 | if (amiibo_detected) { | ||
| 540 | amiibo_detected = false; | ||
| 541 | joycon_poller->UpdateAmiibo({}); | ||
| 542 | } | ||
| 543 | |||
| 544 | return result; | ||
| 545 | } | ||
| 546 | |||
| 547 | DriverResult JoyconDriver::ReadAmiiboData(std::vector<u8>& out_data) { | ||
| 548 | std::scoped_lock lock{mutex}; | ||
| 549 | |||
| 550 | if (!supported_features.nfc) { | ||
| 551 | return DriverResult::NotSupported; | ||
| 552 | } | ||
| 553 | if (!nfc_protocol->IsEnabled()) { | ||
| 554 | return DriverResult::Disabled; | ||
| 555 | } | ||
| 556 | if (!amiibo_detected) { | ||
| 557 | return DriverResult::ErrorWritingData; | ||
| 558 | } | ||
| 559 | |||
| 560 | out_data.resize(0x21C); | ||
| 561 | disable_input_thread = true; | ||
| 562 | const auto result = nfc_protocol->ReadAmiibo(out_data); | ||
| 563 | disable_input_thread = false; | ||
| 564 | |||
| 565 | return result; | ||
| 566 | } | ||
| 567 | |||
| 568 | DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) { | ||
| 569 | std::scoped_lock lock{mutex}; | ||
| 570 | |||
| 571 | if (!supported_features.nfc) { | ||
| 572 | return DriverResult::NotSupported; | ||
| 573 | } | ||
| 574 | if (!nfc_protocol->IsEnabled()) { | ||
| 575 | return DriverResult::Disabled; | ||
| 576 | } | ||
| 577 | if (!amiibo_detected) { | ||
| 578 | return DriverResult::ErrorWritingData; | ||
| 579 | } | ||
| 580 | |||
| 581 | disable_input_thread = true; | ||
| 582 | const auto result = nfc_protocol->WriteAmiibo(data); | ||
| 583 | disable_input_thread = false; | ||
| 584 | |||
| 585 | return result; | ||
| 586 | } | ||
| 587 | |||
| 588 | DriverResult JoyconDriver::ReadMifareData(std::span<const MifareReadChunk> data, | ||
| 589 | std::span<MifareReadData> out_data) { | ||
| 590 | std::scoped_lock lock{mutex}; | ||
| 591 | |||
| 592 | if (!supported_features.nfc) { | ||
| 593 | return DriverResult::NotSupported; | ||
| 594 | } | ||
| 595 | if (!nfc_protocol->IsEnabled()) { | ||
| 596 | return DriverResult::Disabled; | ||
| 597 | } | ||
| 598 | if (!amiibo_detected) { | ||
| 599 | return DriverResult::ErrorWritingData; | ||
| 600 | } | ||
| 601 | |||
| 602 | disable_input_thread = true; | ||
| 603 | const auto result = nfc_protocol->ReadMifare(data, out_data); | ||
| 604 | disable_input_thread = false; | ||
| 605 | |||
| 606 | return result; | ||
| 607 | } | ||
| 608 | |||
| 609 | DriverResult JoyconDriver::WriteMifareData(std::span<const MifareWriteChunk> data) { | ||
| 610 | std::scoped_lock lock{mutex}; | ||
| 611 | |||
| 612 | if (!supported_features.nfc) { | ||
| 613 | return DriverResult::NotSupported; | ||
| 614 | } | ||
| 615 | if (!nfc_protocol->IsEnabled()) { | ||
| 616 | return DriverResult::Disabled; | ||
| 617 | } | ||
| 618 | if (!amiibo_detected) { | ||
| 619 | return DriverResult::ErrorWritingData; | ||
| 620 | } | ||
| 621 | |||
| 622 | disable_input_thread = true; | ||
| 623 | const auto result = nfc_protocol->WriteMifare(data); | ||
| 624 | disable_input_thread = false; | ||
| 625 | |||
| 626 | return result; | ||
| 627 | } | ||
| 628 | |||
| 629 | bool JoyconDriver::IsConnected() const { | ||
| 630 | std::scoped_lock lock{mutex}; | ||
| 631 | return is_connected.load(); | ||
| 632 | } | ||
| 633 | |||
| 634 | bool JoyconDriver::IsVibrationEnabled() const { | ||
| 635 | std::scoped_lock lock{mutex}; | ||
| 636 | return vibration_enabled; | ||
| 637 | } | ||
| 638 | |||
| 639 | FirmwareVersion JoyconDriver::GetDeviceVersion() const { | ||
| 640 | std::scoped_lock lock{mutex}; | ||
| 641 | return version; | ||
| 642 | } | ||
| 643 | |||
| 644 | Color JoyconDriver::GetDeviceColor() const { | ||
| 645 | std::scoped_lock lock{mutex}; | ||
| 646 | return color; | ||
| 647 | } | ||
| 648 | |||
| 649 | std::size_t JoyconDriver::GetDevicePort() const { | ||
| 650 | std::scoped_lock lock{mutex}; | ||
| 651 | return port; | ||
| 652 | } | ||
| 653 | |||
| 654 | ControllerType JoyconDriver::GetDeviceType() const { | ||
| 655 | std::scoped_lock lock{mutex}; | ||
| 656 | return device_type; | ||
| 657 | } | ||
| 658 | |||
| 659 | ControllerType JoyconDriver::GetHandleDeviceType() const { | ||
| 660 | std::scoped_lock lock{mutex}; | ||
| 661 | return handle_device_type; | ||
| 662 | } | ||
| 663 | |||
| 664 | SerialNumber JoyconDriver::GetSerialNumber() const { | ||
| 665 | std::scoped_lock lock{mutex}; | ||
| 666 | return serial_number; | ||
| 667 | } | ||
| 668 | |||
| 669 | SerialNumber JoyconDriver::GetHandleSerialNumber() const { | ||
| 670 | std::scoped_lock lock{mutex}; | ||
| 671 | return handle_serial_number; | ||
| 672 | } | ||
| 673 | |||
| 674 | void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) { | ||
| 675 | joycon_poller->SetCallbacks(callbacks); | ||
| 676 | } | ||
| 677 | |||
| 678 | DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, | ||
| 679 | ControllerType& controller_type) { | ||
| 680 | static constexpr std::array<std::pair<u32, ControllerType>, 6> supported_devices{ | ||
| 681 | std::pair<u32, ControllerType>{0x2006, ControllerType::Left}, | ||
| 682 | {0x2007, ControllerType::Right}, | ||
| 683 | {0x2009, ControllerType::Pro}, | ||
| 684 | }; | ||
| 685 | constexpr u16 nintendo_vendor_id = 0x057e; | ||
| 686 | |||
| 687 | controller_type = ControllerType::None; | ||
| 688 | if (device_info->vendor_id != nintendo_vendor_id) { | ||
| 689 | return DriverResult::UnsupportedControllerType; | ||
| 690 | } | ||
| 691 | |||
| 692 | for (const auto& [product_id, type] : supported_devices) { | ||
| 693 | if (device_info->product_id == static_cast<u16>(product_id)) { | ||
| 694 | controller_type = type; | ||
| 695 | return Joycon::DriverResult::Success; | ||
| 696 | } | ||
| 697 | } | ||
| 698 | return Joycon::DriverResult::UnsupportedControllerType; | ||
| 699 | } | ||
| 700 | |||
| 701 | DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info, | ||
| 702 | SerialNumber& serial_number) { | ||
| 703 | if (device_info->serial_number == nullptr) { | ||
| 704 | return DriverResult::Unknown; | ||
| 705 | } | ||
| 706 | std::memcpy(&serial_number, device_info->serial_number, 15); | ||
| 707 | return Joycon::DriverResult::Success; | ||
| 708 | } | ||
| 709 | |||
| 710 | } // namespace InputCommon::Joycon | ||