summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/input.h26
-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
8 files changed, 1786 insertions, 3 deletions
diff --git a/src/common/input.h b/src/common/input.h
index d27b1d772..1e5ba038d 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -51,6 +51,8 @@ enum class PollingMode {
51 NFC, 51 NFC,
52 // Enable infrared camera polling 52 // Enable infrared camera polling
53 IR, 53 IR,
54 // Enable ring controller polling
55 Ring,
54}; 56};
55 57
56enum class CameraFormat { 58enum class CameraFormat {
@@ -67,6 +69,7 @@ enum class VibrationError {
67 None, 69 None,
68 NotSupported, 70 NotSupported,
69 Disabled, 71 Disabled,
72 InvalidHandle,
70 Unknown, 73 Unknown,
71}; 74};
72 75
@@ -74,6 +77,7 @@ enum class VibrationError {
74enum class PollingError { 77enum class PollingError {
75 None, 78 None,
76 NotSupported, 79 NotSupported,
80 InvalidHandle,
77 Unknown, 81 Unknown,
78}; 82};
79 83
@@ -190,6 +194,8 @@ struct TouchStatus {
190struct BodyColorStatus { 194struct BodyColorStatus {
191 u32 body{}; 195 u32 body{};
192 u32 buttons{}; 196 u32 buttons{};
197 u32 left_grip{};
198 u32 right_grip{};
193}; 199};
194 200
195// HD rumble data 201// HD rumble data
@@ -228,17 +234,31 @@ enum class ButtonNames {
228 Engine, 234 Engine,
229 // This will display the button by value instead of the button name 235 // This will display the button by value instead of the button name
230 Value, 236 Value,
237
238 // Joycon button names
231 ButtonLeft, 239 ButtonLeft,
232 ButtonRight, 240 ButtonRight,
233 ButtonDown, 241 ButtonDown,
234 ButtonUp, 242 ButtonUp,
235 TriggerZ,
236 TriggerR,
237 TriggerL,
238 ButtonA, 243 ButtonA,
239 ButtonB, 244 ButtonB,
240 ButtonX, 245 ButtonX,
241 ButtonY, 246 ButtonY,
247 ButtonPlus,
248 ButtonMinus,
249 ButtonHome,
250 ButtonCapture,
251 ButtonStickL,
252 ButtonStickR,
253 TriggerL,
254 TriggerZL,
255 TriggerSL,
256 TriggerR,
257 TriggerZR,
258 TriggerSR,
259
260 // GC button names
261 TriggerZ,
242 ButtonStart, 262 ButtonStart,
243 263
244 // DS4 button names 264 // DS4 button names
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