summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/input_common/CMakeLists.txt5
-rw-r--r--src/input_common/drivers/joycon.cpp615
-rw-r--r--src/input_common/drivers/joycon.h107
-rw-r--r--src/input_common/helpers/joycon_driver.cpp382
-rw-r--r--src/input_common/helpers/joycon_driver.h146
-rw-r--r--src/input_common/helpers/joycon_protocol/joycon_types.h494
-rw-r--r--src/input_common/main.cpp14
7 files changed, 1763 insertions, 0 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index cef2c4d52..41885d0d2 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -51,8 +51,13 @@ endif()
51 51
52if (ENABLE_SDL2) 52if (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/joycon_types.h
56 ) 61 )
57 target_link_libraries(input_common PRIVATE SDL2::SDL2) 62 target_link_libraries(input_common PRIVATE SDL2::SDL2)
58 target_compile_definitions(input_common PRIVATE HAVE_SDL2) 63 target_compile_definitions(input_common PRIVATE HAVE_SDL2)
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
new file mode 100644
index 000000000..eab10d11c
--- /dev/null
+++ b/src/input_common/drivers/joycon.cpp
@@ -0,0 +1,615 @@
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/settings.h"
8#include "common/thread.h"
9#include "input_common/drivers/joycon.h"
10#include "input_common/helpers/joycon_driver.h"
11#include "input_common/helpers/joycon_protocol/joycon_types.h"
12
13namespace InputCommon {
14
15Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
16 LOG_INFO(Input, "Joycon driver Initialization started");
17 const int init_res = SDL_hid_init();
18 if (init_res == 0) {
19 Setup();
20 } else {
21 LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
22 }
23}
24
25Joycons::~Joycons() {
26 Reset();
27}
28
29void Joycons::Reset() {
30 scan_thread = {};
31 for (const auto& device : left_joycons) {
32 if (!device) {
33 continue;
34 }
35 device->Stop();
36 }
37 for (const auto& device : right_joycons) {
38 if (!device) {
39 continue;
40 }
41 device->Stop();
42 }
43 for (const auto& device : pro_joycons) {
44 if (!device) {
45 continue;
46 }
47 device->Stop();
48 }
49 SDL_hid_exit();
50}
51
52void Joycons::Setup() {
53 u32 port = 0;
54 for (auto& device : left_joycons) {
55 PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
56 device = std::make_shared<Joycon::JoyconDriver>(port++);
57 }
58 for (auto& device : right_joycons) {
59 PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
60 device = std::make_shared<Joycon::JoyconDriver>(port++);
61 }
62 for (auto& device : pro_joycons) {
63 PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro));
64 device = std::make_shared<Joycon::JoyconDriver>(port++);
65 }
66
67 if (!scan_thread_running) {
68 scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
69 }
70}
71
72void Joycons::ScanThread(std::stop_token stop_token) {
73 constexpr u16 nintendo_vendor_id = 0x057e;
74 Common::SetCurrentThreadName("yuzu:input:JoyconScanThread");
75 scan_thread_running = true;
76 while (!stop_token.stop_requested()) {
77 SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
78 SDL_hid_device_info* cur_dev = devs;
79
80 while (cur_dev) {
81 if (IsDeviceNew(cur_dev)) {
82 LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
83 cur_dev->product_id);
84 RegisterNewDevice(cur_dev);
85 }
86 cur_dev = cur_dev->next;
87 }
88
89 std::this_thread::sleep_for(std::chrono::seconds(5));
90 }
91 scan_thread_running = false;
92}
93
94bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
95 Joycon::ControllerType type{};
96 Joycon::SerialNumber serial_number{};
97
98 const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
99 if (result != Joycon::DriverResult::Success) {
100 return false;
101 }
102
103 const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
104 if (result2 != Joycon::DriverResult::Success) {
105 return false;
106 }
107
108 auto is_handle_identical = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
109 if (!device) {
110 return false;
111 }
112 if (!device->IsConnected()) {
113 return false;
114 }
115 if (device->GetHandleSerialNumber() != serial_number) {
116 return false;
117 }
118 return true;
119 };
120
121 // Check if device already exist
122 switch (type) {
123 case Joycon::ControllerType::Left:
124 for (const auto& device : left_joycons) {
125 if (is_handle_identical(device)) {
126 return false;
127 }
128 }
129 break;
130 case Joycon::ControllerType::Right:
131 for (const auto& device : right_joycons) {
132 if (is_handle_identical(device)) {
133 return false;
134 }
135 }
136 break;
137 case Joycon::ControllerType::Pro:
138 case Joycon::ControllerType::Grip:
139 for (const auto& device : pro_joycons) {
140 if (is_handle_identical(device)) {
141 return false;
142 }
143 }
144 break;
145 default:
146 return false;
147 }
148
149 return true;
150}
151
152void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
153 Joycon::ControllerType type{};
154 auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
155 auto handle = GetNextFreeHandle(type);
156 if (handle == nullptr) {
157 LOG_WARNING(Input, "No free handles available");
158 return;
159 }
160 if (result == Joycon::DriverResult::Success) {
161 result = handle->RequestDeviceAccess(device_info);
162 }
163 if (result == Joycon::DriverResult::Success) {
164 LOG_WARNING(Input, "Initialize device");
165
166 std::function<void(Joycon::Battery)> on_battery_data;
167 std::function<void(Joycon::Color)> on_button_data;
168 std::function<void(int, f32)> on_stick_data;
169 std::function<void(int, std::array<u8, 6>)> on_motion_data;
170 std::function<void(s16)> on_ring_data;
171 std::function<void(const std::vector<u8>&)> on_amiibo_data;
172
173 const std::size_t port = handle->GetDevicePort();
174 handle->on_battery_data = {
175 [this, port, type](Joycon::Battery value) { OnBatteryUpdate(port, type, value); }};
176 handle->on_color_data = {
177 [this, port, type](Joycon::Color value) { OnColorUpdate(port, type, value); }};
178 handle->on_button_data = {
179 [this, port, type](int id, bool value) { OnButtonUpdate(port, type, id, value); }};
180 handle->on_stick_data = {
181 [this, port, type](int id, f32 value) { OnStickUpdate(port, type, id, value); }};
182 handle->on_motion_data = {[this, port, type](int id, Joycon::MotionData value) {
183 OnMotionUpdate(port, type, id, value);
184 }};
185 handle->on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }};
186 handle->on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
187 OnAmiiboUpdate(port, amiibo_data);
188 }};
189 handle->InitializeDevice();
190 }
191}
192
193std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
194 Joycon::ControllerType type) const {
195
196 if (type == Joycon::ControllerType::Left) {
197 for (const auto& device : left_joycons) {
198 if (!device->IsConnected()) {
199 return device;
200 }
201 }
202 }
203 if (type == Joycon::ControllerType::Right) {
204 for (const auto& device : right_joycons) {
205 if (!device->IsConnected()) {
206 return device;
207 }
208 }
209 }
210 if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
211 for (const auto& device : pro_joycons) {
212 if (!device->IsConnected()) {
213 return device;
214 }
215 }
216 }
217 return nullptr;
218}
219
220bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
221 const auto handle = GetHandle(identifier);
222 if (handle == nullptr) {
223 return false;
224 }
225 return handle->IsVibrationEnabled();
226}
227
228Common::Input::VibrationError Joycons::SetVibration(
229 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
230 const Joycon::VibrationValue native_vibration{
231 .low_amplitude = vibration.low_amplitude,
232 .low_frequency = vibration.low_frequency,
233 .high_amplitude = vibration.high_amplitude,
234 .high_frequency = vibration.high_amplitude,
235 };
236 auto handle = GetHandle(identifier);
237 if (handle == nullptr) {
238 return Common::Input::VibrationError::InvalidHandle;
239 }
240
241 handle->SetVibration(native_vibration);
242 return Common::Input::VibrationError::None;
243}
244
245void Joycons::SetLeds(const PadIdentifier& identifier, const Common::Input::LedStatus& led_status) {
246 auto handle = GetHandle(identifier);
247 if (handle == nullptr) {
248 return;
249 }
250 int led_config = led_status.led_1 ? 1 : 0;
251 led_config += led_status.led_2 ? 2 : 0;
252 led_config += led_status.led_3 ? 4 : 0;
253 led_config += led_status.led_4 ? 8 : 0;
254
255 const auto result = handle->SetLedConfig(static_cast<u8>(led_config));
256 if (result != Joycon::DriverResult::Success) {
257 LOG_ERROR(Input, "Failed to set led config");
258 }
259}
260
261Common::Input::CameraError Joycons::SetCameraFormat(const PadIdentifier& identifier_,
262 Common::Input::CameraFormat camera_format) {
263 return Common::Input::CameraError::NotSupported;
264};
265
266Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
267 return Common::Input::NfcState::Success;
268};
269
270Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
271 const std::vector<u8>& data) {
272 return Common::Input::NfcState::NotSupported;
273};
274
275Common::Input::PollingError Joycons::SetPollingMode(const PadIdentifier& identifier,
276 const Common::Input::PollingMode polling_mode) {
277 auto handle = GetHandle(identifier);
278 if (handle == nullptr) {
279 LOG_ERROR(Input, "Invalid handle {}", identifier.port);
280 return Common::Input::PollingError::InvalidHandle;
281 }
282
283 switch (polling_mode) {
284 case Common::Input::PollingMode::NFC:
285 handle->SetNfcMode();
286 break;
287 case Common::Input::PollingMode::Active:
288 handle->SetActiveMode();
289 break;
290 case Common::Input::PollingMode::Pasive:
291 handle->SetPasiveMode();
292 break;
293 case Common::Input::PollingMode::Ring:
294 handle->SetRingConMode();
295 break;
296 default:
297 return Common::Input::PollingError::NotSupported;
298 }
299
300 return Common::Input::PollingError::None;
301}
302
303void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
304 Joycon::Battery value) {
305 const auto identifier = GetIdentifier(port, type);
306 if (value.charging != 0) {
307 SetBattery(identifier, Common::Input::BatteryLevel::Charging);
308 return;
309 }
310
311 Common::Input::BatteryLevel battery{value.status.Value()};
312 switch (value.status) {
313 case 0:
314 battery = Common::Input::BatteryLevel::Empty;
315 break;
316 case 1:
317 battery = Common::Input::BatteryLevel::Critical;
318 break;
319 case 2:
320 battery = Common::Input::BatteryLevel::Low;
321 break;
322 case 3:
323 battery = Common::Input::BatteryLevel::Medium;
324 break;
325 case 4:
326 default:
327 battery = Common::Input::BatteryLevel::Full;
328 break;
329 }
330 SetBattery(identifier, battery);
331}
332
333void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
334 const Joycon::Color& value) {}
335
336void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
337 const auto identifier = GetIdentifier(port, type);
338 SetButton(identifier, id, value);
339}
340
341void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
342 const auto identifier = GetIdentifier(port, type);
343 SetAxis(identifier, id, value);
344}
345
346void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
347 const Joycon::MotionData& value) {
348 const auto identifier = GetIdentifier(port, type);
349 BasicMotion motion_data{
350 .gyro_x = value.gyro_x,
351 .gyro_y = value.gyro_y,
352 .gyro_z = value.gyro_z,
353 .accel_x = value.accel_x,
354 .accel_y = value.accel_y,
355 .accel_z = value.accel_z,
356 .delta_timestamp = 15000,
357 };
358 SetMotion(identifier, id, motion_data);
359}
360
361void Joycons::OnRingConUpdate(f32 ring_data) {
362 // To simplify ring detection it will always be mapped to an empty identifier for all
363 // controllers
364 constexpr PadIdentifier identifier = {
365 .guid = Common::UUID{},
366 .port = 0,
367 .pad = 0,
368 };
369 SetAxis(identifier, 100, ring_data);
370}
371
372void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
373 const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
374 SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
375}
376
377std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
378 auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
379 if (!device) {
380 return false;
381 }
382 if (!device->IsConnected()) {
383 return false;
384 }
385 if (device->GetDevicePort() == identifier.port) {
386 return true;
387 }
388 return false;
389 };
390 const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
391 if (type == Joycon::ControllerType::Left) {
392 for (const auto& device : left_joycons) {
393 if (is_handle_active(device)) {
394 return device;
395 }
396 }
397 }
398 if (type == Joycon::ControllerType::Right) {
399 for (const auto& device : right_joycons) {
400 if (is_handle_active(device)) {
401 return device;
402 }
403 }
404 }
405 if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
406 for (const auto& device : pro_joycons) {
407 if (is_handle_active(device)) {
408 return device;
409 }
410 }
411 }
412 return nullptr;
413}
414
415PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
416 return {
417 .guid = Common::UUID{Common::InvalidUUID},
418 .port = port,
419 .pad = static_cast<std::size_t>(type),
420 };
421}
422
423std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
424 std::vector<Common::ParamPackage> devices{};
425
426 auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
427 if (!device) {
428 return;
429 }
430 if (!device->IsConnected()) {
431 return;
432 }
433 std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
434 device->GetDevicePort());
435 devices.emplace_back(Common::ParamPackage{
436 {"engine", GetEngineName()},
437 {"display", std::move(name)},
438 {"port", std::to_string(device->GetDevicePort())},
439 {"pad", std::to_string(static_cast<std::size_t>(device->GetHandleDeviceType()))},
440 });
441 };
442
443 for (const auto& controller : left_joycons) {
444 add_entry(controller);
445 }
446 for (const auto& controller : right_joycons) {
447 add_entry(controller);
448 }
449 for (const auto& controller : pro_joycons) {
450 add_entry(controller);
451 }
452
453 return devices;
454}
455
456ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
457 static constexpr std::array<std::pair<Settings::NativeButton::Values, Joycon::PadButton>, 20>
458 switch_to_joycon_button = {
459 std::pair{Settings::NativeButton::A, Joycon::PadButton::A},
460 {Settings::NativeButton::B, Joycon::PadButton::B},
461 {Settings::NativeButton::X, Joycon::PadButton::X},
462 {Settings::NativeButton::Y, Joycon::PadButton::Y},
463 {Settings::NativeButton::DLeft, Joycon::PadButton::Left},
464 {Settings::NativeButton::DUp, Joycon::PadButton::Up},
465 {Settings::NativeButton::DRight, Joycon::PadButton::Right},
466 {Settings::NativeButton::DDown, Joycon::PadButton::Down},
467 {Settings::NativeButton::SL, Joycon::PadButton::LeftSL},
468 {Settings::NativeButton::SR, Joycon::PadButton::LeftSR},
469 {Settings::NativeButton::L, Joycon::PadButton::L},
470 {Settings::NativeButton::R, Joycon::PadButton::R},
471 {Settings::NativeButton::ZL, Joycon::PadButton::ZL},
472 {Settings::NativeButton::ZR, Joycon::PadButton::ZR},
473 {Settings::NativeButton::Plus, Joycon::PadButton::Plus},
474 {Settings::NativeButton::Minus, Joycon::PadButton::Minus},
475 {Settings::NativeButton::Home, Joycon::PadButton::Home},
476 {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture},
477 {Settings::NativeButton::LStick, Joycon::PadButton::StickL},
478 {Settings::NativeButton::RStick, Joycon::PadButton::StickR},
479 };
480
481 if (!params.Has("port")) {
482 return {};
483 }
484
485 ButtonMapping mapping{};
486 for (const auto& [switch_button, joycon_button] : switch_to_joycon_button) {
487 Common::ParamPackage button_params{};
488 button_params.Set("engine", GetEngineName());
489 button_params.Set("port", params.Get("port", 0));
490 button_params.Set("button", static_cast<int>(joycon_button));
491 mapping.insert_or_assign(switch_button, std::move(button_params));
492 }
493
494 return mapping;
495}
496
497AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
498 if (!params.Has("port")) {
499 return {};
500 }
501
502 AnalogMapping mapping = {};
503 Common::ParamPackage left_analog_params;
504 left_analog_params.Set("engine", GetEngineName());
505 left_analog_params.Set("port", params.Get("port", 0));
506 left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
507 left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
508 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
509 Common::ParamPackage right_analog_params;
510 right_analog_params.Set("engine", GetEngineName());
511 right_analog_params.Set("port", params.Get("port", 0));
512 right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
513 right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
514 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
515 return mapping;
516}
517
518MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
519 if (!params.Has("port")) {
520 return {};
521 }
522
523 MotionMapping mapping = {};
524 Common::ParamPackage left_motion_params;
525 left_motion_params.Set("engine", GetEngineName());
526 left_motion_params.Set("port", params.Get("port", 0));
527 left_motion_params.Set("motion", 0);
528 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
529 Common::ParamPackage right_Motion_params;
530 right_Motion_params.Set("engine", GetEngineName());
531 right_Motion_params.Set("port", params.Get("port", 0));
532 right_Motion_params.Set("motion", 1);
533 mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
534 return mapping;
535}
536
537Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
538 const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
539 switch (button) {
540 case Joycon::PadButton::Left:
541 return Common::Input::ButtonNames::ButtonLeft;
542 case Joycon::PadButton::Right:
543 return Common::Input::ButtonNames::ButtonRight;
544 case Joycon::PadButton::Down:
545 return Common::Input::ButtonNames::ButtonDown;
546 case Joycon::PadButton::Up:
547 return Common::Input::ButtonNames::ButtonUp;
548 case Joycon::PadButton::LeftSL:
549 case Joycon::PadButton::RightSL:
550 return Common::Input::ButtonNames::TriggerSL;
551 case Joycon::PadButton::LeftSR:
552 case Joycon::PadButton::RightSR:
553 return Common::Input::ButtonNames::TriggerSR;
554 case Joycon::PadButton::L:
555 return Common::Input::ButtonNames::TriggerL;
556 case Joycon::PadButton::R:
557 return Common::Input::ButtonNames::TriggerR;
558 case Joycon::PadButton::ZL:
559 return Common::Input::ButtonNames::TriggerZL;
560 case Joycon::PadButton::ZR:
561 return Common::Input::ButtonNames::TriggerZR;
562 case Joycon::PadButton::A:
563 return Common::Input::ButtonNames::ButtonA;
564 case Joycon::PadButton::B:
565 return Common::Input::ButtonNames::ButtonB;
566 case Joycon::PadButton::X:
567 return Common::Input::ButtonNames::ButtonX;
568 case Joycon::PadButton::Y:
569 return Common::Input::ButtonNames::ButtonY;
570 case Joycon::PadButton::Plus:
571 return Common::Input::ButtonNames::ButtonPlus;
572 case Joycon::PadButton::Minus:
573 return Common::Input::ButtonNames::ButtonMinus;
574 case Joycon::PadButton::Home:
575 return Common::Input::ButtonNames::ButtonHome;
576 case Joycon::PadButton::Capture:
577 return Common::Input::ButtonNames::ButtonCapture;
578 case Joycon::PadButton::StickL:
579 return Common::Input::ButtonNames::ButtonStickL;
580 case Joycon::PadButton::StickR:
581 return Common::Input::ButtonNames::ButtonStickR;
582 default:
583 return Common::Input::ButtonNames::Undefined;
584 }
585}
586
587Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
588 if (params.Has("button")) {
589 return GetUIButtonName(params);
590 }
591 if (params.Has("axis")) {
592 return Common::Input::ButtonNames::Value;
593 }
594 if (params.Has("motion")) {
595 return Common::Input::ButtonNames::Engine;
596 }
597
598 return Common::Input::ButtonNames::Invalid;
599}
600
601std::string Joycons::JoyconName(Joycon::ControllerType type) const {
602 switch (type) {
603 case Joycon::ControllerType::Left:
604 return "Left Joycon";
605 case Joycon::ControllerType::Right:
606 return "Right Joycon";
607 case Joycon::ControllerType::Pro:
608 return "Pro Controller";
609 case Joycon::ControllerType::Grip:
610 return "Grip Controller";
611 default:
612 return "Unknow Joycon";
613 }
614}
615} // namespace InputCommon
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
new file mode 100644
index 000000000..56c117270
--- /dev/null
+++ b/src/input_common/drivers/joycon.h
@@ -0,0 +1,107 @@
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
13namespace InputCommon::Joycon {
14using SerialNumber = std::array<u8, 15>;
15struct Battery;
16struct Color;
17struct MotionData;
18enum class ControllerType;
19enum class DriverResult;
20class JoyconDriver;
21} // namespace InputCommon::Joycon
22
23namespace InputCommon {
24
25class Joycons final : public InputCommon::InputEngine {
26public:
27 explicit Joycons(const std::string& input_engine_);
28
29 ~Joycons();
30
31 bool IsVibrationEnabled(const PadIdentifier& identifier) override;
32 Common::Input::VibrationError SetVibration(
33 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
34
35 void SetLeds(const PadIdentifier& identifier,
36 const Common::Input::LedStatus& led_status) override;
37
38 Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
39 Common::Input::CameraFormat camera_format) override;
40
41 Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
42 Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
43 const std::vector<u8>& data) override;
44
45 Common::Input::PollingError SetPollingMode(
46 const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
47
48 /// Used for automapping features
49 std::vector<Common::ParamPackage> GetInputDevices() const override;
50 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
51 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
52 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
53 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
54
55private:
56 static constexpr std::size_t MaxSupportedControllers = 8;
57
58 /// For shutting down, clear all data, join all threads, release usb devices
59 void Reset();
60
61 /// Registers controllers, clears all data and starts the scan thread
62 void Setup();
63
64 /// Actively searchs for new devices
65 void ScanThread(std::stop_token stop_token);
66
67 /// Returns true if device is valid and not registered
68 bool IsDeviceNew(SDL_hid_device_info* device_info) const;
69
70 /// Tries to connect to the new device
71 void RegisterNewDevice(SDL_hid_device_info* device_info);
72
73 /// Returns the next free handle
74 std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const;
75
76 void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value);
77 void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value);
78 void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value);
79 void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value);
80 void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
81 const Joycon::MotionData& value);
82 void OnRingConUpdate(f32 ring_data);
83 void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
84
85 /// Returns a JoyconHandle corresponding to a PadIdentifier
86 std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
87
88 /// Returns a PadIdentifier corresponding to the port number
89 PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
90
91 std::string JoyconName(std::size_t port) const;
92
93 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
94
95 /// Returns the name of the device in text format
96 std::string JoyconName(Joycon::ControllerType type) const;
97
98 std::jthread scan_thread;
99 bool scan_thread_running{};
100
101 // Joycon types are split by type to ease supporting dualjoycon configurations
102 std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
103 std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
104 std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> pro_joycons{};
105};
106
107} // namespace InputCommon
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
new file mode 100644
index 000000000..a0a2a180b
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,382 @@
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
9namespace InputCommon::Joycon {
10JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
11 hidapi_handle = std::make_shared<JoyconHandle>();
12}
13
14JoyconDriver::~JoyconDriver() {
15 Stop();
16}
17
18void JoyconDriver::Stop() {
19 is_connected = false;
20 input_thread = {};
21}
22
23DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
24 std::scoped_lock lock{mutex};
25
26 handle_device_type = ControllerType::None;
27 GetDeviceType(device_info, handle_device_type);
28 if (handle_device_type == ControllerType::None) {
29 return DriverResult::UnsupportedControllerType;
30 }
31
32 hidapi_handle->handle =
33 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
34 std::memcpy(&handle_serial_number, device_info->serial_number, 15);
35 if (!hidapi_handle->handle) {
36 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
37 device_info->vendor_id, device_info->product_id);
38 return DriverResult::HandleInUse;
39 }
40 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
41 return DriverResult::Success;
42}
43
44DriverResult JoyconDriver::InitializeDevice() {
45 if (!hidapi_handle->handle) {
46 return DriverResult::InvalidHandle;
47 }
48 std::scoped_lock lock{mutex};
49 disable_input_thread = true;
50
51 // Reset Counters
52 error_counter = 0;
53 hidapi_handle->packet_counter = 0;
54
55 // Set HW default configuration
56 vibration_enabled = true;
57 motion_enabled = true;
58 hidbus_enabled = false;
59 nfc_enabled = false;
60 passive_enabled = false;
61 gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
62 gyro_performance = Joycon::GyroPerformance::HZ833;
63 accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
64 accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
65
66 // Initialize HW Protocols
67
68 // Get fixed joycon info
69 supported_features = GetSupportedFeatures();
70
71 // Get Calibration data
72
73 // Set led status
74
75 // Apply HW configuration
76 SetPollingMode();
77
78 // Start pooling for data
79 is_connected = true;
80 if (!input_thread_running) {
81 input_thread =
82 std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
83 }
84
85 disable_input_thread = false;
86 return DriverResult::Success;
87}
88
89void JoyconDriver::InputThread(std::stop_token stop_token) {
90 LOG_INFO(Input, "JC Adapter input thread started");
91 Common::SetCurrentThreadName("JoyconInput");
92 input_thread_running = true;
93
94 // Max update rate is 5ms, ensure we are always able to read a bit faster
95 constexpr int ThreadDelay = 2;
96 std::vector<u8> buffer(MaxBufferSize);
97
98 while (!stop_token.stop_requested()) {
99 int status = 0;
100
101 if (!IsInputThreadValid()) {
102 input_thread.request_stop();
103 continue;
104 }
105
106 // By disabling the input thread we can ensure custom commands will succeed as no package is
107 // skipped
108 if (!disable_input_thread) {
109 status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
110 ThreadDelay);
111 } else {
112 std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
113 }
114
115 if (IsPayloadCorrect(status, buffer)) {
116 OnNewData(buffer);
117 }
118
119 std::this_thread::yield();
120 }
121
122 is_connected = false;
123 input_thread_running = false;
124 LOG_INFO(Input, "JC Adapter input thread stopped");
125}
126
127void JoyconDriver::OnNewData(std::span<u8> buffer) {
128 const auto report_mode = static_cast<InputReport>(buffer[0]);
129
130 switch (report_mode) {
131 case InputReport::STANDARD_FULL_60HZ:
132 ReadActiveMode(buffer);
133 break;
134 case InputReport::NFC_IR_MODE_60HZ:
135 ReadNfcIRMode(buffer);
136 break;
137 case InputReport::SIMPLE_HID_MODE:
138 ReadPassiveMode(buffer);
139 break;
140 default:
141 LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
142 break;
143 }
144}
145
146void JoyconDriver::SetPollingMode() {
147 disable_input_thread = true;
148 disable_input_thread = false;
149}
150
151JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
152 SupportedFeatures features{
153 .passive = true,
154 .motion = true,
155 .vibration = true,
156 };
157
158 if (device_type == ControllerType::Right) {
159 features.nfc = true;
160 features.irs = true;
161 features.hidbus = true;
162 }
163
164 if (device_type == ControllerType::Pro) {
165 features.nfc = true;
166 }
167 return features;
168}
169
170void JoyconDriver::ReadActiveMode(std::span<u8> buffer) {
171 InputReportActive data{};
172 memcpy(&data, buffer.data(), sizeof(InputReportActive));
173
174 // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
175 // experience
176 const auto now = std::chrono::steady_clock::now();
177 const auto new_delta_time =
178 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count();
179 delta_time = static_cast<u64>((delta_time * 0.8f) + (new_delta_time * 0.2));
180 last_update = now;
181
182 switch (device_type) {
183 case Joycon::ControllerType::Left:
184 break;
185 case Joycon::ControllerType::Right:
186 break;
187 case Joycon::ControllerType::Pro:
188 break;
189 case Joycon::ControllerType::Grip:
190 case Joycon::ControllerType::Dual:
191 case Joycon::ControllerType::None:
192 break;
193 }
194
195 on_battery_data(data.battery_status);
196 on_color_data(color);
197}
198
199void JoyconDriver::ReadPassiveMode(std::span<u8> buffer) {
200 InputReportPassive data{};
201 memcpy(&data, buffer.data(), sizeof(InputReportPassive));
202
203 switch (device_type) {
204 case Joycon::ControllerType::Left:
205 break;
206 case Joycon::ControllerType::Right:
207 break;
208 case Joycon::ControllerType::Pro:
209 break;
210 case Joycon::ControllerType::Grip:
211 case Joycon::ControllerType::Dual:
212 case Joycon::ControllerType::None:
213 break;
214 }
215}
216
217void JoyconDriver::ReadNfcIRMode(std::span<u8> buffer) {
218 // This mode is compatible with the active mode
219 ReadActiveMode(buffer);
220
221 if (!nfc_enabled) {
222 return;
223 }
224}
225
226bool JoyconDriver::IsInputThreadValid() const {
227 if (!is_connected) {
228 return false;
229 }
230 if (hidapi_handle->handle == nullptr) {
231 return false;
232 }
233 // Controller is not responding. Terminate connection
234 if (error_counter > MaxErrorCount) {
235 return false;
236 }
237 return true;
238}
239
240bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
241 if (status <= -1) {
242 error_counter++;
243 return false;
244 }
245 // There's no new data
246 if (status == 0) {
247 return false;
248 }
249 // No reply ever starts with zero
250 if (buffer[0] == 0x00) {
251 error_counter++;
252 return false;
253 }
254 error_counter = 0;
255 return true;
256}
257
258DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
259 std::scoped_lock lock{mutex};
260 return DriverResult::NotSupported;
261}
262
263DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
264 std::scoped_lock lock{mutex};
265 return DriverResult::NotSupported;
266}
267
268DriverResult JoyconDriver::SetPasiveMode() {
269 motion_enabled = false;
270 hidbus_enabled = false;
271 nfc_enabled = false;
272 passive_enabled = true;
273 SetPollingMode();
274 return DriverResult::Success;
275}
276
277DriverResult JoyconDriver::SetActiveMode() {
278 motion_enabled = false;
279 hidbus_enabled = false;
280 nfc_enabled = false;
281 passive_enabled = false;
282 SetPollingMode();
283 return DriverResult::Success;
284}
285
286DriverResult JoyconDriver::SetNfcMode() {
287 motion_enabled = false;
288 hidbus_enabled = false;
289 nfc_enabled = true;
290 passive_enabled = false;
291 SetPollingMode();
292 return DriverResult::Success;
293}
294
295DriverResult JoyconDriver::SetRingConMode() {
296 motion_enabled = true;
297 hidbus_enabled = true;
298 nfc_enabled = false;
299 passive_enabled = false;
300 SetPollingMode();
301 return DriverResult::Success;
302}
303
304bool JoyconDriver::IsConnected() const {
305 std::scoped_lock lock{mutex};
306 return is_connected;
307}
308
309bool JoyconDriver::IsVibrationEnabled() const {
310 std::scoped_lock lock{mutex};
311 return vibration_enabled;
312}
313
314FirmwareVersion JoyconDriver::GetDeviceVersion() const {
315 std::scoped_lock lock{mutex};
316 return version;
317}
318
319Color JoyconDriver::GetDeviceColor() const {
320 std::scoped_lock lock{mutex};
321 return color;
322}
323
324std::size_t JoyconDriver::GetDevicePort() const {
325 std::scoped_lock lock{mutex};
326 return port;
327}
328
329ControllerType JoyconDriver::GetDeviceType() const {
330 std::scoped_lock lock{mutex};
331 return handle_device_type;
332}
333
334ControllerType JoyconDriver::GetHandleDeviceType() const {
335 std::scoped_lock lock{mutex};
336 return handle_device_type;
337}
338
339SerialNumber JoyconDriver::GetSerialNumber() const {
340 std::scoped_lock lock{mutex};
341 return serial_number;
342}
343
344SerialNumber JoyconDriver::GetHandleSerialNumber() const {
345 std::scoped_lock lock{mutex};
346 return handle_serial_number;
347}
348
349Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
350 ControllerType& controller_type) {
351 std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
352 std::pair<u32, Joycon::ControllerType>{0x2006, Joycon::ControllerType::Left},
353 {0x2007, Joycon::ControllerType::Right},
354 {0x2009, Joycon::ControllerType::Pro},
355 {0x200E, Joycon::ControllerType::Grip},
356 };
357 constexpr u16 nintendo_vendor_id = 0x057e;
358
359 controller_type = Joycon::ControllerType::None;
360 if (device_info->vendor_id != nintendo_vendor_id) {
361 return Joycon::DriverResult::UnsupportedControllerType;
362 }
363
364 for (const auto& [product_id, type] : supported_devices) {
365 if (device_info->product_id == static_cast<u16>(product_id)) {
366 controller_type = type;
367 return Joycon::DriverResult::Success;
368 }
369 }
370 return Joycon::DriverResult::UnsupportedControllerType;
371}
372
373Joycon::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
374 Joycon::SerialNumber& serial_number) {
375 if (device_info->serial_number == nullptr) {
376 return Joycon::DriverResult::Unknown;
377 }
378 std::memcpy(&serial_number, device_info->serial_number, 15);
379 return Joycon::DriverResult::Success;
380}
381
382} // 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..be3053a7b
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.h
@@ -0,0 +1,146 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <functional>
7#include <mutex>
8#include <span>
9#include <thread>
10
11#include "input_common/helpers/joycon_protocol/joycon_types.h"
12
13namespace InputCommon::Joycon {
14
15class JoyconDriver final {
16public:
17 explicit JoyconDriver(std::size_t port_);
18
19 ~JoyconDriver();
20
21 DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
22 DriverResult InitializeDevice();
23 void Stop();
24
25 bool IsConnected() const;
26 bool IsVibrationEnabled() const;
27
28 FirmwareVersion GetDeviceVersion() const;
29 Color GetDeviceColor() const;
30 std::size_t GetDevicePort() const;
31 ControllerType GetDeviceType() const;
32 ControllerType GetHandleDeviceType() const;
33 SerialNumber GetSerialNumber() const;
34 SerialNumber GetHandleSerialNumber() const;
35
36 DriverResult SetVibration(const VibrationValue& vibration);
37 DriverResult SetLedConfig(u8 led_pattern);
38 DriverResult SetPasiveMode();
39 DriverResult SetActiveMode();
40 DriverResult SetNfcMode();
41 DriverResult SetRingConMode();
42
43 // Returns device type from hidapi handle
44 static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info,
45 Joycon::ControllerType& controller_type);
46
47 // Returns serial number from hidapi handle
48 static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
49 Joycon::SerialNumber& serial_number);
50
51 std::function<void(Battery)> on_battery_data;
52 std::function<void(Color)> on_color_data;
53 std::function<void(int, bool)> on_button_data;
54 std::function<void(int, f32)> on_stick_data;
55 std::function<void(int, MotionData)> on_motion_data;
56 std::function<void(f32)> on_ring_data;
57 std::function<void(const std::vector<u8>&)> on_amiibo_data;
58
59private:
60 struct SupportedFeatures {
61 bool passive{};
62 bool hidbus{};
63 bool irs{};
64 bool motion{};
65 bool nfc{};
66 bool vibration{};
67 };
68
69 /// Main thread, actively request new data from the handle
70 void InputThread(std::stop_token stop_token);
71
72 /// Called everytime a valid package arrives
73 void OnNewData(std::span<u8> buffer);
74
75 /// Updates device configuration to enable or disable features
76 void SetPollingMode();
77
78 /// Returns true if input thread is valid and doesn't need to be stopped
79 bool IsInputThreadValid() const;
80
81 /// Returns true if the data should be interpreted. Otherwise the error counter is incremented
82 bool IsPayloadCorrect(int status, std::span<const u8> buffer);
83
84 /// Returns a list of supported features that can be enabled on this device
85 SupportedFeatures GetSupportedFeatures();
86
87 /// Handles data from passive packages
88 void ReadPassiveMode(std::span<u8> buffer);
89
90 /// Handles data from active packages
91 void ReadActiveMode(std::span<u8> buffer);
92
93 /// Handles data from nfc or ir packages
94 void ReadNfcIRMode(std::span<u8> buffer);
95
96 // Protocol Features
97
98 // Connection status
99 bool is_connected{};
100 u64 delta_time;
101 std::size_t error_counter{};
102 std::shared_ptr<JoyconHandle> hidapi_handle = nullptr;
103 std::chrono::time_point<std::chrono::steady_clock> last_update;
104
105 // External device status
106 bool starlink_connected{};
107 bool ring_connected{};
108 bool amiibo_detected{};
109
110 // Harware configuration
111 u8 leds{};
112 ReportMode mode{};
113 bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
114 bool hidbus_enabled{}; // External device support
115 bool irs_enabled{}; // Infrared camera input
116 bool motion_enabled{}; // Enables motion input
117 bool nfc_enabled{}; // Enables Amiibo detection
118 bool vibration_enabled{}; // Allows vibrations
119
120 // Calibration data
121 GyroSensitivity gyro_sensitivity{};
122 GyroPerformance gyro_performance{};
123 AccelerometerSensitivity accelerometer_sensitivity{};
124 AccelerometerPerformance accelerometer_performance{};
125 JoyStickCalibration left_stick_calibration{};
126 JoyStickCalibration right_stick_calibration{};
127 MotionCalibration motion_calibration{};
128
129 // Fixed joycon info
130 FirmwareVersion version{};
131 Color color{};
132 std::size_t port{};
133 ControllerType device_type{}; // Device type reported by controller
134 ControllerType handle_device_type{}; // Device type reported by hidapi
135 SerialNumber serial_number{}; // Serial number reported by controller
136 SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
137 SupportedFeatures supported_features{};
138
139 // Thread related
140 mutable std::mutex mutex;
141 std::jthread input_thread;
142 bool input_thread_running{};
143 bool disable_input_thread{};
144};
145
146} // 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..de512fe63
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -0,0 +1,494 @@
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
19namespace InputCommon::Joycon {
20constexpr u32 MaxErrorCount = 50;
21constexpr u32 MaxBufferSize = 60;
22constexpr u32 MaxResponseSize = 49;
23constexpr u32 MaxSubCommandResponseSize = 64;
24constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
25
26using MacAddress = std::array<u8, 6>;
27using SerialNumber = std::array<u8, 15>;
28
29enum class ControllerType {
30 None,
31 Left,
32 Right,
33 Pro,
34 Grip,
35 Dual,
36};
37
38enum class PadAxes {
39 LeftStickX,
40 LeftStickY,
41 RightStickX,
42 RightStickY,
43 Undefined,
44};
45
46enum class PadMotion {
47 LeftMotion,
48 RightMotion,
49 Undefined,
50};
51
52enum class PadButton : u32 {
53 Down = 0x000001,
54 Up = 0x000002,
55 Right = 0x000004,
56 Left = 0x000008,
57 LeftSR = 0x000010,
58 LeftSL = 0x000020,
59 L = 0x000040,
60 ZL = 0x000080,
61 Y = 0x000100,
62 X = 0x000200,
63 B = 0x000400,
64 A = 0x000800,
65 RightSR = 0x001000,
66 RightSL = 0x002000,
67 R = 0x004000,
68 ZR = 0x008000,
69 Minus = 0x010000,
70 Plus = 0x020000,
71 StickR = 0x040000,
72 StickL = 0x080000,
73 Home = 0x100000,
74 Capture = 0x200000,
75};
76
77enum class PasivePadButton : u32 {
78 Down_A = 0x0001,
79 Right_X = 0x0002,
80 Left_B = 0x0004,
81 Up_Y = 0x0008,
82 SL = 0x0010,
83 SR = 0x0020,
84 Minus = 0x0100,
85 Plus = 0x0200,
86 StickL = 0x0400,
87 StickR = 0x0800,
88 Home = 0x1000,
89 Capture = 0x2000,
90 L_R = 0x4000,
91 ZL_ZR = 0x8000,
92};
93
94enum class OutputReport : u8 {
95 RUMBLE_AND_SUBCMD = 0x01,
96 FW_UPDATE_PKT = 0x03,
97 RUMBLE_ONLY = 0x10,
98 MCU_DATA = 0x11,
99 USB_CMD = 0x80,
100};
101
102enum class InputReport : u8 {
103 SUBCMD_REPLY = 0x21,
104 STANDARD_FULL_60HZ = 0x30,
105 NFC_IR_MODE_60HZ = 0x31,
106 SIMPLE_HID_MODE = 0x3F,
107 INPUT_USB_RESPONSE = 0x81,
108};
109
110enum class FeatureReport : u8 {
111 Last_SUBCMD = 0x02,
112 OTA_GW_UPGRADE = 0x70,
113 SETUP_MEM_READ = 0x71,
114 MEM_READ = 0x72,
115 ERASE_MEM_SECTOR = 0x73,
116 MEM_WRITE = 0x74,
117 LAUNCH = 0x75,
118};
119
120enum class SubCommand : u8 {
121 STATE = 0x00,
122 MANUAL_BT_PAIRING = 0x01,
123 REQ_DEV_INFO = 0x02,
124 SET_REPORT_MODE = 0x03,
125 TRIGGERS_ELAPSED = 0x04,
126 GET_PAGE_LIST_STATE = 0x05,
127 SET_HCI_STATE = 0x06,
128 RESET_PAIRING_INFO = 0x07,
129 LOW_POWER_MODE = 0x08,
130 SPI_FLASH_READ = 0x10,
131 SPI_FLASH_WRITE = 0x11,
132 RESET_MCU = 0x20,
133 SET_MCU_CONFIG = 0x21,
134 SET_MCU_STATE = 0x22,
135 SET_PLAYER_LIGHTS = 0x30,
136 GET_PLAYER_LIGHTS = 0x31,
137 SET_HOME_LIGHT = 0x38,
138 ENABLE_IMU = 0x40,
139 SET_IMU_SENSITIVITY = 0x41,
140 WRITE_IMU_REG = 0x42,
141 READ_IMU_REG = 0x43,
142 ENABLE_VIBRATION = 0x48,
143 GET_REGULATED_VOLTAGE = 0x50,
144 SET_EXTERNAL_CONFIG = 0x58,
145 UNKNOWN_RINGCON = 0x59,
146 UNKNOWN_RINGCON2 = 0x5A,
147 UNKNOWN_RINGCON3 = 0x5C,
148};
149
150enum class UsbSubCommand : u8 {
151 CONN_STATUS = 0x01,
152 HADSHAKE = 0x02,
153 BAUDRATE_3M = 0x03,
154 NO_TIMEOUT = 0x04,
155 EN_TIMEOUT = 0x05,
156 RESET = 0x06,
157 PRE_HANDSHAKE = 0x91,
158 SEND_UART = 0x92,
159};
160
161enum class CalMagic : u8 {
162 USR_MAGIC_0 = 0xB2,
163 USR_MAGIC_1 = 0xA1,
164 USRR_MAGI_SIZE = 2,
165};
166
167enum class CalAddr {
168 SERIAL_NUMBER = 0X6000,
169 DEVICE_TYPE = 0X6012,
170 COLOR_EXIST = 0X601B,
171 FACT_LEFT_DATA = 0X603d,
172 FACT_RIGHT_DATA = 0X6046,
173 COLOR_DATA = 0X6050,
174 FACT_IMU_DATA = 0X6020,
175 USER_LEFT_MAGIC = 0X8010,
176 USER_LEFT_DATA = 0X8012,
177 USER_RIGHT_MAGIC = 0X801B,
178 USER_RIGHT_DATA = 0X801D,
179 USER_IMU_MAGIC = 0X8026,
180 USER_IMU_DATA = 0X8028,
181};
182
183enum class ReportMode : u8 {
184 ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
185 ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
186 ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
187 ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
188 MCU_UPDATE_STATE = 0x23,
189 STANDARD_FULL_60HZ = 0x30,
190 NFC_IR_MODE_60HZ = 0x31,
191 SIMPLE_HID_MODE = 0x3F,
192};
193
194enum class GyroSensitivity : u8 {
195 DPS250,
196 DPS500,
197 DPS1000,
198 DPS2000, // Default
199};
200
201enum class AccelerometerSensitivity : u8 {
202 G8, // Default
203 G4,
204 G2,
205 G16,
206};
207
208enum class GyroPerformance : u8 {
209 HZ833,
210 HZ208, // Default
211};
212
213enum class AccelerometerPerformance : u8 {
214 HZ200,
215 HZ100, // Default
216};
217
218enum class MCUCommand : u8 {
219 ConfigureMCU = 0x21,
220 ConfigureIR = 0x23,
221};
222
223enum class MCUSubCommand : u8 {
224 SetMCUMode = 0x0,
225 SetDeviceMode = 0x1,
226 ReadDeviceMode = 0x02,
227 WriteDeviceRegisters = 0x4,
228};
229
230enum class MCUMode : u8 {
231 Suspend = 0,
232 Standby = 1,
233 Ringcon = 3,
234 NFC = 4,
235 IR = 5,
236 MaybeFWUpdate = 6,
237};
238
239enum class MCURequest : u8 {
240 GetMCUStatus = 1,
241 GetNFCData = 2,
242 GetIRData = 3,
243};
244
245enum class MCUReport : u8 {
246 Empty = 0x00,
247 StateReport = 0x01,
248 IRData = 0x03,
249 BusyInitializing = 0x0b,
250 IRStatus = 0x13,
251 IRRegisters = 0x1b,
252 NFCState = 0x2a,
253 NFCReadData = 0x3a,
254 EmptyAwaitingCmd = 0xff,
255};
256
257enum class MCUPacketFlag : u8 {
258 MorePacketsRemaining = 0x00,
259 LastCommandPacket = 0x08,
260};
261
262enum class NFCReadCommand : u8 {
263 CancelAll = 0x00,
264 StartPolling = 0x01,
265 StopPolling = 0x02,
266 StartWaitingRecieve = 0x04,
267 Ntag = 0x06,
268 Mifare = 0x0F,
269};
270
271enum class NFCTagType : u8 {
272 AllTags = 0x00,
273 Ntag215 = 0x01,
274};
275
276enum class DriverResult {
277 Success,
278 WrongReply,
279 Timeout,
280 UnsupportedControllerType,
281 HandleInUse,
282 ErrorReadingData,
283 ErrorWritingData,
284 NoDeviceDetected,
285 InvalidHandle,
286 NotSupported,
287 Unknown,
288};
289
290struct MotionSensorCalibration {
291 s16 offset;
292 s16 scale;
293};
294
295struct MotionCalibration {
296 std::array<MotionSensorCalibration, 3> accelerometer;
297 std::array<MotionSensorCalibration, 3> gyro;
298};
299
300// Basic motion data containing data from the sensors and a timestamp in microseconds
301struct MotionData {
302 float gyro_x{};
303 float gyro_y{};
304 float gyro_z{};
305 float accel_x{};
306 float accel_y{};
307 float accel_z{};
308 u64 delta_timestamp{};
309};
310
311struct JoyStickAxisCalibration {
312 u16 max{1};
313 u16 min{1};
314 u16 center{0};
315};
316
317struct JoyStickCalibration {
318 JoyStickAxisCalibration x;
319 JoyStickAxisCalibration y;
320};
321
322struct RingCalibration {
323 s16 default_value;
324 s16 max_value;
325 s16 min_value;
326};
327
328struct Color {
329 u32 body;
330 u32 buttons;
331 u32 left_grip;
332 u32 right_grip;
333};
334
335struct Battery {
336 union {
337 u8 raw{};
338
339 BitField<0, 4, u8> unknown;
340 BitField<4, 1, u8> charging;
341 BitField<5, 3, u8> status;
342 };
343};
344
345struct VibrationValue {
346 f32 low_amplitude;
347 f32 low_frequency;
348 f32 high_amplitude;
349 f32 high_frequency;
350};
351
352struct JoyconHandle {
353 SDL_hid_device* handle = nullptr;
354 u8 packet_counter{};
355};
356
357struct MCUConfig {
358 MCUCommand command;
359 MCUSubCommand sub_command;
360 MCUMode mode;
361 INSERT_PADDING_BYTES(0x22);
362 u8 crc;
363};
364static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
365
366#pragma pack(push, 1)
367struct InputReportPassive {
368 InputReport report_mode;
369 u16 button_input;
370 u8 stick_state;
371 std::array<u8, 10> unknown_data;
372};
373static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
374
375struct InputReportActive {
376 InputReport report_mode;
377 u8 packet_id;
378 Battery battery_status;
379 std::array<u8, 3> button_input;
380 std::array<u8, 3> left_stick_state;
381 std::array<u8, 3> right_stick_state;
382 u8 vibration_code;
383 std::array<s16, 6 * 2> motion_input;
384 INSERT_PADDING_BYTES(0x2);
385 s16 ring_input;
386};
387static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
388
389struct InputReportNfcIr {
390 InputReport report_mode;
391 u8 packet_id;
392 Battery battery_status;
393 std::array<u8, 3> button_input;
394 std::array<u8, 3> left_stick_state;
395 std::array<u8, 3> right_stick_state;
396 u8 vibration_code;
397 std::array<s16, 6 * 2> motion_input;
398 INSERT_PADDING_BYTES(0x4);
399};
400static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
401#pragma pack(pop)
402
403struct IMUCalibration {
404 std::array<s16, 3> accelerometer_offset;
405 std::array<s16, 3> accelerometer_scale;
406 std::array<s16, 3> gyroscope_offset;
407 std::array<s16, 3> gyroscope_scale;
408};
409static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
410
411struct NFCReadBlock {
412 u8 start;
413 u8 end;
414};
415static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
416
417struct NFCReadBlockCommand {
418 u8 block_count{};
419 std::array<NFCReadBlock, 4> blocks{};
420};
421static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
422
423struct NFCReadCommandData {
424 u8 unknown;
425 u8 uuid_length;
426 u8 unknown_2;
427 std::array<u8, 6> uid;
428 NFCTagType tag_type;
429 NFCReadBlockCommand read_block;
430};
431static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
432
433struct NFCPollingCommandData {
434 u8 enable_mifare;
435 u8 unknown_1;
436 u8 unknown_2;
437 u8 unknown_3;
438 u8 unknown_4;
439};
440static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
441
442struct NFCRequestState {
443 MCUSubCommand sub_command;
444 NFCReadCommand command_argument;
445 u8 packet_id;
446 INSERT_PADDING_BYTES(0x1);
447 MCUPacketFlag packet_flag;
448 u8 data_length;
449 union {
450 std::array<u8, 0x1F> raw_data;
451 NFCReadCommandData nfc_read;
452 NFCPollingCommandData nfc_polling;
453 };
454 u8 crc;
455};
456static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
457
458struct FirmwareVersion {
459 u8 major;
460 u8 minor;
461};
462static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
463
464struct DeviceInfo {
465 FirmwareVersion firmware;
466 MacAddress mac_address;
467};
468static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
469
470struct MotionStatus {
471 bool is_enabled;
472 u64 delta_time;
473 GyroSensitivity gyro_sensitivity;
474 AccelerometerSensitivity accelerometer_sensitivity;
475};
476
477struct RingStatus {
478 bool is_enabled;
479 s16 default_value;
480 s16 max_value;
481 s16 min_value;
482};
483
484struct JoyconCallbacks {
485 std::function<void(Battery)> on_battery_data;
486 std::function<void(Color)> on_color_data;
487 std::function<void(int, bool)> on_button_data;
488 std::function<void(int, f32)> on_stick_data;
489 std::function<void(int, const MotionData&)> on_motion_data;
490 std::function<void(f32)> on_ring_data;
491 std::function<void(const std::vector<u8>&)> on_amiibo_data;
492};
493
494} // namespace InputCommon::Joycon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index e0b2131ed..c77fc04ee 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -23,6 +23,7 @@
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
@@ -81,6 +82,7 @@ struct InputSubsystem::Impl {
81 RegisterEngine("virtual_gamepad", virtual_gamepad); 82 RegisterEngine("virtual_gamepad", virtual_gamepad);
82#ifdef HAVE_SDL2 83#ifdef HAVE_SDL2
83 RegisterEngine("sdl", sdl); 84 RegisterEngine("sdl", sdl);
85 RegisterEngine("joycon", joycon);
84#endif 86#endif
85 87
86 Common::Input::RegisterInputFactory("touch_from_button", 88 Common::Input::RegisterInputFactory("touch_from_button",
@@ -111,6 +113,7 @@ struct InputSubsystem::Impl {
111 UnregisterEngine(virtual_gamepad); 113 UnregisterEngine(virtual_gamepad);
112#ifdef HAVE_SDL2 114#ifdef HAVE_SDL2
113 UnregisterEngine(sdl); 115 UnregisterEngine(sdl);
116 UnregisterEngine(joycon);
114#endif 117#endif
115 118
116 Common::Input::UnregisterInputFactory("touch_from_button"); 119 Common::Input::UnregisterInputFactory("touch_from_button");
@@ -133,6 +136,8 @@ struct InputSubsystem::Impl {
133 auto udp_devices = udp_client->GetInputDevices(); 136 auto udp_devices = udp_client->GetInputDevices();
134 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); 137 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
135#ifdef HAVE_SDL2 138#ifdef HAVE_SDL2
139 auto joycon_devices = joycon->GetInputDevices();
140 devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
136 auto sdl_devices = sdl->GetInputDevices(); 141 auto sdl_devices = sdl->GetInputDevices();
137 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); 142 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
138#endif 143#endif
@@ -164,6 +169,9 @@ struct InputSubsystem::Impl {
164 if (engine == sdl->GetEngineName()) { 169 if (engine == sdl->GetEngineName()) {
165 return sdl; 170 return sdl;
166 } 171 }
172 if (engine == joycon->GetEngineName()) {
173 return joycon;
174 }
167#endif 175#endif
168 return nullptr; 176 return nullptr;
169 } 177 }
@@ -247,6 +255,9 @@ struct InputSubsystem::Impl {
247 if (engine == sdl->GetEngineName()) { 255 if (engine == sdl->GetEngineName()) {
248 return true; 256 return true;
249 } 257 }
258 if (engine == joycon->GetEngineName()) {
259 return true;
260 }
250#endif 261#endif
251 return false; 262 return false;
252 } 263 }
@@ -260,6 +271,7 @@ struct InputSubsystem::Impl {
260 udp_client->BeginConfiguration(); 271 udp_client->BeginConfiguration();
261#ifdef HAVE_SDL2 272#ifdef HAVE_SDL2
262 sdl->BeginConfiguration(); 273 sdl->BeginConfiguration();
274 joycon->BeginConfiguration();
263#endif 275#endif
264 } 276 }
265 277
@@ -272,6 +284,7 @@ struct InputSubsystem::Impl {
272 udp_client->EndConfiguration(); 284 udp_client->EndConfiguration();
273#ifdef HAVE_SDL2 285#ifdef HAVE_SDL2
274 sdl->EndConfiguration(); 286 sdl->EndConfiguration();
287 joycon->EndConfiguration();
275#endif 288#endif
276 } 289 }
277 290
@@ -304,6 +317,7 @@ struct InputSubsystem::Impl {
304 317
305#ifdef HAVE_SDL2 318#ifdef HAVE_SDL2
306 std::shared_ptr<SDLDriver> sdl; 319 std::shared_ptr<SDLDriver> sdl;
320 std::shared_ptr<Joycons> joycon;
307#endif 321#endif
308}; 322};
309 323