summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common')
-rw-r--r--src/input_common/CMakeLists.txt21
-rw-r--r--src/input_common/drivers/camera.cpp4
-rw-r--r--src/input_common/drivers/camera.h4
-rw-r--r--src/input_common/drivers/gc_adapter.cpp6
-rw-r--r--src/input_common/drivers/gc_adapter.h2
-rw-r--r--src/input_common/drivers/joycon.cpp677
-rw-r--r--src/input_common/drivers/joycon.h111
-rw-r--r--src/input_common/drivers/sdl_driver.cpp23
-rw-r--r--src/input_common/drivers/sdl_driver.h2
-rw-r--r--src/input_common/drivers/virtual_amiibo.cpp11
-rw-r--r--src/input_common/drivers/virtual_amiibo.h2
-rw-r--r--src/input_common/helpers/joycon_driver.cpp572
-rw-r--r--src/input_common/helpers/joycon_driver.h150
-rw-r--r--src/input_common/helpers/joycon_protocol/calibration.cpp184
-rw-r--r--src/input_common/helpers/joycon_protocol/calibration.h64
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.h173
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.cpp125
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.h108
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.cpp298
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.h63
-rw-r--r--src/input_common/helpers/joycon_protocol/joycon_types.h612
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.cpp400
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.h61
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.cpp341
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.h81
-rw-r--r--src/input_common/helpers/joycon_protocol/ringcon.cpp117
-rw-r--r--src/input_common/helpers/joycon_protocol/ringcon.h38
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.h33
-rw-r--r--src/input_common/input_engine.cpp37
-rw-r--r--src/input_common/input_engine.h25
-rw-r--r--src/input_common/input_poller.cpp78
-rw-r--r--src/input_common/input_poller.h11
-rw-r--r--src/input_common/main.cpp14
35 files changed, 5013 insertions, 33 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index cef2c4d52..e3b627e4f 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -51,8 +51,29 @@ endif()
51 51
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/calibration.cpp
61 helpers/joycon_protocol/calibration.h
62 helpers/joycon_protocol/common_protocol.cpp
63 helpers/joycon_protocol/common_protocol.h
64 helpers/joycon_protocol/generic_functions.cpp
65 helpers/joycon_protocol/generic_functions.h
66 helpers/joycon_protocol/joycon_types.h
67 helpers/joycon_protocol/irs.cpp
68 helpers/joycon_protocol/irs.h
69 helpers/joycon_protocol/nfc.cpp
70 helpers/joycon_protocol/nfc.h
71 helpers/joycon_protocol/poller.cpp
72 helpers/joycon_protocol/poller.h
73 helpers/joycon_protocol/ringcon.cpp
74 helpers/joycon_protocol/ringcon.h
75 helpers/joycon_protocol/rumble.cpp
76 helpers/joycon_protocol/rumble.h
56 ) 77 )
57 target_link_libraries(input_common PRIVATE SDL2::SDL2) 78 target_link_libraries(input_common PRIVATE SDL2::SDL2)
58 target_compile_definitions(input_common PRIVATE HAVE_SDL2) 79 target_compile_definitions(input_common PRIVATE HAVE_SDL2)
diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp
index fad9177dc..04970f635 100644
--- a/src/input_common/drivers/camera.cpp
+++ b/src/input_common/drivers/camera.cpp
@@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const {
72 } 72 }
73} 73}
74 74
75Common::Input::CameraError Camera::SetCameraFormat( 75Common::Input::DriverResult Camera::SetCameraFormat(
76 [[maybe_unused]] const PadIdentifier& identifier_, 76 [[maybe_unused]] const PadIdentifier& identifier_,
77 const Common::Input::CameraFormat camera_format) { 77 const Common::Input::CameraFormat camera_format) {
78 status.format = camera_format; 78 status.format = camera_format;
79 return Common::Input::CameraError::None; 79 return Common::Input::DriverResult::Success;
80} 80}
81 81
82} // namespace InputCommon 82} // namespace InputCommon
diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h
index ead3e0fde..24b27e325 100644
--- a/src/input_common/drivers/camera.h
+++ b/src/input_common/drivers/camera.h
@@ -22,8 +22,8 @@ public:
22 std::size_t getImageWidth() const; 22 std::size_t getImageWidth() const;
23 std::size_t getImageHeight() const; 23 std::size_t getImageHeight() const;
24 24
25 Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, 25 Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
26 Common::Input::CameraFormat camera_format) override; 26 Common::Input::CameraFormat camera_format) override;
27 27
28private: 28private:
29 Common::Input::CameraStatus status{}; 29 Common::Input::CameraStatus status{};
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index 826fa2109..ecb3e9dc2 100644
--- a/src/input_common/drivers/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
324 return true; 324 return true;
325} 325}
326 326
327Common::Input::VibrationError GCAdapter::SetVibration( 327Common::Input::DriverResult GCAdapter::SetVibration(
328 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { 328 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
329 const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; 329 const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
330 const auto processed_amplitude = 330 const auto processed_amplitude =
@@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration(
333 pads[identifier.port].rumble_amplitude = processed_amplitude; 333 pads[identifier.port].rumble_amplitude = processed_amplitude;
334 334
335 if (!rumble_enabled) { 335 if (!rumble_enabled) {
336 return Common::Input::VibrationError::Disabled; 336 return Common::Input::DriverResult::Disabled;
337 } 337 }
338 return Common::Input::VibrationError::None; 338 return Common::Input::DriverResult::Success;
339} 339}
340 340
341bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { 341bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
index b5270fd0b..3c2eb376d 100644
--- a/src/input_common/drivers/gc_adapter.h
+++ b/src/input_common/drivers/gc_adapter.h
@@ -25,7 +25,7 @@ public:
25 explicit GCAdapter(std::string input_engine_); 25 explicit GCAdapter(std::string input_engine_);
26 ~GCAdapter() override; 26 ~GCAdapter() override;
27 27
28 Common::Input::VibrationError SetVibration( 28 Common::Input::DriverResult SetVibration(
29 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; 29 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
30 30
31 bool IsVibrationEnabled(const PadIdentifier& identifier) override; 31 bool IsVibrationEnabled(const PadIdentifier& identifier) override;
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
new file mode 100644
index 000000000..7122093c6
--- /dev/null
+++ b/src/input_common/drivers/joycon.cpp
@@ -0,0 +1,677 @@
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 // Avoid conflicting with SDL driver
17 if (!Settings::values.enable_joycon_driver) {
18 return;
19 }
20 LOG_INFO(Input, "Joycon driver Initialization started");
21 const int init_res = SDL_hid_init();
22 if (init_res == 0) {
23 Setup();
24 } else {
25 LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
26 }
27}
28
29Joycons::~Joycons() {
30 Reset();
31}
32
33void Joycons::Reset() {
34 scan_thread = {};
35 for (const auto& device : left_joycons) {
36 if (!device) {
37 continue;
38 }
39 device->Stop();
40 }
41 for (const auto& device : right_joycons) {
42 if (!device) {
43 continue;
44 }
45 device->Stop();
46 }
47 SDL_hid_exit();
48}
49
50void Joycons::Setup() {
51 u32 port = 0;
52 PreSetController(GetIdentifier(0, Joycon::ControllerType::None));
53 for (auto& device : left_joycons) {
54 PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
55 device = std::make_shared<Joycon::JoyconDriver>(port++);
56 }
57 port = 0;
58 for (auto& device : right_joycons) {
59 PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
60 device = std::make_shared<Joycon::JoyconDriver>(port++);
61 }
62
63 scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
64}
65
66void Joycons::ScanThread(std::stop_token stop_token) {
67 constexpr u16 nintendo_vendor_id = 0x057e;
68 Common::SetCurrentThreadName("JoyconScanThread");
69 while (!stop_token.stop_requested()) {
70 SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
71 SDL_hid_device_info* cur_dev = devs;
72
73 while (cur_dev) {
74 if (IsDeviceNew(cur_dev)) {
75 LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
76 cur_dev->product_id);
77 RegisterNewDevice(cur_dev);
78 }
79 cur_dev = cur_dev->next;
80 }
81
82 SDL_hid_free_enumeration(devs);
83 std::this_thread::sleep_for(std::chrono::seconds(5));
84 }
85}
86
87bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
88 Joycon::ControllerType type{};
89 Joycon::SerialNumber serial_number{};
90
91 const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
92 if (result != Joycon::DriverResult::Success) {
93 return false;
94 }
95
96 const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
97 if (result2 != Joycon::DriverResult::Success) {
98 return false;
99 }
100
101 auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) {
102 if (!device) {
103 return false;
104 }
105 if (!device->IsConnected()) {
106 return false;
107 }
108 if (device->GetHandleSerialNumber() != serial_number) {
109 return false;
110 }
111 return true;
112 };
113
114 // Check if device already exist
115 switch (type) {
116 case Joycon::ControllerType::Left:
117 for (const auto& device : left_joycons) {
118 if (is_handle_identical(device)) {
119 return false;
120 }
121 }
122 break;
123 case Joycon::ControllerType::Right:
124 for (const auto& device : right_joycons) {
125 if (is_handle_identical(device)) {
126 return false;
127 }
128 }
129 break;
130 default:
131 return false;
132 }
133
134 return true;
135}
136
137void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
138 Joycon::ControllerType type{};
139 auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
140 auto handle = GetNextFreeHandle(type);
141 if (handle == nullptr) {
142 LOG_WARNING(Input, "No free handles available");
143 return;
144 }
145 if (result == Joycon::DriverResult::Success) {
146 result = handle->RequestDeviceAccess(device_info);
147 }
148 if (result == Joycon::DriverResult::Success) {
149 LOG_WARNING(Input, "Initialize device");
150
151 const std::size_t port = handle->GetDevicePort();
152 const Joycon::JoyconCallbacks callbacks{
153 .on_battery_data = {[this, port, type](Joycon::Battery value) {
154 OnBatteryUpdate(port, type, value);
155 }},
156 .on_color_data = {[this, port, type](Joycon::Color value) {
157 OnColorUpdate(port, type, value);
158 }},
159 .on_button_data = {[this, port, type](int id, bool value) {
160 OnButtonUpdate(port, type, id, value);
161 }},
162 .on_stick_data = {[this, port, type](int id, f32 value) {
163 OnStickUpdate(port, type, id, value);
164 }},
165 .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) {
166 OnMotionUpdate(port, type, id, value);
167 }},
168 .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
169 .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
170 OnAmiiboUpdate(port, amiibo_data);
171 }},
172 .on_camera_data = {[this, port](const std::vector<u8>& camera_data,
173 Joycon::IrsResolution format) {
174 OnCameraUpdate(port, camera_data, format);
175 }},
176 };
177
178 handle->InitializeDevice();
179 handle->SetCallbacks(callbacks);
180 }
181}
182
183std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
184 Joycon::ControllerType type) const {
185 if (type == Joycon::ControllerType::Left) {
186 const auto unconnected_device =
187 std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); });
188 if (unconnected_device != left_joycons.end()) {
189 return *unconnected_device;
190 }
191 }
192 if (type == Joycon::ControllerType::Right) {
193 const auto unconnected_device = std::ranges::find_if(
194 right_joycons, [](auto& device) { return !device->IsConnected(); });
195
196 if (unconnected_device != right_joycons.end()) {
197 return *unconnected_device;
198 }
199 }
200 return nullptr;
201}
202
203bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
204 const auto handle = GetHandle(identifier);
205 if (handle == nullptr) {
206 return false;
207 }
208 return handle->IsVibrationEnabled();
209}
210
211Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier,
212 const Common::Input::VibrationStatus& vibration) {
213 const Joycon::VibrationValue native_vibration{
214 .low_amplitude = vibration.low_amplitude,
215 .low_frequency = vibration.low_frequency,
216 .high_amplitude = vibration.high_amplitude,
217 .high_frequency = vibration.high_frequency,
218 };
219 auto handle = GetHandle(identifier);
220 if (handle == nullptr) {
221 return Common::Input::DriverResult::InvalidHandle;
222 }
223
224 handle->SetVibration(native_vibration);
225 return Common::Input::DriverResult::Success;
226}
227
228Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
229 const Common::Input::LedStatus& led_status) {
230 auto handle = GetHandle(identifier);
231 if (handle == nullptr) {
232 return Common::Input::DriverResult::InvalidHandle;
233 }
234 int led_config = led_status.led_1 ? 1 : 0;
235 led_config += led_status.led_2 ? 2 : 0;
236 led_config += led_status.led_3 ? 4 : 0;
237 led_config += led_status.led_4 ? 8 : 0;
238
239 return static_cast<Common::Input::DriverResult>(
240 handle->SetLedConfig(static_cast<u8>(led_config)));
241}
242
243Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier,
244 Common::Input::CameraFormat camera_format) {
245 auto handle = GetHandle(identifier);
246 if (handle == nullptr) {
247 return Common::Input::DriverResult::InvalidHandle;
248 }
249 return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig(
250 Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format)));
251};
252
253Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
254 return Common::Input::NfcState::Success;
255};
256
257Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
258 const std::vector<u8>& data) {
259 return Common::Input::NfcState::NotSupported;
260};
261
262Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
263 const Common::Input::PollingMode polling_mode) {
264 auto handle = GetHandle(identifier);
265 if (handle == nullptr) {
266 LOG_ERROR(Input, "Invalid handle {}", identifier.port);
267 return Common::Input::DriverResult::InvalidHandle;
268 }
269
270 switch (polling_mode) {
271 case Common::Input::PollingMode::Active:
272 return static_cast<Common::Input::DriverResult>(handle->SetActiveMode());
273 case Common::Input::PollingMode::Pasive:
274 return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode());
275 case Common::Input::PollingMode::IR:
276 return static_cast<Common::Input::DriverResult>(handle->SetIrMode());
277 case Common::Input::PollingMode::NFC:
278 return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
279 case Common::Input::PollingMode::Ring:
280 return static_cast<Common::Input::DriverResult>(handle->SetRingConMode());
281 default:
282 return Common::Input::DriverResult::NotSupported;
283 }
284}
285
286void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
287 Joycon::Battery value) {
288 const auto identifier = GetIdentifier(port, type);
289 if (value.charging != 0) {
290 SetBattery(identifier, Common::Input::BatteryLevel::Charging);
291 return;
292 }
293
294 Common::Input::BatteryLevel battery{};
295 switch (value.status) {
296 case 0:
297 battery = Common::Input::BatteryLevel::Empty;
298 break;
299 case 1:
300 battery = Common::Input::BatteryLevel::Critical;
301 break;
302 case 2:
303 battery = Common::Input::BatteryLevel::Low;
304 break;
305 case 3:
306 battery = Common::Input::BatteryLevel::Medium;
307 break;
308 case 4:
309 default:
310 battery = Common::Input::BatteryLevel::Full;
311 break;
312 }
313 SetBattery(identifier, battery);
314}
315
316void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
317 const Joycon::Color& value) {
318 const auto identifier = GetIdentifier(port, type);
319 Common::Input::BodyColorStatus color{
320 .body = value.body,
321 .buttons = value.buttons,
322 .left_grip = value.left_grip,
323 .right_grip = value.right_grip,
324 };
325 SetColor(identifier, color);
326}
327
328void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
329 const auto identifier = GetIdentifier(port, type);
330 SetButton(identifier, id, value);
331}
332
333void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
334 const auto identifier = GetIdentifier(port, type);
335 SetAxis(identifier, id, value);
336}
337
338void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
339 const Joycon::MotionData& value) {
340 const auto identifier = GetIdentifier(port, type);
341 BasicMotion motion_data{
342 .gyro_x = value.gyro_x,
343 .gyro_y = value.gyro_y,
344 .gyro_z = value.gyro_z,
345 .accel_x = value.accel_x,
346 .accel_y = value.accel_y,
347 .accel_z = value.accel_z,
348 .delta_timestamp = 15000,
349 };
350 SetMotion(identifier, id, motion_data);
351}
352
353void Joycons::OnRingConUpdate(f32 ring_data) {
354 // To simplify ring detection it will always be mapped to an empty identifier for all
355 // controllers
356 constexpr PadIdentifier identifier = {
357 .guid = Common::UUID{},
358 .port = 0,
359 .pad = 0,
360 };
361 SetAxis(identifier, 100, ring_data);
362}
363
364void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
365 const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
366 const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
367 : Common::Input::NfcState::NewAmiibo;
368 SetNfc(identifier, {nfc_state, amiibo_data});
369}
370
371void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
372 Joycon::IrsResolution format) {
373 const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
374 SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_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
392 if (type == Joycon::ControllerType::Left) {
393 const auto matching_device = std::ranges::find_if(
394 left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
395
396 if (matching_device != left_joycons.end()) {
397 return *matching_device;
398 }
399 }
400
401 if (type == Joycon::ControllerType::Right) {
402 const auto matching_device = std::ranges::find_if(
403 right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
404
405 if (matching_device != right_joycons.end()) {
406 return *matching_device;
407 }
408 }
409
410 return nullptr;
411}
412
413PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
414 const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0,
415 0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)};
416 return {
417 .guid = Common::UUID{guid},
418 .port = port,
419 .pad = static_cast<std::size_t>(type),
420 };
421}
422
423Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const {
424 const auto identifier = GetIdentifier(port, type);
425 return {
426 {"engine", GetEngineName()},
427 {"guid", identifier.guid.RawString()},
428 {"port", std::to_string(identifier.port)},
429 {"pad", std::to_string(identifier.pad)},
430 };
431}
432
433std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
434 std::vector<Common::ParamPackage> devices{};
435
436 auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
437 if (!device) {
438 return;
439 }
440 if (!device->IsConnected()) {
441 return;
442 }
443 auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType());
444 std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
445 device->GetDevicePort() + 1);
446 param.Set("display", std::move(name));
447 devices.emplace_back(param);
448 };
449
450 for (const auto& controller : left_joycons) {
451 add_entry(controller);
452 }
453 for (const auto& controller : right_joycons) {
454 add_entry(controller);
455 }
456
457 // List dual joycon pairs
458 for (std::size_t i = 0; i < MaxSupportedControllers; i++) {
459 if (!left_joycons[i] || !right_joycons[i]) {
460 continue;
461 }
462 if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) {
463 continue;
464 }
465 auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType());
466 const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType());
467 const auto type = Joycon::ControllerType::Dual;
468 std::string name = fmt::format("{} {}", JoyconName(type), i + 1);
469
470 main_param.Set("display", std::move(name));
471 main_param.Set("guid2", second_param.Get("guid", ""));
472 main_param.Set("pad", std::to_string(static_cast<size_t>(type)));
473 devices.emplace_back(main_param);
474 }
475
476 return devices;
477}
478
479ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
480 static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>,
481 18>
482 switch_to_joycon_button = {
483 std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true},
484 {Settings::NativeButton::B, Joycon::PadButton::B, true},
485 {Settings::NativeButton::X, Joycon::PadButton::X, true},
486 {Settings::NativeButton::Y, Joycon::PadButton::Y, true},
487 {Settings::NativeButton::DLeft, Joycon::PadButton::Left, false},
488 {Settings::NativeButton::DUp, Joycon::PadButton::Up, false},
489 {Settings::NativeButton::DRight, Joycon::PadButton::Right, false},
490 {Settings::NativeButton::DDown, Joycon::PadButton::Down, false},
491 {Settings::NativeButton::L, Joycon::PadButton::L, false},
492 {Settings::NativeButton::R, Joycon::PadButton::R, true},
493 {Settings::NativeButton::ZL, Joycon::PadButton::ZL, false},
494 {Settings::NativeButton::ZR, Joycon::PadButton::ZR, true},
495 {Settings::NativeButton::Plus, Joycon::PadButton::Plus, true},
496 {Settings::NativeButton::Minus, Joycon::PadButton::Minus, false},
497 {Settings::NativeButton::Home, Joycon::PadButton::Home, true},
498 {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false},
499 {Settings::NativeButton::LStick, Joycon::PadButton::StickL, false},
500 {Settings::NativeButton::RStick, Joycon::PadButton::StickR, true},
501 };
502
503 if (!params.Has("port")) {
504 return {};
505 }
506
507 ButtonMapping mapping{};
508 for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) {
509 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
510 auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
511 if (pad == Joycon::ControllerType::Dual) {
512 pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left;
513 }
514
515 Common::ParamPackage button_params = GetParamPackage(port, pad);
516 button_params.Set("button", static_cast<int>(joycon_button));
517 mapping.insert_or_assign(switch_button, std::move(button_params));
518 }
519
520 // Map SL and SR buttons for left joycons
521 if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) {
522 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
523 Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left);
524
525 Common::ParamPackage sl_button_params = button_params;
526 Common::ParamPackage sr_button_params = button_params;
527 sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL));
528 sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR));
529 mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
530 mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
531 }
532
533 // Map SL and SR buttons for right joycons
534 if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) {
535 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
536 Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right);
537
538 Common::ParamPackage sl_button_params = button_params;
539 Common::ParamPackage sr_button_params = button_params;
540 sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL));
541 sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR));
542 mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
543 mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
544 }
545
546 return mapping;
547}
548
549AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
550 if (!params.Has("port")) {
551 return {};
552 }
553
554 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
555 auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
556 auto pad_right = pad_left;
557 if (pad_left == Joycon::ControllerType::Dual) {
558 pad_left = Joycon::ControllerType::Left;
559 pad_right = Joycon::ControllerType::Right;
560 }
561
562 AnalogMapping mapping = {};
563 Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left);
564 left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
565 left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
566 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
567 Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right);
568 right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
569 right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
570 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
571 return mapping;
572}
573
574MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
575 if (!params.Has("port")) {
576 return {};
577 }
578
579 const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
580 auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
581 auto pad_right = pad_left;
582 if (pad_left == Joycon::ControllerType::Dual) {
583 pad_left = Joycon::ControllerType::Left;
584 pad_right = Joycon::ControllerType::Right;
585 }
586
587 MotionMapping mapping = {};
588 Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left);
589 left_motion_params.Set("motion", 0);
590 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
591 Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right);
592 right_Motion_params.Set("motion", 1);
593 mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
594 return mapping;
595}
596
597Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
598 const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
599 switch (button) {
600 case Joycon::PadButton::Left:
601 return Common::Input::ButtonNames::ButtonLeft;
602 case Joycon::PadButton::Right:
603 return Common::Input::ButtonNames::ButtonRight;
604 case Joycon::PadButton::Down:
605 return Common::Input::ButtonNames::ButtonDown;
606 case Joycon::PadButton::Up:
607 return Common::Input::ButtonNames::ButtonUp;
608 case Joycon::PadButton::LeftSL:
609 case Joycon::PadButton::RightSL:
610 return Common::Input::ButtonNames::TriggerSL;
611 case Joycon::PadButton::LeftSR:
612 case Joycon::PadButton::RightSR:
613 return Common::Input::ButtonNames::TriggerSR;
614 case Joycon::PadButton::L:
615 return Common::Input::ButtonNames::TriggerL;
616 case Joycon::PadButton::R:
617 return Common::Input::ButtonNames::TriggerR;
618 case Joycon::PadButton::ZL:
619 return Common::Input::ButtonNames::TriggerZL;
620 case Joycon::PadButton::ZR:
621 return Common::Input::ButtonNames::TriggerZR;
622 case Joycon::PadButton::A:
623 return Common::Input::ButtonNames::ButtonA;
624 case Joycon::PadButton::B:
625 return Common::Input::ButtonNames::ButtonB;
626 case Joycon::PadButton::X:
627 return Common::Input::ButtonNames::ButtonX;
628 case Joycon::PadButton::Y:
629 return Common::Input::ButtonNames::ButtonY;
630 case Joycon::PadButton::Plus:
631 return Common::Input::ButtonNames::ButtonPlus;
632 case Joycon::PadButton::Minus:
633 return Common::Input::ButtonNames::ButtonMinus;
634 case Joycon::PadButton::Home:
635 return Common::Input::ButtonNames::ButtonHome;
636 case Joycon::PadButton::Capture:
637 return Common::Input::ButtonNames::ButtonCapture;
638 case Joycon::PadButton::StickL:
639 return Common::Input::ButtonNames::ButtonStickL;
640 case Joycon::PadButton::StickR:
641 return Common::Input::ButtonNames::ButtonStickR;
642 default:
643 return Common::Input::ButtonNames::Undefined;
644 }
645}
646
647Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
648 if (params.Has("button")) {
649 return GetUIButtonName(params);
650 }
651 if (params.Has("axis")) {
652 return Common::Input::ButtonNames::Value;
653 }
654 if (params.Has("motion")) {
655 return Common::Input::ButtonNames::Engine;
656 }
657
658 return Common::Input::ButtonNames::Invalid;
659}
660
661std::string Joycons::JoyconName(Joycon::ControllerType type) const {
662 switch (type) {
663 case Joycon::ControllerType::Left:
664 return "Left Joycon";
665 case Joycon::ControllerType::Right:
666 return "Right Joycon";
667 case Joycon::ControllerType::Pro:
668 return "Pro Controller";
669 case Joycon::ControllerType::Grip:
670 return "Grip Controller";
671 case Joycon::ControllerType::Dual:
672 return "Dual Joycon";
673 default:
674 return "Unknown Joycon";
675 }
676}
677} // namespace InputCommon
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
new file mode 100644
index 000000000..316d383d8
--- /dev/null
+++ b/src/input_common/drivers/joycon.h
@@ -0,0 +1,111 @@
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;
20enum class IrsResolution;
21class JoyconDriver;
22} // namespace InputCommon::Joycon
23
24namespace InputCommon {
25
26class Joycons final : public InputCommon::InputEngine {
27public:
28 explicit Joycons(const std::string& input_engine_);
29
30 ~Joycons();
31
32 bool IsVibrationEnabled(const PadIdentifier& identifier) override;
33 Common::Input::DriverResult SetVibration(
34 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
35
36 Common::Input::DriverResult SetLeds(const PadIdentifier& identifier,
37 const Common::Input::LedStatus& led_status) override;
38
39 Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
40 Common::Input::CameraFormat camera_format) override;
41
42 Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
43 Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
44 const std::vector<u8>& data) override;
45
46 Common::Input::DriverResult SetPollingMode(
47 const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
48
49 /// Used for automapping features
50 std::vector<Common::ParamPackage> GetInputDevices() const override;
51 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
52 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
53 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
54 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
55
56private:
57 static constexpr std::size_t MaxSupportedControllers = 8;
58
59 /// For shutting down, clear all data, join all threads, release usb devices
60 void Reset();
61
62 /// Registers controllers, clears all data and starts the scan thread
63 void Setup();
64
65 /// Actively searchs for new devices
66 void ScanThread(std::stop_token stop_token);
67
68 /// Returns true if device is valid and not registered
69 bool IsDeviceNew(SDL_hid_device_info* device_info) const;
70
71 /// Tries to connect to the new device
72 void RegisterNewDevice(SDL_hid_device_info* device_info);
73
74 /// Returns the next free handle
75 std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const;
76
77 void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value);
78 void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value);
79 void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value);
80 void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value);
81 void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
82 const Joycon::MotionData& value);
83 void OnRingConUpdate(f32 ring_data);
84 void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
85 void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
86 Joycon::IrsResolution format);
87
88 /// Returns a JoyconHandle corresponding to a PadIdentifier
89 std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
90
91 /// Returns a PadIdentifier corresponding to the port number and joycon type
92 PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
93
94 /// Returns a ParamPackage corresponding to the port number and joycon type
95 Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const;
96
97 std::string JoyconName(std::size_t port) const;
98
99 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
100
101 /// Returns the name of the device in text format
102 std::string JoyconName(Joycon::ControllerType type) const;
103
104 std::jthread scan_thread;
105
106 // Joycon types are split by type to ease supporting dualjoycon configurations
107 std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
108 std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
109};
110
111} // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 9835d99d2..d975eb815 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -334,6 +334,15 @@ void SDLDriver::InitJoystick(int joystick_index) {
334 334
335 const auto guid = GetGUID(sdl_joystick); 335 const auto guid = GetGUID(sdl_joystick);
336 336
337 if (Settings::values.enable_joycon_driver) {
338 if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
339 (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
340 LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
341 SDL_JoystickClose(sdl_joystick);
342 return;
343 }
344 }
345
337 std::scoped_lock lock{joystick_map_mutex}; 346 std::scoped_lock lock{joystick_map_mutex};
338 if (joystick_map.find(guid) == joystick_map.end()) { 347 if (joystick_map.find(guid) == joystick_map.end()) {
339 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); 348 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
@@ -456,9 +465,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
456 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); 465 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
457 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); 466 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
458 467
459 // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and 468 // Disable hidapi drivers for switch controllers when the custom joycon driver is enabled
460 // not a generic one 469 if (Settings::values.enable_joycon_driver) {
461 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); 470 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
471 } else {
472 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
473 }
474 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
462 475
463 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native 476 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
464 // driver on Linux. 477 // driver on Linux.
@@ -548,7 +561,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
548 return devices; 561 return devices;
549} 562}
550 563
551Common::Input::VibrationError SDLDriver::SetVibration( 564Common::Input::DriverResult SDLDriver::SetVibration(
552 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { 565 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
553 const auto joystick = 566 const auto joystick =
554 GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); 567 GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
@@ -582,7 +595,7 @@ Common::Input::VibrationError SDLDriver::SetVibration(
582 .vibration = new_vibration, 595 .vibration = new_vibration,
583 }); 596 });
584 597
585 return Common::Input::VibrationError::None; 598 return Common::Input::DriverResult::Success;
586} 599}
587 600
588bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { 601bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index 366bcc496..ffde169b3 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -63,7 +63,7 @@ public:
63 63
64 bool IsStickInverted(const Common::ParamPackage& params) override; 64 bool IsStickInverted(const Common::ParamPackage& params) override;
65 65
66 Common::Input::VibrationError SetVibration( 66 Common::Input::DriverResult SetVibration(
67 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; 67 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
68 68
69 bool IsVibrationEnabled(const PadIdentifier& identifier) override; 69 bool IsVibrationEnabled(const PadIdentifier& identifier) override;
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
index 63ffaca67..4a0268a4d 100644
--- a/src/input_common/drivers/virtual_amiibo.cpp
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -22,22 +22,23 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(
22 22
23VirtualAmiibo::~VirtualAmiibo() = default; 23VirtualAmiibo::~VirtualAmiibo() = default;
24 24
25Common::Input::PollingError VirtualAmiibo::SetPollingMode( 25Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
26 [[maybe_unused]] const PadIdentifier& identifier_, 26 [[maybe_unused]] const PadIdentifier& identifier_,
27 const Common::Input::PollingMode polling_mode_) { 27 const Common::Input::PollingMode polling_mode_) {
28 polling_mode = polling_mode_; 28 polling_mode = polling_mode_;
29 29
30 if (polling_mode == Common::Input::PollingMode::NFC) { 30 switch (polling_mode) {
31 case Common::Input::PollingMode::NFC:
31 if (state == State::Initialized) { 32 if (state == State::Initialized) {
32 state = State::WaitingForAmiibo; 33 state = State::WaitingForAmiibo;
33 } 34 }
34 } else { 35 return Common::Input::DriverResult::Success;
36 default:
35 if (state == State::AmiiboIsOpen) { 37 if (state == State::AmiiboIsOpen) {
36 CloseAmiibo(); 38 CloseAmiibo();
37 } 39 }
40 return Common::Input::DriverResult::NotSupported;
38 } 41 }
39
40 return Common::Input::PollingError::None;
41} 42}
42 43
43Common::Input::NfcState VirtualAmiibo::SupportsNfc( 44Common::Input::NfcState VirtualAmiibo::SupportsNfc(
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
index 0f9dad333..13cacfc0a 100644
--- a/src/input_common/drivers/virtual_amiibo.h
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -36,7 +36,7 @@ public:
36 ~VirtualAmiibo() override; 36 ~VirtualAmiibo() override;
37 37
38 // Sets polling mode to a controller 38 // Sets polling mode to a controller
39 Common::Input::PollingError SetPollingMode( 39 Common::Input::DriverResult SetPollingMode(
40 const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; 40 const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
41 41
42 Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; 42 Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
new file mode 100644
index 000000000..4159e5717
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,572 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "common/swap.h"
6#include "common/thread.h"
7#include "input_common/helpers/joycon_driver.h"
8#include "input_common/helpers/joycon_protocol/calibration.h"
9#include "input_common/helpers/joycon_protocol/generic_functions.h"
10#include "input_common/helpers/joycon_protocol/irs.h"
11#include "input_common/helpers/joycon_protocol/nfc.h"
12#include "input_common/helpers/joycon_protocol/poller.h"
13#include "input_common/helpers/joycon_protocol/ringcon.h"
14#include "input_common/helpers/joycon_protocol/rumble.h"
15
16namespace InputCommon::Joycon {
17JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
18 hidapi_handle = std::make_shared<JoyconHandle>();
19}
20
21JoyconDriver::~JoyconDriver() {
22 Stop();
23}
24
25void JoyconDriver::Stop() {
26 is_connected = false;
27 input_thread = {};
28}
29
30DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
31 std::scoped_lock lock{mutex};
32
33 handle_device_type = ControllerType::None;
34 GetDeviceType(device_info, handle_device_type);
35 if (handle_device_type == ControllerType::None) {
36 return DriverResult::UnsupportedControllerType;
37 }
38
39 hidapi_handle->handle =
40 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
41 std::memcpy(&handle_serial_number, device_info->serial_number, 15);
42 if (!hidapi_handle->handle) {
43 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
44 device_info->vendor_id, device_info->product_id);
45 return DriverResult::HandleInUse;
46 }
47 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
48 return DriverResult::Success;
49}
50
51DriverResult JoyconDriver::InitializeDevice() {
52 if (!hidapi_handle->handle) {
53 return DriverResult::InvalidHandle;
54 }
55 std::scoped_lock lock{mutex};
56 disable_input_thread = true;
57
58 // Reset Counters
59 error_counter = 0;
60 hidapi_handle->packet_counter = 0;
61
62 // Reset external device status
63 starlink_connected = false;
64 ring_connected = false;
65 amiibo_detected = false;
66
67 // Set HW default configuration
68 vibration_enabled = true;
69 motion_enabled = true;
70 hidbus_enabled = false;
71 nfc_enabled = false;
72 passive_enabled = false;
73 irs_enabled = false;
74 gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
75 gyro_performance = Joycon::GyroPerformance::HZ833;
76 accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
77 accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
78
79 // Initialize HW Protocols
80 calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
81 generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
82 irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
83 nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
84 ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
85 rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
86
87 // Get fixed joycon info
88 generic_protocol->GetVersionNumber(version);
89 generic_protocol->GetColor(color);
90 if (handle_device_type == ControllerType::Pro) {
91 // Some 3rd party controllers aren't pro controllers
92 generic_protocol->GetControllerType(device_type);
93 } else {
94 device_type = handle_device_type;
95 }
96 generic_protocol->GetSerialNumber(serial_number);
97 supported_features = GetSupportedFeatures();
98
99 // Get Calibration data
100 calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
101 calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
102 calibration_protocol->GetImuCalibration(motion_calibration);
103
104 // Set led status
105 generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
106
107 // Apply HW configuration
108 SetPollingMode();
109
110 // Initialize joycon poller
111 joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
112 right_stick_calibration, motion_calibration);
113
114 // Start pooling for data
115 is_connected = true;
116 if (!input_thread_running) {
117 input_thread =
118 std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
119 }
120
121 disable_input_thread = false;
122 return DriverResult::Success;
123}
124
125void JoyconDriver::InputThread(std::stop_token stop_token) {
126 LOG_INFO(Input, "Joycon Adapter input thread started");
127 Common::SetCurrentThreadName("JoyconInput");
128 input_thread_running = true;
129
130 // Max update rate is 5ms, ensure we are always able to read a bit faster
131 constexpr int ThreadDelay = 2;
132 std::vector<u8> buffer(MaxBufferSize);
133
134 while (!stop_token.stop_requested()) {
135 int status = 0;
136
137 if (!IsInputThreadValid()) {
138 input_thread.request_stop();
139 continue;
140 }
141
142 // By disabling the input thread we can ensure custom commands will succeed as no package is
143 // skipped
144 if (!disable_input_thread) {
145 status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
146 ThreadDelay);
147 } else {
148 std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
149 }
150
151 if (IsPayloadCorrect(status, buffer)) {
152 OnNewData(buffer);
153 }
154
155 std::this_thread::yield();
156 }
157
158 is_connected = false;
159 input_thread_running = false;
160 LOG_INFO(Input, "Joycon Adapter input thread stopped");
161}
162
163void JoyconDriver::OnNewData(std::span<u8> buffer) {
164 const auto report_mode = static_cast<InputReport>(buffer[0]);
165
166 // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
167 // experience
168 switch (report_mode) {
169 case InputReport::STANDARD_FULL_60HZ:
170 case InputReport::NFC_IR_MODE_60HZ:
171 case InputReport::SIMPLE_HID_MODE: {
172 const auto now = std::chrono::steady_clock::now();
173 const auto new_delta_time = static_cast<u64>(
174 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
175 delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
176 last_update = now;
177 joycon_poller->UpdateColor(color);
178 break;
179 }
180 default:
181 break;
182 }
183
184 const MotionStatus motion_status{
185 .is_enabled = motion_enabled,
186 .delta_time = delta_time,
187 .gyro_sensitivity = gyro_sensitivity,
188 .accelerometer_sensitivity = accelerometer_sensitivity,
189 };
190
191 // TODO: Remove this when calibration is properly loaded and not calculated
192 if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) {
193 InputReportActive data{};
194 memcpy(&data, buffer.data(), sizeof(InputReportActive));
195 calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
196 }
197
198 const RingStatus ring_status{
199 .is_enabled = ring_connected,
200 .default_value = ring_calibration.default_value,
201 .max_value = ring_calibration.max_value,
202 .min_value = ring_calibration.min_value,
203 };
204
205 if (irs_protocol->IsEnabled()) {
206 irs_protocol->RequestImage(buffer);
207 joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
208 }
209
210 if (nfc_protocol->IsEnabled()) {
211 if (amiibo_detected) {
212 if (!nfc_protocol->HasAmiibo()) {
213 joycon_poller->UpdateAmiibo({});
214 amiibo_detected = false;
215 return;
216 }
217 }
218
219 if (!amiibo_detected) {
220 std::vector<u8> data(0x21C);
221 const auto result = nfc_protocol->ScanAmiibo(data);
222 if (result == DriverResult::Success) {
223 joycon_poller->UpdateAmiibo(data);
224 amiibo_detected = true;
225 }
226 }
227 }
228
229 switch (report_mode) {
230 case InputReport::STANDARD_FULL_60HZ:
231 joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
232 break;
233 case InputReport::NFC_IR_MODE_60HZ:
234 joycon_poller->ReadNfcIRMode(buffer, motion_status);
235 break;
236 case InputReport::SIMPLE_HID_MODE:
237 joycon_poller->ReadPassiveMode(buffer);
238 break;
239 case InputReport::SUBCMD_REPLY:
240 LOG_DEBUG(Input, "Unhandled command reply");
241 break;
242 default:
243 LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
244 break;
245 }
246}
247
248DriverResult JoyconDriver::SetPollingMode() {
249 disable_input_thread = true;
250
251 rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
252
253 if (motion_enabled && supported_features.motion) {
254 generic_protocol->EnableImu(true);
255 generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
256 accelerometer_sensitivity, accelerometer_performance);
257 } else {
258 generic_protocol->EnableImu(false);
259 }
260
261 if (irs_protocol->IsEnabled()) {
262 irs_protocol->DisableIrs();
263 }
264
265 if (nfc_protocol->IsEnabled()) {
266 amiibo_detected = false;
267 nfc_protocol->DisableNfc();
268 }
269
270 if (ring_protocol->IsEnabled()) {
271 ring_connected = false;
272 ring_protocol->DisableRingCon();
273 }
274
275 if (irs_enabled && supported_features.irs) {
276 auto result = irs_protocol->EnableIrs();
277 if (result == DriverResult::Success) {
278 disable_input_thread = false;
279 return result;
280 }
281 irs_protocol->DisableIrs();
282 LOG_ERROR(Input, "Error enabling IRS");
283 }
284
285 if (nfc_enabled && supported_features.nfc) {
286 auto result = nfc_protocol->EnableNfc();
287 if (result == DriverResult::Success) {
288 result = nfc_protocol->StartNFCPollingMode();
289 }
290 if (result == DriverResult::Success) {
291 disable_input_thread = false;
292 return result;
293 }
294 nfc_protocol->DisableNfc();
295 LOG_ERROR(Input, "Error enabling NFC");
296 }
297
298 if (hidbus_enabled && supported_features.hidbus) {
299 auto result = ring_protocol->EnableRingCon();
300 if (result == DriverResult::Success) {
301 result = ring_protocol->StartRingconPolling();
302 }
303 if (result == DriverResult::Success) {
304 ring_connected = true;
305 disable_input_thread = false;
306 return result;
307 }
308 ring_connected = false;
309 ring_protocol->DisableRingCon();
310 LOG_ERROR(Input, "Error enabling Ringcon");
311 }
312
313 if (passive_enabled && supported_features.passive) {
314 const auto result = generic_protocol->EnablePassiveMode();
315 if (result == DriverResult::Success) {
316 disable_input_thread = false;
317 return result;
318 }
319 LOG_ERROR(Input, "Error enabling passive mode");
320 }
321
322 // Default Mode
323 const auto result = generic_protocol->EnableActiveMode();
324 if (result != DriverResult::Success) {
325 LOG_ERROR(Input, "Error enabling active mode");
326 }
327
328 disable_input_thread = false;
329 return result;
330}
331
332JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
333 SupportedFeatures features{
334 .passive = true,
335 .motion = true,
336 .vibration = true,
337 };
338
339 if (device_type == ControllerType::Right) {
340 features.nfc = true;
341 features.irs = true;
342 features.hidbus = true;
343 }
344
345 if (device_type == ControllerType::Pro) {
346 features.nfc = true;
347 }
348 return features;
349}
350
351bool JoyconDriver::IsInputThreadValid() const {
352 if (!is_connected.load()) {
353 return false;
354 }
355 if (hidapi_handle->handle == nullptr) {
356 return false;
357 }
358 // Controller is not responding. Terminate connection
359 if (error_counter > MaxErrorCount) {
360 return false;
361 }
362 return true;
363}
364
365bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
366 if (status <= -1) {
367 error_counter++;
368 return false;
369 }
370 // There's no new data
371 if (status == 0) {
372 return false;
373 }
374 // No reply ever starts with zero
375 if (buffer[0] == 0x00) {
376 error_counter++;
377 return false;
378 }
379 error_counter = 0;
380 return true;
381}
382
383DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
384 std::scoped_lock lock{mutex};
385 if (disable_input_thread) {
386 return DriverResult::HandleInUse;
387 }
388 return rumble_protocol->SendVibration(vibration);
389}
390
391DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
392 std::scoped_lock lock{mutex};
393 if (disable_input_thread) {
394 return DriverResult::HandleInUse;
395 }
396 return generic_protocol->SetLedPattern(led_pattern);
397}
398
399DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
400 std::scoped_lock lock{mutex};
401 if (disable_input_thread) {
402 return DriverResult::HandleInUse;
403 }
404 disable_input_thread = true;
405 const auto result = irs_protocol->SetIrsConfig(mode_, format_);
406 disable_input_thread = false;
407 return result;
408}
409
410DriverResult JoyconDriver::SetPasiveMode() {
411 std::scoped_lock lock{mutex};
412 motion_enabled = false;
413 hidbus_enabled = false;
414 nfc_enabled = false;
415 passive_enabled = true;
416 irs_enabled = false;
417 return SetPollingMode();
418}
419
420DriverResult JoyconDriver::SetActiveMode() {
421 if (is_ring_disabled_by_irs) {
422 is_ring_disabled_by_irs = false;
423 SetActiveMode();
424 return SetRingConMode();
425 }
426
427 std::scoped_lock lock{mutex};
428 motion_enabled = true;
429 hidbus_enabled = false;
430 nfc_enabled = false;
431 passive_enabled = false;
432 irs_enabled = false;
433 return SetPollingMode();
434}
435
436DriverResult JoyconDriver::SetIrMode() {
437 std::scoped_lock lock{mutex};
438
439 if (!supported_features.irs) {
440 return DriverResult::NotSupported;
441 }
442
443 if (ring_connected) {
444 is_ring_disabled_by_irs = true;
445 }
446
447 motion_enabled = false;
448 hidbus_enabled = false;
449 nfc_enabled = false;
450 passive_enabled = false;
451 irs_enabled = true;
452 return SetPollingMode();
453}
454
455DriverResult JoyconDriver::SetNfcMode() {
456 std::scoped_lock lock{mutex};
457
458 if (!supported_features.nfc) {
459 return DriverResult::NotSupported;
460 }
461
462 motion_enabled = true;
463 hidbus_enabled = false;
464 nfc_enabled = true;
465 passive_enabled = false;
466 irs_enabled = false;
467 return SetPollingMode();
468}
469
470DriverResult JoyconDriver::SetRingConMode() {
471 std::scoped_lock lock{mutex};
472
473 if (!supported_features.hidbus) {
474 return DriverResult::NotSupported;
475 }
476
477 motion_enabled = true;
478 hidbus_enabled = true;
479 nfc_enabled = false;
480 passive_enabled = false;
481 irs_enabled = false;
482
483 const auto result = SetPollingMode();
484
485 if (!ring_connected) {
486 return DriverResult::NoDeviceDetected;
487 }
488
489 return result;
490}
491
492bool JoyconDriver::IsConnected() const {
493 std::scoped_lock lock{mutex};
494 return is_connected.load();
495}
496
497bool JoyconDriver::IsVibrationEnabled() const {
498 std::scoped_lock lock{mutex};
499 return vibration_enabled;
500}
501
502FirmwareVersion JoyconDriver::GetDeviceVersion() const {
503 std::scoped_lock lock{mutex};
504 return version;
505}
506
507Color JoyconDriver::GetDeviceColor() const {
508 std::scoped_lock lock{mutex};
509 return color;
510}
511
512std::size_t JoyconDriver::GetDevicePort() const {
513 std::scoped_lock lock{mutex};
514 return port;
515}
516
517ControllerType JoyconDriver::GetDeviceType() const {
518 std::scoped_lock lock{mutex};
519 return device_type;
520}
521
522ControllerType JoyconDriver::GetHandleDeviceType() const {
523 std::scoped_lock lock{mutex};
524 return handle_device_type;
525}
526
527SerialNumber JoyconDriver::GetSerialNumber() const {
528 std::scoped_lock lock{mutex};
529 return serial_number;
530}
531
532SerialNumber JoyconDriver::GetHandleSerialNumber() const {
533 std::scoped_lock lock{mutex};
534 return handle_serial_number;
535}
536
537void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
538 joycon_poller->SetCallbacks(callbacks);
539}
540
541DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
542 ControllerType& controller_type) {
543 static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{
544 std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
545 {0x2007, ControllerType::Right},
546 };
547 constexpr u16 nintendo_vendor_id = 0x057e;
548
549 controller_type = ControllerType::None;
550 if (device_info->vendor_id != nintendo_vendor_id) {
551 return DriverResult::UnsupportedControllerType;
552 }
553
554 for (const auto& [product_id, type] : supported_devices) {
555 if (device_info->product_id == static_cast<u16>(product_id)) {
556 controller_type = type;
557 return Joycon::DriverResult::Success;
558 }
559 }
560 return Joycon::DriverResult::UnsupportedControllerType;
561}
562
563DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
564 SerialNumber& serial_number) {
565 if (device_info->serial_number == nullptr) {
566 return DriverResult::Unknown;
567 }
568 std::memcpy(&serial_number, device_info->serial_number, 15);
569 return Joycon::DriverResult::Success;
570}
571
572} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
new file mode 100644
index 000000000..c1e189fa5
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.h
@@ -0,0 +1,150 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <functional>
8#include <mutex>
9#include <span>
10#include <thread>
11
12#include "input_common/helpers/joycon_protocol/joycon_types.h"
13
14namespace InputCommon::Joycon {
15class CalibrationProtocol;
16class GenericProtocol;
17class IrsProtocol;
18class NfcProtocol;
19class JoyconPoller;
20class RingConProtocol;
21class RumbleProtocol;
22
23class JoyconDriver final {
24public:
25 explicit JoyconDriver(std::size_t port_);
26
27 ~JoyconDriver();
28
29 DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
30 DriverResult InitializeDevice();
31 void Stop();
32
33 bool IsConnected() const;
34 bool IsVibrationEnabled() const;
35
36 FirmwareVersion GetDeviceVersion() const;
37 Color GetDeviceColor() const;
38 std::size_t GetDevicePort() const;
39 ControllerType GetDeviceType() const;
40 ControllerType GetHandleDeviceType() const;
41 SerialNumber GetSerialNumber() const;
42 SerialNumber GetHandleSerialNumber() const;
43
44 DriverResult SetVibration(const VibrationValue& vibration);
45 DriverResult SetLedConfig(u8 led_pattern);
46 DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
47 DriverResult SetPasiveMode();
48 DriverResult SetActiveMode();
49 DriverResult SetIrMode();
50 DriverResult SetNfcMode();
51 DriverResult SetRingConMode();
52
53 void SetCallbacks(const JoyconCallbacks& callbacks);
54
55 // Returns device type from hidapi handle
56 static DriverResult GetDeviceType(SDL_hid_device_info* device_info,
57 ControllerType& controller_type);
58
59 // Returns serial number from hidapi handle
60 static DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
61 SerialNumber& serial_number);
62
63private:
64 struct SupportedFeatures {
65 bool passive{};
66 bool hidbus{};
67 bool irs{};
68 bool motion{};
69 bool nfc{};
70 bool vibration{};
71 };
72
73 /// Main thread, actively request new data from the handle
74 void InputThread(std::stop_token stop_token);
75
76 /// Called everytime a valid package arrives
77 void OnNewData(std::span<u8> buffer);
78
79 /// Updates device configuration to enable or disable features
80 DriverResult SetPollingMode();
81
82 /// Returns true if input thread is valid and doesn't need to be stopped
83 bool IsInputThreadValid() const;
84
85 /// Returns true if the data should be interpreted. Otherwise the error counter is incremented
86 bool IsPayloadCorrect(int status, std::span<const u8> buffer);
87
88 /// Returns a list of supported features that can be enabled on this device
89 SupportedFeatures GetSupportedFeatures();
90
91 // Protocol Features
92 std::unique_ptr<CalibrationProtocol> calibration_protocol;
93 std::unique_ptr<GenericProtocol> generic_protocol;
94 std::unique_ptr<IrsProtocol> irs_protocol;
95 std::unique_ptr<NfcProtocol> nfc_protocol;
96 std::unique_ptr<JoyconPoller> joycon_poller;
97 std::unique_ptr<RingConProtocol> ring_protocol;
98 std::unique_ptr<RumbleProtocol> rumble_protocol;
99
100 // Connection status
101 std::atomic<bool> is_connected{};
102 u64 delta_time;
103 std::size_t error_counter{};
104 std::shared_ptr<JoyconHandle> hidapi_handle;
105 std::chrono::time_point<std::chrono::steady_clock> last_update;
106
107 // External device status
108 bool starlink_connected{};
109 bool ring_connected{};
110 bool amiibo_detected{};
111 bool is_ring_disabled_by_irs{};
112
113 // Harware configuration
114 u8 leds{};
115 ReportMode mode{};
116 bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
117 bool hidbus_enabled{}; // External device support
118 bool irs_enabled{}; // Infrared camera input
119 bool motion_enabled{}; // Enables motion input
120 bool nfc_enabled{}; // Enables Amiibo detection
121 bool vibration_enabled{}; // Allows vibrations
122
123 // Calibration data
124 GyroSensitivity gyro_sensitivity{};
125 GyroPerformance gyro_performance{};
126 AccelerometerSensitivity accelerometer_sensitivity{};
127 AccelerometerPerformance accelerometer_performance{};
128 JoyStickCalibration left_stick_calibration{};
129 JoyStickCalibration right_stick_calibration{};
130 MotionCalibration motion_calibration{};
131 RingCalibration ring_calibration{};
132
133 // Fixed joycon info
134 FirmwareVersion version{};
135 Color color{};
136 std::size_t port{};
137 ControllerType device_type{}; // Device type reported by controller
138 ControllerType handle_device_type{}; // Device type reported by hidapi
139 SerialNumber serial_number{}; // Serial number reported by controller
140 SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
141 SupportedFeatures supported_features{};
142
143 // Thread related
144 mutable std::mutex mutex;
145 std::jthread input_thread;
146 bool input_thread_running{};
147 bool disable_input_thread{};
148};
149
150} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
new file mode 100644
index 000000000..f6e7e97d5
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -0,0 +1,184 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cstring>
5
6#include "input_common/helpers/joycon_protocol/calibration.h"
7#include "input_common/helpers/joycon_protocol/joycon_types.h"
8
9namespace InputCommon::Joycon {
10
11CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
12 : JoyconCommonProtocol(std::move(handle)) {}
13
14DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
15 ScopedSetBlocking sb(this);
16 std::vector<u8> buffer;
17 DriverResult result{DriverResult::Success};
18 calibration = {};
19
20 result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
21
22 if (result == DriverResult::Success) {
23 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
24 if (has_user_calibration) {
25 result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer);
26 } else {
27 result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer);
28 }
29 }
30
31 if (result == DriverResult::Success) {
32 calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
33 calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
34 calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
35 calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
36 calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
37 calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
38 }
39
40 // Nintendo fix for drifting stick
41 // result = ReadSPI(0x60, 0x86 ,buffer, 16);
42 // calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
43
44 // Set a valid default calibration if data is missing
45 ValidateCalibration(calibration);
46
47 return result;
48}
49
50DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
51 ScopedSetBlocking sb(this);
52 std::vector<u8> buffer;
53 DriverResult result{DriverResult::Success};
54 calibration = {};
55
56 result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
57
58 if (result == DriverResult::Success) {
59 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
60 if (has_user_calibration) {
61 result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer);
62 } else {
63 result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer);
64 }
65 }
66
67 if (result == DriverResult::Success) {
68 calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
69 calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
70 calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
71 calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
72 calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
73 calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
74 }
75
76 // Nintendo fix for drifting stick
77 // buffer = ReadSPI(0x60, 0x98 , 16);
78 // joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
79
80 // Set a valid default calibration if data is missing
81 ValidateCalibration(calibration);
82
83 return result;
84}
85
86DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
87 ScopedSetBlocking sb(this);
88 std::vector<u8> buffer;
89 DriverResult result{DriverResult::Success};
90 calibration = {};
91
92 result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
93
94 if (result == DriverResult::Success) {
95 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
96 if (has_user_calibration) {
97 result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer);
98 } else {
99 result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer);
100 }
101 }
102
103 if (result == DriverResult::Success) {
104 IMUCalibration device_calibration{};
105 memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration));
106 calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0];
107 calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1];
108 calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2];
109
110 calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0];
111 calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1];
112 calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2];
113
114 calibration.gyro[0].offset = device_calibration.gyroscope_offset[0];
115 calibration.gyro[1].offset = device_calibration.gyroscope_offset[1];
116 calibration.gyro[2].offset = device_calibration.gyroscope_offset[2];
117
118 calibration.gyro[0].scale = device_calibration.gyroscope_scale[0];
119 calibration.gyro[1].scale = device_calibration.gyroscope_scale[1];
120 calibration.gyro[2].scale = device_calibration.gyroscope_scale[2];
121 }
122
123 ValidateCalibration(calibration);
124
125 return result;
126}
127
128DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
129 s16 current_value) {
130 // TODO: Get default calibration form ring itself
131 if (ring_data_max == 0 && ring_data_min == 0) {
132 ring_data_max = current_value + 800;
133 ring_data_min = current_value - 800;
134 ring_data_default = current_value;
135 }
136 ring_data_max = std::max(ring_data_max, current_value);
137 ring_data_min = std::min(ring_data_min, current_value);
138 calibration = {
139 .default_value = ring_data_default,
140 .max_value = ring_data_max,
141 .min_value = ring_data_min,
142 };
143 return DriverResult::Success;
144}
145
146void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
147 constexpr u16 DefaultStickCenter{2048};
148 constexpr u16 DefaultStickRange{1740};
149
150 if (calibration.x.center == 0xFFF || calibration.x.center == 0) {
151 calibration.x.center = DefaultStickCenter;
152 }
153 if (calibration.x.max == 0xFFF || calibration.x.max == 0) {
154 calibration.x.max = DefaultStickRange;
155 }
156 if (calibration.x.min == 0xFFF || calibration.x.min == 0) {
157 calibration.x.min = DefaultStickRange;
158 }
159
160 if (calibration.y.center == 0xFFF || calibration.y.center == 0) {
161 calibration.y.center = DefaultStickCenter;
162 }
163 if (calibration.y.max == 0xFFF || calibration.y.max == 0) {
164 calibration.y.max = DefaultStickRange;
165 }
166 if (calibration.y.min == 0xFFF || calibration.y.min == 0) {
167 calibration.y.min = DefaultStickRange;
168 }
169}
170
171void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
172 for (auto& sensor : calibration.accelerometer) {
173 if (sensor.scale == 0) {
174 sensor.scale = 0x4000;
175 }
176 }
177 for (auto& sensor : calibration.gyro) {
178 if (sensor.scale == 0) {
179 sensor.scale = 0x3be7;
180 }
181 }
182}
183
184} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h
new file mode 100644
index 000000000..afb52a36a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.h
@@ -0,0 +1,64 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14
15namespace InputCommon::Joycon {
16enum class DriverResult;
17struct JoyStickCalibration;
18struct IMUCalibration;
19struct JoyconHandle;
20} // namespace InputCommon::Joycon
21
22namespace InputCommon::Joycon {
23
24/// Driver functions related to retrieving calibration data from the device
25class CalibrationProtocol final : private JoyconCommonProtocol {
26public:
27 explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
28
29 /**
30 * Sends a request to obtain the left stick calibration from memory
31 * @param is_factory_calibration if true factory values will be returned
32 * @returns JoyStickCalibration of the left joystick
33 */
34 DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);
35
36 /**
37 * Sends a request to obtain the right stick calibration from memory
38 * @param is_factory_calibration if true factory values will be returned
39 * @returns JoyStickCalibration of the right joystick
40 */
41 DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);
42
43 /**
44 * Sends a request to obtain the motion calibration from memory
45 * @returns ImuCalibration of the motion sensor
46 */
47 DriverResult GetImuCalibration(MotionCalibration& calibration);
48
49 /**
50 * Calculates on run time the proper calibration of the ring controller
51 * @returns RingCalibration of the ring sensor
52 */
53 DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
54
55private:
56 void ValidateCalibration(JoyStickCalibration& calibration);
57 void ValidateCalibration(MotionCalibration& calibration);
58
59 s16 ring_data_max = 0;
60 s16 ring_data_default = 0;
61 s16 ring_data_min = 0;
62};
63
64} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
new file mode 100644
index 000000000..417d0dcc5
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/common_protocol.h"
6
7namespace InputCommon::Joycon {
8JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
9 : hidapi_handle{std::move(hidapi_handle_)} {}
10
11u8 JoyconCommonProtocol::GetCounter() {
12 hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
13 return hidapi_handle->packet_counter;
14}
15
16void JoyconCommonProtocol::SetBlocking() {
17 SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
18}
19
20void JoyconCommonProtocol::SetNonBlocking() {
21 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
22}
23
24DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
25 std::vector<u8> buffer;
26 const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer);
27 controller_type = ControllerType::None;
28
29 if (result == DriverResult::Success) {
30 controller_type = static_cast<ControllerType>(buffer[0]);
31 // Fallback to 3rd party pro controllers
32 if (controller_type == ControllerType::None) {
33 controller_type = ControllerType::Pro;
34 }
35 }
36
37 return result;
38}
39
40DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
41 ControllerType controller_type{ControllerType::None};
42 const auto result = GetDeviceType(controller_type);
43 if (result != DriverResult::Success || controller_type == ControllerType::None) {
44 return DriverResult::UnsupportedControllerType;
45 }
46
47 hidapi_handle->handle =
48 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
49
50 if (!hidapi_handle->handle) {
51 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
52 device_info->vendor_id, device_info->product_id);
53 return DriverResult::HandleInUse;
54 }
55
56 SetNonBlocking();
57 return DriverResult::Success;
58}
59
60DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
61 const std::array<u8, 1> buffer{static_cast<u8>(report_mode)};
62 return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer);
63}
64
65DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
66 const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
67
68 if (result == -1) {
69 return DriverResult::ErrorWritingData;
70 }
71
72 return DriverResult::Success;
73}
74
75DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
76 constexpr int timeout_mili = 66;
77 constexpr int MaxTries = 15;
78 int tries = 0;
79 output.resize(MaxSubCommandResponseSize);
80
81 do {
82 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
83 MaxSubCommandResponseSize, timeout_mili);
84
85 if (result < 1) {
86 LOG_ERROR(Input, "No response from joycon");
87 }
88 if (tries++ > MaxTries) {
89 return DriverResult::Timeout;
90 }
91 } while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
92
93 if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
94 return DriverResult::WrongReply;
95 }
96
97 return DriverResult::Success;
98}
99
100DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
101 std::vector<u8>& output) {
102 std::vector<u8> local_buffer(MaxResponseSize);
103
104 local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
105 local_buffer[1] = GetCounter();
106 local_buffer[10] = static_cast<u8>(sc);
107 for (std::size_t i = 0; i < buffer.size(); ++i) {
108 local_buffer[11 + i] = buffer[i];
109 }
110
111 auto result = SendData(local_buffer);
112
113 if (result != DriverResult::Success) {
114 return result;
115 }
116
117 result = GetSubCommandResponse(sc, output);
118
119 return DriverResult::Success;
120}
121
122DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
123 std::vector<u8> output;
124 return SendSubCommand(sc, buffer, output);
125}
126
127DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
128 std::vector<u8> local_buffer(MaxResponseSize);
129
130 local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
131 local_buffer[1] = GetCounter();
132 local_buffer[10] = static_cast<u8>(sc);
133 for (std::size_t i = 0; i < buffer.size(); ++i) {
134 local_buffer[11 + i] = buffer[i];
135 }
136
137 return SendData(local_buffer);
138}
139
140DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
141 std::vector<u8> local_buffer(MaxResponseSize);
142
143 local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
144 local_buffer[1] = GetCounter();
145
146 memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
147
148 return SendData(local_buffer);
149}
150
151DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
152 constexpr std::size_t MaxTries = 10;
153 std::size_t tries = 0;
154 std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size};
155 std::vector<u8> local_buffer(size + 20);
156
157 buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
158 buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
159 do {
160 const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
161 if (result != DriverResult::Success) {
162 return result;
163 }
164
165 if (tries++ > MaxTries) {
166 return DriverResult::Timeout;
167 }
168 } while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
169
170 // Remove header from output
171 output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
172 return DriverResult::Success;
173}
174
175DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
176 const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
177 const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
178
179 if (result != DriverResult::Success) {
180 LOG_ERROR(Input, "SendMCUData failed with error {}", result);
181 }
182
183 return result;
184}
185
186DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
187 LOG_DEBUG(Input, "ConfigureMCU");
188 std::array<u8, sizeof(MCUConfig)> config_buffer;
189 memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
190 config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
191
192 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
193
194 if (result != DriverResult::Success) {
195 LOG_ERROR(Input, "Set MCU config failed with error {}", result);
196 }
197
198 return result;
199}
200
201DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
202 std::vector<u8>& output) {
203 const int report_mode = static_cast<u8>(report_mode_);
204 constexpr int TimeoutMili = 200;
205 constexpr int MaxTries = 9;
206 int tries = 0;
207 output.resize(0x170);
208
209 do {
210 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
211
212 if (result < 1) {
213 LOG_ERROR(Input, "No response from joycon attempt {}", tries);
214 }
215 if (tries++ > MaxTries) {
216 return DriverResult::Timeout;
217 }
218 } while (output[0] != report_mode || output[49] == 0xFF);
219
220 if (output[0] != report_mode || output[49] == 0xFF) {
221 return DriverResult::WrongReply;
222 }
223
224 return DriverResult::Success;
225}
226
227DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
228 std::span<const u8> buffer,
229 std::vector<u8>& output) {
230 std::vector<u8> local_buffer(MaxResponseSize);
231
232 local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
233 local_buffer[1] = GetCounter();
234 local_buffer[9] = static_cast<u8>(sc);
235 for (std::size_t i = 0; i < buffer.size(); ++i) {
236 local_buffer[10 + i] = buffer[i];
237 }
238
239 auto result = SendData(local_buffer);
240
241 if (result != DriverResult::Success) {
242 return result;
243 }
244
245 result = GetMCUDataResponse(report_mode, output);
246
247 return DriverResult::Success;
248}
249
250DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
251 std::vector<u8> output;
252 constexpr std::size_t MaxTries{8};
253 std::size_t tries{};
254
255 do {
256 const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
257 const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
258
259 if (result != DriverResult::Success) {
260 return result;
261 }
262
263 if (tries++ > MaxTries) {
264 return DriverResult::WrongReply;
265 }
266 } while (output[49] != 1 || output[56] != static_cast<u8>(mode));
267
268 return DriverResult::Success;
269}
270
271// crc-8-ccitt / polynomial 0x07 look up table
272constexpr std::array<u8, 256> mcu_crc8_table = {
273 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
274 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
275 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
276 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
277 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
278 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
279 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
280 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
281 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
282 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
283 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
284 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
285 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
286 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
287 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
288 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
289
290u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
291 u8 crc8 = 0x0;
292
293 for (int i = 0; i < size; ++i) {
294 crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
295 }
296 return crc8;
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
new file mode 100644
index 000000000..903bcf402
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -0,0 +1,173 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <memory>
12#include <span>
13#include <vector>
14
15#include "common/common_types.h"
16#include "input_common/helpers/joycon_protocol/joycon_types.h"
17
18namespace InputCommon::Joycon {
19
20/// Joycon driver functions that handle low level communication
21class JoyconCommonProtocol {
22public:
23 explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
24
25 /**
26 * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
27 * data to read before returning.
28 */
29 void SetBlocking();
30
31 /**
32 * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
33 * immediately with a value of 0 if there is no data to be read
34 */
35 void SetNonBlocking();
36
37 /**
38 * Sends a request to obtain the joycon type from device
39 * @returns controller type of the joycon
40 */
41 DriverResult GetDeviceType(ControllerType& controller_type);
42
43 /**
44 * Verifies and sets the joycon_handle if device is valid
45 * @param device info from the driver
46 * @returns success if the device is valid
47 */
48 DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
49
50 /**
51 * Sends a request to set the polling mode of the joycon
52 * @param report_mode polling mode to be set
53 */
54 DriverResult SetReportMode(Joycon::ReportMode report_mode);
55
56 /**
57 * Sends data to the joycon device
58 * @param buffer data to be send
59 */
60 DriverResult SendData(std::span<const u8> buffer);
61
62 /**
63 * Waits for incoming data of the joycon device that matchs the subcommand
64 * @param sub_command type of data to be returned
65 * @returns a buffer containing the responce
66 */
67 DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output);
68
69 /**
70 * Sends a sub command to the device and waits for it's reply
71 * @param sc sub command to be send
72 * @param buffer data to be send
73 * @returns output buffer containing the responce
74 */
75 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
76
77 /**
78 * Sends a sub command to the device and waits for it's reply and ignores the output
79 * @param sc sub command to be send
80 * @param buffer data to be send
81 */
82 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer);
83
84 /**
85 * Sends a mcu command to the device
86 * @param sc sub command to be send
87 * @param buffer data to be send
88 */
89 DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer);
90
91 /**
92 * Sends vibration data to the joycon
93 * @param buffer data to be send
94 */
95 DriverResult SendVibrationReport(std::span<const u8> buffer);
96
97 /**
98 * Reads the SPI memory stored on the joycon
99 * @param Initial address location
100 * @param size in bytes to be read
101 * @returns output buffer containing the responce
102 */
103 DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output);
104
105 /**
106 * Enables MCU chip on the joycon
107 * @param enable if true the chip will be enabled
108 */
109 DriverResult EnableMCU(bool enable);
110
111 /**
112 * Configures the MCU to the correspoinding mode
113 * @param MCUConfig configuration
114 */
115 DriverResult ConfigureMCU(const MCUConfig& config);
116
117 /**
118 * Waits until there's MCU data available. On timeout returns error
119 * @param report mode of the expected reply
120 * @returns a buffer containing the responce
121 */
122 DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output);
123
124 /**
125 * Sends data to the MCU chip and waits for it's reply
126 * @param report mode of the expected reply
127 * @param sub command to be send
128 * @param buffer data to be send
129 * @returns output buffer containing the responce
130 */
131 DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
132 std::vector<u8>& output);
133
134 /**
135 * Wait's until the MCU chip is on the specified mode
136 * @param report mode of the expected reply
137 * @param MCUMode configuration
138 */
139 DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
140
141 /**
142 * Calculates the checksum from the MCU data
143 * @param buffer containing the data to be send
144 * @param size of the buffer in bytes
145 * @returns byte with the correct checksum
146 */
147 u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
148
149private:
150 /**
151 * Increments and returns the packet counter of the handle
152 * @param joycon_handle device to send the data
153 * @returns packet counter value
154 */
155 u8 GetCounter();
156
157 std::shared_ptr<JoyconHandle> hidapi_handle;
158};
159
160class ScopedSetBlocking {
161public:
162 explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} {
163 m_self->SetBlocking();
164 }
165
166 ~ScopedSetBlocking() {
167 m_self->SetNonBlocking();
168 }
169
170private:
171 JoyconCommonProtocol* m_self{};
172};
173} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
new file mode 100644
index 000000000..52bb8b61a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
@@ -0,0 +1,125 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/generic_functions.h"
6
7namespace InputCommon::Joycon {
8
9GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(std::move(handle)) {}
11
12DriverResult GenericProtocol::EnablePassiveMode() {
13 ScopedSetBlocking sb(this);
14 return SetReportMode(ReportMode::SIMPLE_HID_MODE);
15}
16
17DriverResult GenericProtocol::EnableActiveMode() {
18 ScopedSetBlocking sb(this);
19 return SetReportMode(ReportMode::STANDARD_FULL_60HZ);
20}
21
22DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
23 ScopedSetBlocking sb(this);
24 std::vector<u8> output;
25
26 const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
27
28 device_info = {};
29 if (result == DriverResult::Success) {
30 memcpy(&device_info, output.data(), sizeof(DeviceInfo));
31 }
32
33 return result;
34}
35
36DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) {
37 return GetDeviceType(controller_type);
38}
39
40DriverResult GenericProtocol::EnableImu(bool enable) {
41 ScopedSetBlocking sb(this);
42 const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
43 return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
44}
45
46DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
47 AccelerometerSensitivity asen,
48 AccelerometerPerformance afrec) {
49 ScopedSetBlocking sb(this);
50 const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
51 static_cast<u8>(gfrec), static_cast<u8>(afrec)};
52 return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer);
53}
54
55DriverResult GenericProtocol::GetBattery(u32& battery_level) {
56 // This function is meant to request the high resolution battery status
57 battery_level = 0;
58 return DriverResult::NotSupported;
59}
60
61DriverResult GenericProtocol::GetColor(Color& color) {
62 ScopedSetBlocking sb(this);
63 std::vector<u8> buffer;
64 const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
65
66 color = {};
67 if (result == DriverResult::Success) {
68 color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
69 color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
70 color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]);
71 color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
72 }
73
74 return result;
75}
76
77DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
78 ScopedSetBlocking sb(this);
79 std::vector<u8> buffer;
80 const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
81
82 serial_number = {};
83 if (result == DriverResult::Success) {
84 memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber));
85 }
86
87 return result;
88}
89
90DriverResult GenericProtocol::GetTemperature(u32& temperature) {
91 // Not all devices have temperature sensor
92 temperature = 25;
93 return DriverResult::NotSupported;
94}
95
96DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
97 DeviceInfo device_info{};
98
99 const auto result = GetDeviceInfo(device_info);
100 version = device_info.firmware;
101
102 return result;
103}
104
105DriverResult GenericProtocol::SetHomeLight() {
106 ScopedSetBlocking sb(this);
107 static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
108 return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer);
109}
110
111DriverResult GenericProtocol::SetLedBusy() {
112 return DriverResult::NotSupported;
113}
114
115DriverResult GenericProtocol::SetLedPattern(u8 leds) {
116 ScopedSetBlocking sb(this);
117 const std::array<u8, 1> buffer{leds};
118 return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer);
119}
120
121DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
122 return SetLedPattern(static_cast<u8>(leds << 4));
123}
124
125} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
new file mode 100644
index 000000000..239bb7dbf
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -0,0 +1,108 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include "input_common/helpers/joycon_protocol/common_protocol.h"
12#include "input_common/helpers/joycon_protocol/joycon_types.h"
13
14namespace InputCommon::Joycon {
15
16/// Joycon driver functions that easily implemented
17class GenericProtocol final : private JoyconCommonProtocol {
18public:
19 explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle);
20
21 /// Enables passive mode. This mode only sends button data on change. Sticks will return digital
22 /// data instead of analog. Motion will be disabled
23 DriverResult EnablePassiveMode();
24
25 /// Enables active mode. This mode will return the current status every 5-15ms
26 DriverResult EnableActiveMode();
27
28 /**
29 * Sends a request to obtain the joycon firmware and mac from handle
30 * @returns controller device info
31 */
32 DriverResult GetDeviceInfo(DeviceInfo& controller_type);
33
34 /**
35 * Sends a request to obtain the joycon type from handle
36 * @returns controller type of the joycon
37 */
38 DriverResult GetControllerType(ControllerType& controller_type);
39
40 /**
41 * Enables motion input
42 * @param enable if true motion data will be enabled
43 */
44 DriverResult EnableImu(bool enable);
45
46 /**
47 * Configures the motion sensor with the specified parameters
48 * @param gsen gyroscope sensor sensitvity in degrees per second
49 * @param gfrec gyroscope sensor frequency in hertz
50 * @param asen accelerometer sensitivity in G force
51 * @param afrec accelerometer frequency in hertz
52 */
53 DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
54 AccelerometerSensitivity asen, AccelerometerPerformance afrec);
55
56 /**
57 * Request battery level from the device
58 * @returns battery level
59 */
60 DriverResult GetBattery(u32& battery_level);
61
62 /**
63 * Request joycon colors from the device
64 * @returns colors of the body and buttons
65 */
66 DriverResult GetColor(Color& color);
67
68 /**
69 * Request joycon serial number from the device
70 * @returns 16 byte serial number
71 */
72 DriverResult GetSerialNumber(SerialNumber& serial_number);
73
74 /**
75 * Request joycon serial number from the device
76 * @returns 16 byte serial number
77 */
78 DriverResult GetTemperature(u32& temperature);
79
80 /**
81 * Request joycon serial number from the device
82 * @returns 16 byte serial number
83 */
84 DriverResult GetVersionNumber(FirmwareVersion& version);
85
86 /**
87 * Sets home led behaviour
88 */
89 DriverResult SetHomeLight();
90
91 /**
92 * Sets home led into a slow breathing state
93 */
94 DriverResult SetLedBusy();
95
96 /**
97 * Sets the 4 player leds on the joycon on a solid state
98 * @params bit flag containing the led state
99 */
100 DriverResult SetLedPattern(u8 leds);
101
102 /**
103 * Sets the 4 player leds on the joycon on a blinking state
104 * @returns bit flag containing the led state
105 */
106 DriverResult SetLedBlinkPattern(u8 leds);
107};
108} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp
new file mode 100644
index 000000000..09e17bc5b
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.cpp
@@ -0,0 +1,298 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <thread>
5#include "common/logging/log.h"
6#include "input_common/helpers/joycon_protocol/irs.h"
7
8namespace InputCommon::Joycon {
9
10IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
11 : JoyconCommonProtocol(std::move(handle)) {}
12
13DriverResult IrsProtocol::EnableIrs() {
14 LOG_INFO(Input, "Enable IRS");
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17
18 if (result == DriverResult::Success) {
19 result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
20 }
21 if (result == DriverResult::Success) {
22 result = EnableMCU(true);
23 }
24 if (result == DriverResult::Success) {
25 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
26 }
27 if (result == DriverResult::Success) {
28 const MCUConfig config{
29 .command = MCUCommand::ConfigureMCU,
30 .sub_command = MCUSubCommand::SetMCUMode,
31 .mode = MCUMode::IR,
32 .crc = {},
33 };
34
35 result = ConfigureMCU(config);
36 }
37 if (result == DriverResult::Success) {
38 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
39 }
40 if (result == DriverResult::Success) {
41 result = ConfigureIrs();
42 }
43 if (result == DriverResult::Success) {
44 result = WriteRegistersStep1();
45 }
46 if (result == DriverResult::Success) {
47 result = WriteRegistersStep2();
48 }
49
50 is_enabled = true;
51
52 return result;
53}
54
55DriverResult IrsProtocol::DisableIrs() {
56 LOG_DEBUG(Input, "Disable IRS");
57 ScopedSetBlocking sb(this);
58 DriverResult result{DriverResult::Success};
59
60 if (result == DriverResult::Success) {
61 result = EnableMCU(false);
62 }
63
64 is_enabled = false;
65
66 return result;
67}
68
69DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) {
70 irs_mode = mode;
71 switch (format) {
72 case IrsResolution::Size320x240:
73 resolution_code = IrsResolutionCode::Size320x240;
74 fragments = IrsFragments::Size320x240;
75 resolution = IrsResolution::Size320x240;
76 break;
77 case IrsResolution::Size160x120:
78 resolution_code = IrsResolutionCode::Size160x120;
79 fragments = IrsFragments::Size160x120;
80 resolution = IrsResolution::Size160x120;
81 break;
82 case IrsResolution::Size80x60:
83 resolution_code = IrsResolutionCode::Size80x60;
84 fragments = IrsFragments::Size80x60;
85 resolution = IrsResolution::Size80x60;
86 break;
87 case IrsResolution::Size20x15:
88 resolution_code = IrsResolutionCode::Size20x15;
89 fragments = IrsFragments::Size20x15;
90 resolution = IrsResolution::Size20x15;
91 break;
92 case IrsResolution::Size40x30:
93 default:
94 resolution_code = IrsResolutionCode::Size40x30;
95 fragments = IrsFragments::Size40x30;
96 resolution = IrsResolution::Size40x30;
97 break;
98 }
99
100 // Restart feature
101 if (is_enabled) {
102 DisableIrs();
103 return EnableIrs();
104 }
105
106 return DriverResult::Success;
107}
108
109DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) {
110 const u8 next_packet_fragment =
111 static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1));
112
113 if (buffer[0] == 0x31 && buffer[49] == 0x03) {
114 u8 new_packet_fragment = buffer[52];
115 if (new_packet_fragment == next_packet_fragment) {
116 packet_fragment = next_packet_fragment;
117 memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300);
118
119 return RequestFrame(packet_fragment);
120 }
121
122 if (new_packet_fragment == packet_fragment) {
123 return RequestFrame(packet_fragment);
124 }
125
126 return ResendFrame(next_packet_fragment);
127 }
128
129 return RequestFrame(packet_fragment);
130}
131
132DriverResult IrsProtocol::ConfigureIrs() {
133 LOG_DEBUG(Input, "Configure IRS");
134 constexpr std::size_t max_tries = 28;
135 std::vector<u8> output;
136 std::size_t tries = 0;
137
138 const IrsConfigure irs_configuration{
139 .command = MCUCommand::ConfigureIR,
140 .sub_command = MCUSubCommand::SetDeviceMode,
141 .irs_mode = IrsMode::ImageTransfer,
142 .number_of_fragments = fragments,
143 .mcu_major_version = 0x0500,
144 .mcu_minor_version = 0x1800,
145 .crc = {},
146 };
147 buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
148
149 std::array<u8, sizeof(IrsConfigure)> request_data{};
150 memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
151 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
152 do {
153 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
154
155 if (result != DriverResult::Success) {
156 return result;
157 }
158 if (tries++ >= max_tries) {
159 return DriverResult::WrongReply;
160 }
161 } while (output[15] != 0x0b);
162
163 return DriverResult::Success;
164}
165
166DriverResult IrsProtocol::WriteRegistersStep1() {
167 LOG_DEBUG(Input, "WriteRegistersStep1");
168 DriverResult result{DriverResult::Success};
169 constexpr std::size_t max_tries = 28;
170 std::vector<u8> output;
171 std::size_t tries = 0;
172
173 const IrsWriteRegisters irs_registers{
174 .command = MCUCommand::ConfigureIR,
175 .sub_command = MCUSubCommand::WriteDeviceRegisters,
176 .number_of_registers = 0x9,
177 .registers =
178 {
179 IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)},
180 {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)},
181 {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)},
182 {IrRegistersAddress::ExposureTime, 0x00},
183 {IrRegistersAddress::Leds, static_cast<u8>(leds)},
184 {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)},
185 {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)},
186 {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)},
187 {IrRegistersAddress::WhitePixelThreshold, 0xc8},
188 },
189 .crc = {},
190 };
191
192 std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
193 memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
194 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
195
196 std::array<u8, 38> mcu_request{0x02};
197 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
198 mcu_request[37] = 0xFF;
199
200 if (result != DriverResult::Success) {
201 return result;
202 }
203
204 do {
205 result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
206
207 // First time we need to set the report mode
208 if (result == DriverResult::Success && tries == 0) {
209 result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
210 }
211 if (result == DriverResult::Success && tries == 0) {
212 GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
213 }
214
215 if (result != DriverResult::Success) {
216 return result;
217 }
218 if (tries++ >= max_tries) {
219 return DriverResult::WrongReply;
220 }
221 } while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23);
222
223 return DriverResult::Success;
224}
225
226DriverResult IrsProtocol::WriteRegistersStep2() {
227 LOG_DEBUG(Input, "WriteRegistersStep2");
228 constexpr std::size_t max_tries = 28;
229 std::vector<u8> output;
230 std::size_t tries = 0;
231
232 const IrsWriteRegisters irs_registers{
233 .command = MCUCommand::ConfigureIR,
234 .sub_command = MCUSubCommand::WriteDeviceRegisters,
235 .number_of_registers = 0x8,
236 .registers =
237 {
238 IrsRegister{IrRegistersAddress::LedIntensitiyMSB,
239 static_cast<u8>(led_intensity >> 8)},
240 {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)},
241 {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)},
242 {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)},
243 {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)},
244 {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)},
245 {IrRegistersAddress::UpdateTime, 0x2d},
246 {IrRegistersAddress::FinalizeConfig, 0x01},
247 },
248 .crc = {},
249 };
250
251 std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
252 memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
253 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
254 do {
255 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
256
257 if (result != DriverResult::Success) {
258 return result;
259 }
260 if (tries++ >= max_tries) {
261 return DriverResult::WrongReply;
262 }
263 } while (output[15] != 0x13 && output[15] != 0x23);
264
265 return DriverResult::Success;
266}
267
268DriverResult IrsProtocol::RequestFrame(u8 frame) {
269 std::array<u8, 38> mcu_request{};
270 mcu_request[3] = frame;
271 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
272 mcu_request[37] = 0xFF;
273 return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
274}
275
276DriverResult IrsProtocol::ResendFrame(u8 frame) {
277 std::array<u8, 38> mcu_request{};
278 mcu_request[1] = 0x1;
279 mcu_request[2] = frame;
280 mcu_request[3] = 0x0;
281 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
282 mcu_request[37] = 0xFF;
283 return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
284}
285
286std::vector<u8> IrsProtocol::GetImage() const {
287 return buf_image;
288}
289
290IrsResolution IrsProtocol::GetIrsFormat() const {
291 return resolution;
292}
293
294bool IrsProtocol::IsEnabled() const {
295 return is_enabled;
296}
297
298} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h
new file mode 100644
index 000000000..76dfa02ea
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.h
@@ -0,0 +1,63 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class IrsProtocol final : private JoyconCommonProtocol {
19public:
20 explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableIrs();
23
24 DriverResult DisableIrs();
25
26 DriverResult SetIrsConfig(IrsMode mode, IrsResolution format);
27
28 DriverResult RequestImage(std::span<u8> buffer);
29
30 std::vector<u8> GetImage() const;
31
32 IrsResolution GetIrsFormat() const;
33
34 bool IsEnabled() const;
35
36private:
37 DriverResult ConfigureIrs();
38
39 DriverResult WriteRegistersStep1();
40 DriverResult WriteRegistersStep2();
41
42 DriverResult RequestFrame(u8 frame);
43 DriverResult ResendFrame(u8 frame);
44
45 IrsMode irs_mode{IrsMode::ImageTransfer};
46 IrsResolution resolution{IrsResolution::Size40x30};
47 IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30};
48 IrsFragments fragments{IrsFragments::Size40x30};
49 IrLeds leds{IrLeds::BrightAndDim};
50 IrExLedFilter led_filter{IrExLedFilter::Enabled};
51 IrImageFlip image_flip{IrImageFlip::Normal};
52 u8 digital_gain{0x01};
53 u16 exposure{0x2490};
54 u16 led_intensity{0x0f10};
55 u32 denoise{0x012344};
56
57 u8 packet_fragment{};
58 std::vector<u8> buf_image; // 8bpp greyscale image.
59
60 bool is_enabled{};
61};
62
63} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
new file mode 100644
index 000000000..e2d47349f
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -0,0 +1,612 @@
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 = 368;
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 NFCPages {
277 Block0 = 0,
278 Block45 = 45,
279 Block135 = 135,
280 Block231 = 231,
281};
282
283enum class NFCStatus : u8 {
284 LastPackage = 0x04,
285 TagLost = 0x07,
286};
287
288enum class IrsMode : u8 {
289 None = 0x02,
290 Moment = 0x03,
291 Dpd = 0x04,
292 Clustering = 0x06,
293 ImageTransfer = 0x07,
294 Silhouette = 0x08,
295 TeraImage = 0x09,
296 SilhouetteTeraImage = 0x0A,
297};
298
299enum class IrsResolution {
300 Size320x240,
301 Size160x120,
302 Size80x60,
303 Size40x30,
304 Size20x15,
305 None,
306};
307
308enum class IrsResolutionCode : u8 {
309 Size320x240 = 0x00, // Full pixel array
310 Size160x120 = 0x50, // Sensor Binning [2 X 2]
311 Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2]
312 Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4]
313 Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4]
314};
315
316// Size of image divided by 300
317enum class IrsFragments : u8 {
318 Size20x15 = 0x00,
319 Size40x30 = 0x03,
320 Size80x60 = 0x0f,
321 Size160x120 = 0x3f,
322 Size320x240 = 0xFF,
323};
324
325enum class IrLeds : u8 {
326 BrightAndDim = 0x00,
327 Bright = 0x20,
328 Dim = 0x10,
329 None = 0x30,
330};
331
332enum class IrExLedFilter : u8 {
333 Disabled = 0x00,
334 Enabled = 0x03,
335};
336
337enum class IrImageFlip : u8 {
338 Normal = 0x00,
339 Inverted = 0x02,
340};
341
342enum class IrRegistersAddress : u16 {
343 UpdateTime = 0x0400,
344 FinalizeConfig = 0x0700,
345 LedFilter = 0x0e00,
346 Leds = 0x1000,
347 LedIntensitiyMSB = 0x1100,
348 LedIntensitiyLSB = 0x1200,
349 ImageFlip = 0x2d00,
350 Resolution = 0x2e00,
351 DigitalGainLSB = 0x2e01,
352 DigitalGainMSB = 0x2f01,
353 ExposureLSB = 0x3001,
354 ExposureMSB = 0x3101,
355 ExposureTime = 0x3201,
356 WhitePixelThreshold = 0x4301,
357 DenoiseSmoothing = 0x6701,
358 DenoiseEdge = 0x6801,
359 DenoiseColor = 0x6901,
360};
361
362enum class DriverResult {
363 Success,
364 WrongReply,
365 Timeout,
366 UnsupportedControllerType,
367 HandleInUse,
368 ErrorReadingData,
369 ErrorWritingData,
370 NoDeviceDetected,
371 InvalidHandle,
372 NotSupported,
373 Disabled,
374 Unknown,
375};
376
377struct MotionSensorCalibration {
378 s16 offset;
379 s16 scale;
380};
381
382struct MotionCalibration {
383 std::array<MotionSensorCalibration, 3> accelerometer;
384 std::array<MotionSensorCalibration, 3> gyro;
385};
386
387// Basic motion data containing data from the sensors and a timestamp in microseconds
388struct MotionData {
389 float gyro_x{};
390 float gyro_y{};
391 float gyro_z{};
392 float accel_x{};
393 float accel_y{};
394 float accel_z{};
395 u64 delta_timestamp{};
396};
397
398struct JoyStickAxisCalibration {
399 u16 max{1};
400 u16 min{1};
401 u16 center{0};
402};
403
404struct JoyStickCalibration {
405 JoyStickAxisCalibration x;
406 JoyStickAxisCalibration y;
407};
408
409struct RingCalibration {
410 s16 default_value;
411 s16 max_value;
412 s16 min_value;
413};
414
415struct Color {
416 u32 body;
417 u32 buttons;
418 u32 left_grip;
419 u32 right_grip;
420};
421
422struct Battery {
423 union {
424 u8 raw{};
425
426 BitField<0, 4, u8> unknown;
427 BitField<4, 1, u8> charging;
428 BitField<5, 3, u8> status;
429 };
430};
431
432struct VibrationValue {
433 f32 low_amplitude;
434 f32 low_frequency;
435 f32 high_amplitude;
436 f32 high_frequency;
437};
438
439struct JoyconHandle {
440 SDL_hid_device* handle = nullptr;
441 u8 packet_counter{};
442};
443
444struct MCUConfig {
445 MCUCommand command;
446 MCUSubCommand sub_command;
447 MCUMode mode;
448 INSERT_PADDING_BYTES(0x22);
449 u8 crc;
450};
451static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
452
453#pragma pack(push, 1)
454struct InputReportPassive {
455 InputReport report_mode;
456 u16 button_input;
457 u8 stick_state;
458 std::array<u8, 10> unknown_data;
459};
460static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
461
462struct InputReportActive {
463 InputReport report_mode;
464 u8 packet_id;
465 Battery battery_status;
466 std::array<u8, 3> button_input;
467 std::array<u8, 3> left_stick_state;
468 std::array<u8, 3> right_stick_state;
469 u8 vibration_code;
470 std::array<s16, 6 * 2> motion_input;
471 INSERT_PADDING_BYTES(0x2);
472 s16 ring_input;
473};
474static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
475
476struct InputReportNfcIr {
477 InputReport report_mode;
478 u8 packet_id;
479 Battery battery_status;
480 std::array<u8, 3> button_input;
481 std::array<u8, 3> left_stick_state;
482 std::array<u8, 3> right_stick_state;
483 u8 vibration_code;
484 std::array<s16, 6 * 2> motion_input;
485 INSERT_PADDING_BYTES(0x4);
486};
487static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
488#pragma pack(pop)
489
490struct IMUCalibration {
491 std::array<s16, 3> accelerometer_offset;
492 std::array<s16, 3> accelerometer_scale;
493 std::array<s16, 3> gyroscope_offset;
494 std::array<s16, 3> gyroscope_scale;
495};
496static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
497
498struct NFCReadBlock {
499 u8 start;
500 u8 end;
501};
502static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
503
504struct NFCReadBlockCommand {
505 u8 block_count{};
506 std::array<NFCReadBlock, 4> blocks{};
507};
508static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
509
510struct NFCReadCommandData {
511 u8 unknown;
512 u8 uuid_length;
513 u8 unknown_2;
514 std::array<u8, 6> uid;
515 NFCTagType tag_type;
516 NFCReadBlockCommand read_block;
517};
518static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
519
520struct NFCPollingCommandData {
521 u8 enable_mifare;
522 u8 unknown_1;
523 u8 unknown_2;
524 u8 unknown_3;
525 u8 unknown_4;
526};
527static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
528
529struct NFCRequestState {
530 MCUSubCommand sub_command;
531 NFCReadCommand command_argument;
532 u8 packet_id;
533 INSERT_PADDING_BYTES(0x1);
534 MCUPacketFlag packet_flag;
535 u8 data_length;
536 union {
537 std::array<u8, 0x1F> raw_data;
538 NFCReadCommandData nfc_read;
539 NFCPollingCommandData nfc_polling;
540 };
541 u8 crc;
542};
543static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
544
545struct IrsConfigure {
546 MCUCommand command;
547 MCUSubCommand sub_command;
548 IrsMode irs_mode;
549 IrsFragments number_of_fragments;
550 u16 mcu_major_version;
551 u16 mcu_minor_version;
552 INSERT_PADDING_BYTES(0x1D);
553 u8 crc;
554};
555static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
556
557#pragma pack(push, 1)
558struct IrsRegister {
559 IrRegistersAddress address;
560 u8 value;
561};
562static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
563
564struct IrsWriteRegisters {
565 MCUCommand command;
566 MCUSubCommand sub_command;
567 u8 number_of_registers;
568 std::array<IrsRegister, 9> registers;
569 INSERT_PADDING_BYTES(0x7);
570 u8 crc;
571};
572static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
573#pragma pack(pop)
574
575struct FirmwareVersion {
576 u8 major;
577 u8 minor;
578};
579static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
580
581struct DeviceInfo {
582 FirmwareVersion firmware;
583 MacAddress mac_address;
584};
585static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
586
587struct MotionStatus {
588 bool is_enabled;
589 u64 delta_time;
590 GyroSensitivity gyro_sensitivity;
591 AccelerometerSensitivity accelerometer_sensitivity;
592};
593
594struct RingStatus {
595 bool is_enabled;
596 s16 default_value;
597 s16 max_value;
598 s16 min_value;
599};
600
601struct JoyconCallbacks {
602 std::function<void(Battery)> on_battery_data;
603 std::function<void(Color)> on_color_data;
604 std::function<void(int, bool)> on_button_data;
605 std::function<void(int, f32)> on_stick_data;
606 std::function<void(int, const MotionData&)> on_motion_data;
607 std::function<void(f32)> on_ring_data;
608 std::function<void(const std::vector<u8>&)> on_amiibo_data;
609 std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
610};
611
612} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
new file mode 100644
index 000000000..5c0f71722
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -0,0 +1,400 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <thread>
5#include "common/logging/log.h"
6#include "input_common/helpers/joycon_protocol/nfc.h"
7
8namespace InputCommon::Joycon {
9
10NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
11 : JoyconCommonProtocol(std::move(handle)) {}
12
13DriverResult NfcProtocol::EnableNfc() {
14 LOG_INFO(Input, "Enable NFC");
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17
18 if (result == DriverResult::Success) {
19 result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
20 }
21 if (result == DriverResult::Success) {
22 result = EnableMCU(true);
23 }
24 if (result == DriverResult::Success) {
25 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
26 }
27 if (result == DriverResult::Success) {
28 const MCUConfig config{
29 .command = MCUCommand::ConfigureMCU,
30 .sub_command = MCUSubCommand::SetMCUMode,
31 .mode = MCUMode::NFC,
32 .crc = {},
33 };
34
35 result = ConfigureMCU(config);
36 }
37
38 return result;
39}
40
41DriverResult NfcProtocol::DisableNfc() {
42 LOG_DEBUG(Input, "Disable NFC");
43 ScopedSetBlocking sb(this);
44 DriverResult result{DriverResult::Success};
45
46 if (result == DriverResult::Success) {
47 result = EnableMCU(false);
48 }
49
50 is_enabled = false;
51
52 return result;
53}
54
55DriverResult NfcProtocol::StartNFCPollingMode() {
56 LOG_DEBUG(Input, "Start NFC pooling Mode");
57 ScopedSetBlocking sb(this);
58 DriverResult result{DriverResult::Success};
59 TagFoundData tag_data{};
60
61 if (result == DriverResult::Success) {
62 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
63 }
64 if (result == DriverResult::Success) {
65 result = WaitUntilNfcIsReady();
66 }
67 if (result == DriverResult::Success) {
68 is_enabled = true;
69 }
70
71 return result;
72}
73
74DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
75 LOG_DEBUG(Input, "Start NFC pooling Mode");
76 ScopedSetBlocking sb(this);
77 DriverResult result{DriverResult::Success};
78 TagFoundData tag_data{};
79
80 if (result == DriverResult::Success) {
81 result = StartPolling(tag_data);
82 }
83 if (result == DriverResult::Success) {
84 result = ReadTag(tag_data);
85 }
86 if (result == DriverResult::Success) {
87 result = WaitUntilNfcIsReady();
88 }
89 if (result == DriverResult::Success) {
90 result = StartPolling(tag_data);
91 }
92 if (result == DriverResult::Success) {
93 result = GetAmiiboData(data);
94 }
95
96 return result;
97}
98
99bool NfcProtocol::HasAmiibo() {
100 ScopedSetBlocking sb(this);
101 DriverResult result{DriverResult::Success};
102 TagFoundData tag_data{};
103
104 if (result == DriverResult::Success) {
105 result = StartPolling(tag_data);
106 }
107
108 return result == DriverResult::Success;
109}
110
111DriverResult NfcProtocol::WaitUntilNfcIsReady() {
112 constexpr std::size_t timeout_limit = 10;
113 std::vector<u8> output;
114 std::size_t tries = 0;
115
116 do {
117 auto result = SendStartWaitingRecieveRequest(output);
118
119 if (result != DriverResult::Success) {
120 return result;
121 }
122 if (tries++ > timeout_limit) {
123 return DriverResult::Timeout;
124 }
125 } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 ||
126 output[56] != 0x00);
127
128 return DriverResult::Success;
129}
130
131DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
132 LOG_DEBUG(Input, "Start Polling for tag");
133 constexpr std::size_t timeout_limit = 7;
134 std::vector<u8> output;
135 std::size_t tries = 0;
136
137 do {
138 const auto result = SendStartPollingRequest(output);
139 if (result != DriverResult::Success) {
140 return result;
141 }
142 if (tries++ > timeout_limit) {
143 return DriverResult::Timeout;
144 }
145 } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09);
146
147 data.type = output[62];
148 data.uuid.resize(output[64]);
149 memcpy(data.uuid.data(), output.data() + 65, data.uuid.size());
150
151 return DriverResult::Success;
152}
153
154DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
155 constexpr std::size_t timeout_limit = 10;
156 std::vector<u8> output;
157 std::size_t tries = 0;
158
159 std::string uuid_string;
160 for (auto& content : data.uuid) {
161 uuid_string += fmt::format(" {:02x}", content);
162 }
163
164 LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
165
166 tries = 0;
167 NFCPages ntag_pages = NFCPages::Block0;
168 // Read Tag data
169 while (true) {
170 auto result = SendReadAmiiboRequest(output, ntag_pages);
171 const auto mcu_report = static_cast<MCUReport>(output[49]);
172 const auto nfc_status = static_cast<NFCStatus>(output[56]);
173
174 if (result != DriverResult::Success) {
175 return result;
176 }
177
178 if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
179 nfc_status == NFCStatus::TagLost) {
180 return DriverResult::ErrorReadingData;
181 }
182
183 if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) {
184 if (data.type != 2) {
185 continue;
186 }
187 switch (output[74]) {
188 case 0:
189 ntag_pages = NFCPages::Block135;
190 break;
191 case 3:
192 ntag_pages = NFCPages::Block45;
193 break;
194 case 4:
195 ntag_pages = NFCPages::Block231;
196 break;
197 default:
198 return DriverResult::ErrorReadingData;
199 }
200 continue;
201 }
202
203 if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
204 // finished
205 SendStopPollingRequest(output);
206 return DriverResult::Success;
207 }
208
209 // Ignore other state reports
210 if (mcu_report == MCUReport::NFCState) {
211 continue;
212 }
213
214 if (tries++ > timeout_limit) {
215 return DriverResult::Timeout;
216 }
217 }
218
219 return DriverResult::Success;
220}
221
222DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
223 constexpr std::size_t timeout_limit = 10;
224 std::vector<u8> output;
225 std::size_t tries = 0;
226
227 NFCPages ntag_pages = NFCPages::Block135;
228 std::size_t ntag_buffer_pos = 0;
229 // Read Tag data
230 while (true) {
231 auto result = SendReadAmiiboRequest(output, ntag_pages);
232 const auto mcu_report = static_cast<MCUReport>(output[49]);
233 const auto nfc_status = static_cast<NFCStatus>(output[56]);
234
235 if (result != DriverResult::Success) {
236 return result;
237 }
238
239 if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
240 nfc_status == NFCStatus::TagLost) {
241 return DriverResult::ErrorReadingData;
242 }
243
244 if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) {
245 std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
246 if (output[52] == 0x01) {
247 memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60);
248 ntag_buffer_pos += payload_size - 60;
249 } else {
250 memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
251 }
252 continue;
253 }
254
255 if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
256 LOG_INFO(Input, "Finished reading amiibo");
257 return DriverResult::Success;
258 }
259
260 // Ignore other state reports
261 if (mcu_report == MCUReport::NFCState) {
262 continue;
263 }
264
265 if (tries++ > timeout_limit) {
266 return DriverResult::Timeout;
267 }
268 }
269
270 return DriverResult::Success;
271}
272
273DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
274 NFCRequestState request{
275 .sub_command = MCUSubCommand::ReadDeviceMode,
276 .command_argument = NFCReadCommand::StartPolling,
277 .packet_id = 0x0,
278 .packet_flag = MCUPacketFlag::LastCommandPacket,
279 .data_length = sizeof(NFCPollingCommandData),
280 .nfc_polling =
281 {
282 .enable_mifare = 0x01,
283 .unknown_1 = 0x00,
284 .unknown_2 = 0x00,
285 .unknown_3 = 0x2c,
286 .unknown_4 = 0x01,
287 },
288 .crc = {},
289 };
290
291 std::array<u8, sizeof(NFCRequestState)> request_data{};
292 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
293 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
294 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
295}
296
297DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
298 NFCRequestState request{
299 .sub_command = MCUSubCommand::ReadDeviceMode,
300 .command_argument = NFCReadCommand::StopPolling,
301 .packet_id = 0x0,
302 .packet_flag = MCUPacketFlag::LastCommandPacket,
303 .data_length = 0,
304 .raw_data = {},
305 .crc = {},
306 };
307
308 std::array<u8, sizeof(NFCRequestState)> request_data{};
309 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
310 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
311 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
312}
313
314DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) {
315 NFCRequestState request{
316 .sub_command = MCUSubCommand::ReadDeviceMode,
317 .command_argument = NFCReadCommand::StartWaitingRecieve,
318 .packet_id = 0x0,
319 .packet_flag = MCUPacketFlag::LastCommandPacket,
320 .data_length = 0,
321 .raw_data = {},
322 .crc = {},
323 };
324
325 std::vector<u8> request_data(sizeof(NFCRequestState));
326 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
327 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
328 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
329}
330
331DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) {
332 NFCRequestState request{
333 .sub_command = MCUSubCommand::ReadDeviceMode,
334 .command_argument = NFCReadCommand::Ntag,
335 .packet_id = 0x0,
336 .packet_flag = MCUPacketFlag::LastCommandPacket,
337 .data_length = sizeof(NFCReadCommandData),
338 .nfc_read =
339 {
340 .unknown = 0xd0,
341 .uuid_length = 0x07,
342 .unknown_2 = 0x00,
343 .uid = {},
344 .tag_type = NFCTagType::AllTags,
345 .read_block = GetReadBlockCommand(ntag_pages),
346 },
347 .crc = {},
348 };
349
350 std::array<u8, sizeof(NFCRequestState)> request_data{};
351 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
352 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
353 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
354}
355
356NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
357 switch (pages) {
358 case NFCPages::Block0:
359 return {
360 .block_count = 1,
361 };
362 case NFCPages::Block45:
363 return {
364 .block_count = 1,
365 .blocks =
366 {
367 NFCReadBlock{0x00, 0x2C},
368 },
369 };
370 case NFCPages::Block135:
371 return {
372 .block_count = 3,
373 .blocks =
374 {
375 NFCReadBlock{0x00, 0x3b},
376 {0x3c, 0x77},
377 {0x78, 0x86},
378 },
379 };
380 case NFCPages::Block231:
381 return {
382 .block_count = 4,
383 .blocks =
384 {
385 NFCReadBlock{0x00, 0x3b},
386 {0x3c, 0x77},
387 {0x78, 0x83},
388 {0xb4, 0xe6},
389 },
390 };
391 default:
392 return {};
393 };
394}
395
396bool NfcProtocol::IsEnabled() const {
397 return is_enabled;
398}
399
400} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
new file mode 100644
index 000000000..e63665aa9
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class NfcProtocol final : private JoyconCommonProtocol {
19public:
20 explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableNfc();
23
24 DriverResult DisableNfc();
25
26 DriverResult StartNFCPollingMode();
27
28 DriverResult ScanAmiibo(std::vector<u8>& data);
29
30 bool HasAmiibo();
31
32 bool IsEnabled() const;
33
34private:
35 struct TagFoundData {
36 u8 type;
37 std::vector<u8> uuid;
38 };
39
40 DriverResult WaitUntilNfcIsReady();
41
42 DriverResult StartPolling(TagFoundData& data);
43
44 DriverResult ReadTag(const TagFoundData& data);
45
46 DriverResult GetAmiiboData(std::vector<u8>& data);
47
48 DriverResult SendStartPollingRequest(std::vector<u8>& output);
49
50 DriverResult SendStopPollingRequest(std::vector<u8>& output);
51
52 DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
53
54 DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages);
55
56 NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
57
58 bool is_enabled{};
59};
60
61} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
new file mode 100644
index 000000000..7f8e093fa
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -0,0 +1,341 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/poller.h"
6
7namespace InputCommon::Joycon {
8
9JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
10 JoyStickCalibration right_stick_calibration_,
11 MotionCalibration motion_calibration_)
12 : device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
13 right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
14
15void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
16 callbacks = std::move(callbacks_);
17}
18
19void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
20 const RingStatus& ring_status) {
21 InputReportActive data{};
22 memcpy(&data, buffer.data(), sizeof(InputReportActive));
23
24 switch (device_type) {
25 case Joycon::ControllerType::Left:
26 UpdateActiveLeftPadInput(data, motion_status);
27 break;
28 case Joycon::ControllerType::Right:
29 UpdateActiveRightPadInput(data, motion_status);
30 break;
31 case Joycon::ControllerType::Pro:
32 UpdateActiveProPadInput(data, motion_status);
33 break;
34 case Joycon::ControllerType::Grip:
35 case Joycon::ControllerType::Dual:
36 case Joycon::ControllerType::None:
37 break;
38 }
39
40 if (ring_status.is_enabled) {
41 UpdateRing(data.ring_input, ring_status);
42 }
43
44 callbacks.on_battery_data(data.battery_status);
45}
46
47void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
48 InputReportPassive data{};
49 memcpy(&data, buffer.data(), sizeof(InputReportPassive));
50
51 switch (device_type) {
52 case Joycon::ControllerType::Left:
53 UpdatePasiveLeftPadInput(data);
54 break;
55 case Joycon::ControllerType::Right:
56 UpdatePasiveRightPadInput(data);
57 break;
58 case Joycon::ControllerType::Pro:
59 UpdatePasiveProPadInput(data);
60 break;
61 case Joycon::ControllerType::Grip:
62 case Joycon::ControllerType::Dual:
63 case Joycon::ControllerType::None:
64 break;
65 }
66}
67
68void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
69 // This mode is compatible with the active mode
70 ReadActiveMode(buffer, motion_status, {});
71}
72
73void JoyconPoller::UpdateColor(const Color& color) {
74 callbacks.on_color_data(color);
75}
76
77void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) {
78 callbacks.on_amiibo_data(amiibo_data);
79}
80
81void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
82 callbacks.on_camera_data(camera_data, format);
83}
84
85void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
86 float normalized_value = static_cast<float>(value - ring_status.default_value);
87 if (normalized_value > 0) {
88 normalized_value = normalized_value /
89 static_cast<float>(ring_status.max_value - ring_status.default_value);
90 }
91 if (normalized_value < 0) {
92 normalized_value = normalized_value /
93 static_cast<float>(ring_status.default_value - ring_status.min_value);
94 }
95 callbacks.on_ring_data(normalized_value);
96}
97
98void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
99 const MotionStatus& motion_status) {
100 static constexpr std::array<Joycon::PadButton, 11> left_buttons{
101 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
102 Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
103 Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
104 Joycon::PadButton::Capture, Joycon::PadButton::StickL,
105 };
106
107 const u32 raw_button =
108 static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
109 for (std::size_t i = 0; i < left_buttons.size(); ++i) {
110 const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
111 const int button = static_cast<int>(left_buttons[i]);
112 callbacks.on_button_data(button, button_status);
113 }
114
115 const u16 raw_left_axis_x =
116 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
117 const u16 raw_left_axis_y =
118 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
119 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
120 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
121 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
122 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
123
124 if (motion_status.is_enabled) {
125 auto left_motion = GetMotionInput(input, motion_status);
126 // Rotate motion axis to the correct direction
127 left_motion.accel_y = -left_motion.accel_y;
128 left_motion.accel_z = -left_motion.accel_z;
129 left_motion.gyro_x = -left_motion.gyro_x;
130 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
131 }
132}
133
134void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
135 const MotionStatus& motion_status) {
136 static constexpr std::array<Joycon::PadButton, 11> right_buttons{
137 Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
138 Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
139 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
140 Joycon::PadButton::Home, Joycon::PadButton::StickR,
141 };
142
143 const u32 raw_button =
144 static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
145 for (std::size_t i = 0; i < right_buttons.size(); ++i) {
146 const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
147 const int button = static_cast<int>(right_buttons[i]);
148 callbacks.on_button_data(button, button_status);
149 }
150
151 const u16 raw_right_axis_x =
152 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
153 const u16 raw_right_axis_y =
154 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
155 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
156 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
157 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
158 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
159
160 if (motion_status.is_enabled) {
161 auto right_motion = GetMotionInput(input, motion_status);
162 // Rotate motion axis to the correct direction
163 right_motion.accel_x = -right_motion.accel_x;
164 right_motion.accel_y = -right_motion.accel_y;
165 right_motion.gyro_z = -right_motion.gyro_z;
166 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
167 }
168}
169
170void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
171 const MotionStatus& motion_status) {
172 static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
173 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
174 Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
175 Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
176 Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
177 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
178 Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
179 };
180
181 const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
182 (input.button_input[1] << 16));
183 for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
184 const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
185 const int button = static_cast<int>(pro_buttons[i]);
186 callbacks.on_button_data(button, button_status);
187 }
188
189 const u16 raw_left_axis_x =
190 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
191 const u16 raw_left_axis_y =
192 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
193 const u16 raw_right_axis_x =
194 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
195 const u16 raw_right_axis_y =
196 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
197
198 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
199 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
200 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
201 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
202 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
203 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
204 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
205 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
206
207 if (motion_status.is_enabled) {
208 auto pro_motion = GetMotionInput(input, motion_status);
209 pro_motion.gyro_x = -pro_motion.gyro_x;
210 pro_motion.accel_y = -pro_motion.accel_y;
211 pro_motion.accel_z = -pro_motion.accel_z;
212 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
213 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
214 }
215}
216
217void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
218 static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
219 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
220 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
221 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
222 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
223 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
224 Joycon::PasivePadButton::StickL,
225 };
226
227 for (auto left_button : left_buttons) {
228 const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
229 const int button = static_cast<int>(left_button);
230 callbacks.on_button_data(button, button_status);
231 }
232}
233
234void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
235 static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
236 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
237 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
238 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
239 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
240 Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
241 Joycon::PasivePadButton::StickR,
242 };
243
244 for (auto right_button : right_buttons) {
245 const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
246 const int button = static_cast<int>(right_button);
247 callbacks.on_button_data(button, button_status);
248 }
249}
250
251void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
252 static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
253 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
254 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
255 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
256 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
257 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
258 Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
259 Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
260 };
261
262 for (auto pro_button : pro_buttons) {
263 const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
264 const int button = static_cast<int>(pro_button);
265 callbacks.on_button_data(button, button_status);
266 }
267}
268
269f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
270 const f32 value = static_cast<f32>(raw_value - calibration.center);
271 if (value > 0.0f) {
272 return value / calibration.max;
273 }
274 return value / calibration.min;
275}
276
277f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
278 AccelerometerSensitivity sensitivity) const {
279 const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
280 switch (sensitivity) {
281 case Joycon::AccelerometerSensitivity::G2:
282 return value / 4.0f;
283 case Joycon::AccelerometerSensitivity::G4:
284 return value / 2.0f;
285 case Joycon::AccelerometerSensitivity::G8:
286 return value;
287 case Joycon::AccelerometerSensitivity::G16:
288 return value * 2.0f;
289 }
290 return value;
291}
292
293f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
294 GyroSensitivity sensitivity) const {
295 const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
296 switch (sensitivity) {
297 case Joycon::GyroSensitivity::DPS250:
298 return value / 8.0f;
299 case Joycon::GyroSensitivity::DPS500:
300 return value / 4.0f;
301 case Joycon::GyroSensitivity::DPS1000:
302 return value / 2.0f;
303 case Joycon::GyroSensitivity::DPS2000:
304 return value;
305 }
306 return value;
307}
308
309s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
310 const InputReportActive& input) const {
311 return input.motion_input[(sensor * 3) + axis];
312}
313
314MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
315 const MotionStatus& motion_status) const {
316 MotionData motion{};
317 const auto& accel_cal = motion_calibration.accelerometer;
318 const auto& gyro_cal = motion_calibration.gyro;
319 const s16 raw_accel_x = input.motion_input[1];
320 const s16 raw_accel_y = input.motion_input[0];
321 const s16 raw_accel_z = input.motion_input[2];
322 const s16 raw_gyro_x = input.motion_input[4];
323 const s16 raw_gyro_y = input.motion_input[3];
324 const s16 raw_gyro_z = input.motion_input[5];
325
326 motion.delta_timestamp = motion_status.delta_time;
327 motion.accel_x =
328 GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
329 motion.accel_y =
330 GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
331 motion.accel_z =
332 GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
333 motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
334 motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
335 motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
336
337 // TODO(German77): Return all three samples data
338 return motion;
339}
340
341} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
new file mode 100644
index 000000000..354d41dad
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -0,0 +1,81 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <functional>
12#include <span>
13
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18// Handles input packages and triggers the corresponding input events
19class JoyconPoller {
20public:
21 JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
22 JoyStickCalibration right_stick_calibration_,
23 MotionCalibration motion_calibration_);
24
25 void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
26
27 /// Handles data from passive packages
28 void ReadPassiveMode(std::span<u8> buffer);
29
30 /// Handles data from active packages
31 void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
32 const RingStatus& ring_status);
33
34 /// Handles data from nfc or ir packages
35 void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
36
37 void UpdateColor(const Color& color);
38 void UpdateRing(s16 value, const RingStatus& ring_status);
39 void UpdateAmiibo(const std::vector<u8>& amiibo_data);
40 void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
41
42private:
43 void UpdateActiveLeftPadInput(const InputReportActive& input,
44 const MotionStatus& motion_status);
45 void UpdateActiveRightPadInput(const InputReportActive& input,
46 const MotionStatus& motion_status);
47 void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
48
49 void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
50 void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
51 void UpdatePasiveProPadInput(const InputReportPassive& buffer);
52
53 /// Returns a calibrated joystick axis from raw axis data
54 f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
55
56 /// Returns a calibrated accelerometer axis from raw motion data
57 f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
58 AccelerometerSensitivity sensitivity) const;
59
60 /// Returns a calibrated gyro axis from raw motion data
61 f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
62 GyroSensitivity sensitivity) const;
63
64 /// Returns a raw motion value from a buffer
65 s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
66
67 /// Returns motion data from a buffer
68 MotionData GetMotionInput(const InputReportActive& input,
69 const MotionStatus& motion_status) const;
70
71 ControllerType device_type{};
72
73 // Device calibration
74 JoyStickCalibration left_stick_calibration{};
75 JoyStickCalibration right_stick_calibration{};
76 MotionCalibration motion_calibration{};
77
78 Joycon::JoyconCallbacks callbacks{};
79};
80
81} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
new file mode 100644
index 000000000..12f81309e
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -0,0 +1,117 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/ringcon.h"
6
7namespace InputCommon::Joycon {
8
9RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(std::move(handle)) {}
11
12DriverResult RingConProtocol::EnableRingCon() {
13 LOG_DEBUG(Input, "Enable Ringcon");
14 ScopedSetBlocking sb(this);
15 DriverResult result{DriverResult::Success};
16
17 if (result == DriverResult::Success) {
18 result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
19 }
20 if (result == DriverResult::Success) {
21 result = EnableMCU(true);
22 }
23 if (result == DriverResult::Success) {
24 const MCUConfig config{
25 .command = MCUCommand::ConfigureMCU,
26 .sub_command = MCUSubCommand::SetDeviceMode,
27 .mode = MCUMode::Standby,
28 .crc = {},
29 };
30 result = ConfigureMCU(config);
31 }
32
33 return result;
34}
35
36DriverResult RingConProtocol::DisableRingCon() {
37 LOG_DEBUG(Input, "Disable RingCon");
38 ScopedSetBlocking sb(this);
39 DriverResult result{DriverResult::Success};
40
41 if (result == DriverResult::Success) {
42 result = EnableMCU(false);
43 }
44
45 is_enabled = false;
46
47 return result;
48}
49
50DriverResult RingConProtocol::StartRingconPolling() {
51 LOG_DEBUG(Input, "Enable Ringcon");
52 ScopedSetBlocking sb(this);
53 DriverResult result{DriverResult::Success};
54 bool is_connected = false;
55
56 if (result == DriverResult::Success) {
57 result = IsRingConnected(is_connected);
58 }
59 if (result == DriverResult::Success && is_connected) {
60 LOG_INFO(Input, "Ringcon detected");
61 result = ConfigureRing();
62 }
63 if (result == DriverResult::Success) {
64 is_enabled = true;
65 }
66
67 return result;
68}
69
70DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
71 LOG_DEBUG(Input, "IsRingConnected");
72 constexpr std::size_t max_tries = 28;
73 constexpr u8 ring_controller_id = 0x20;
74 std::vector<u8> output;
75 std::size_t tries = 0;
76 is_connected = false;
77
78 do {
79 std::array<u8, 1> empty_data{};
80 const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
81
82 if (result != DriverResult::Success) {
83 return result;
84 }
85
86 if (tries++ >= max_tries) {
87 return DriverResult::NoDeviceDetected;
88 }
89 } while (output[16] != ring_controller_id);
90
91 is_connected = true;
92 return DriverResult::Success;
93}
94
95DriverResult RingConProtocol::ConfigureRing() {
96 LOG_DEBUG(Input, "ConfigureRing");
97
98 static constexpr std::array<u8, 37> ring_config{
99 0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
100 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
101 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
102
103 const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config);
104
105 if (result != DriverResult::Success) {
106 return result;
107 }
108
109 static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
110 return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data);
111}
112
113bool RingConProtocol::IsEnabled() const {
114 return is_enabled;
115}
116
117} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h
new file mode 100644
index 000000000..6e858f3fc
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RingConProtocol final : private JoyconCommonProtocol {
19public:
20 explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRingCon();
23
24 DriverResult DisableRingCon();
25
26 DriverResult StartRingconPolling();
27
28 bool IsEnabled() const;
29
30private:
31 DriverResult IsRingConnected(bool& is_connected);
32
33 DriverResult ConfigureRing();
34
35 bool is_enabled{};
36};
37
38} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
new file mode 100644
index 000000000..63b60c946
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <cmath>
6
7#include "common/logging/log.h"
8#include "input_common/helpers/joycon_protocol/rumble.h"
9
10namespace InputCommon::Joycon {
11
12RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
13 : JoyconCommonProtocol(std::move(handle)) {}
14
15DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
16 LOG_DEBUG(Input, "Enable Rumble");
17 ScopedSetBlocking sb(this);
18 const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
19 return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
20}
21
22DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
23 std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};
24
25 if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
26 return SendVibrationReport(DefaultVibrationBuffer);
27 }
28
29 // Protect joycons from damage from strong vibrations
30 const f32 clamp_amplitude =
31 1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
32
33 const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
34 const u8 encoded_high_amplitude =
35 EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
36 const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
37 const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
38
39 buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
40 buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
41 buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
42 buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
43
44 // Duplicate rumble for now
45 buffer[4] = buffer[0];
46 buffer[5] = buffer[1];
47 buffer[6] = buffer[2];
48 buffer[7] = buffer[3];
49
50 return SendVibrationReport(buffer);
51}
52
53u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
54 const u8 new_frequency =
55 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
56 return static_cast<u16>((new_frequency - 0x60) * 4);
57}
58
59u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
60 const u8 new_frequency =
61 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
62 return static_cast<u8>(new_frequency - 0x40);
63}
64
65u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
66 // More information about these values can be found here:
67 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
68
69 static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
70 std::pair<f32, int>{0.0f, 0x0},
71 {0.01f, 0x2},
72 {0.012f, 0x4},
73 {0.014f, 0x6},
74 {0.017f, 0x8},
75 {0.02f, 0x0a},
76 {0.024f, 0x0c},
77 {0.028f, 0x0e},
78 {0.033f, 0x10},
79 {0.04f, 0x12},
80 {0.047f, 0x14},
81 {0.056f, 0x16},
82 {0.067f, 0x18},
83 {0.08f, 0x1a},
84 {0.095f, 0x1c},
85 {0.112f, 0x1e},
86 {0.117f, 0x20},
87 {0.123f, 0x22},
88 {0.128f, 0x24},
89 {0.134f, 0x26},
90 {0.14f, 0x28},
91 {0.146f, 0x2a},
92 {0.152f, 0x2c},
93 {0.159f, 0x2e},
94 {0.166f, 0x30},
95 {0.173f, 0x32},
96 {0.181f, 0x34},
97 {0.189f, 0x36},
98 {0.198f, 0x38},
99 {0.206f, 0x3a},
100 {0.215f, 0x3c},
101 {0.225f, 0x3e},
102 {0.23f, 0x40},
103 {0.235f, 0x42},
104 {0.24f, 0x44},
105 {0.245f, 0x46},
106 {0.251f, 0x48},
107 {0.256f, 0x4a},
108 {0.262f, 0x4c},
109 {0.268f, 0x4e},
110 {0.273f, 0x50},
111 {0.279f, 0x52},
112 {0.286f, 0x54},
113 {0.292f, 0x56},
114 {0.298f, 0x58},
115 {0.305f, 0x5a},
116 {0.311f, 0x5c},
117 {0.318f, 0x5e},
118 {0.325f, 0x60},
119 {0.332f, 0x62},
120 {0.34f, 0x64},
121 {0.347f, 0x66},
122 {0.355f, 0x68},
123 {0.362f, 0x6a},
124 {0.37f, 0x6c},
125 {0.378f, 0x6e},
126 {0.387f, 0x70},
127 {0.395f, 0x72},
128 {0.404f, 0x74},
129 {0.413f, 0x76},
130 {0.422f, 0x78},
131 {0.431f, 0x7a},
132 {0.44f, 0x7c},
133 {0.45f, 0x7e},
134 {0.46f, 0x80},
135 {0.47f, 0x82},
136 {0.48f, 0x84},
137 {0.491f, 0x86},
138 {0.501f, 0x88},
139 {0.512f, 0x8a},
140 {0.524f, 0x8c},
141 {0.535f, 0x8e},
142 {0.547f, 0x90},
143 {0.559f, 0x92},
144 {0.571f, 0x94},
145 {0.584f, 0x96},
146 {0.596f, 0x98},
147 {0.609f, 0x9a},
148 {0.623f, 0x9c},
149 {0.636f, 0x9e},
150 {0.65f, 0xa0},
151 {0.665f, 0xa2},
152 {0.679f, 0xa4},
153 {0.694f, 0xa6},
154 {0.709f, 0xa8},
155 {0.725f, 0xaa},
156 {0.741f, 0xac},
157 {0.757f, 0xae},
158 {0.773f, 0xb0},
159 {0.79f, 0xb2},
160 {0.808f, 0xb4},
161 {0.825f, 0xb6},
162 {0.843f, 0xb8},
163 {0.862f, 0xba},
164 {0.881f, 0xbc},
165 {0.9f, 0xbe},
166 {0.92f, 0xc0},
167 {0.94f, 0xc2},
168 {0.96f, 0xc4},
169 {0.981f, 0xc6},
170 {1.003f, 0xc8},
171 };
172
173 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
174 if (amplitude <= amplitude_value) {
175 return static_cast<u8>(code);
176 }
177 }
178
179 return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
180}
181
182u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
183 // More information about these values can be found here:
184 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
185
186 static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
187 std::pair<f32, int>{0.0f, 0x0040},
188 {0.01f, 0x8040},
189 {0.012f, 0x0041},
190 {0.014f, 0x8041},
191 {0.017f, 0x0042},
192 {0.02f, 0x8042},
193 {0.024f, 0x0043},
194 {0.028f, 0x8043},
195 {0.033f, 0x0044},
196 {0.04f, 0x8044},
197 {0.047f, 0x0045},
198 {0.056f, 0x8045},
199 {0.067f, 0x0046},
200 {0.08f, 0x8046},
201 {0.095f, 0x0047},
202 {0.112f, 0x8047},
203 {0.117f, 0x0048},
204 {0.123f, 0x8048},
205 {0.128f, 0x0049},
206 {0.134f, 0x8049},
207 {0.14f, 0x004a},
208 {0.146f, 0x804a},
209 {0.152f, 0x004b},
210 {0.159f, 0x804b},
211 {0.166f, 0x004c},
212 {0.173f, 0x804c},
213 {0.181f, 0x004d},
214 {0.189f, 0x804d},
215 {0.198f, 0x004e},
216 {0.206f, 0x804e},
217 {0.215f, 0x004f},
218 {0.225f, 0x804f},
219 {0.23f, 0x0050},
220 {0.235f, 0x8050},
221 {0.24f, 0x0051},
222 {0.245f, 0x8051},
223 {0.251f, 0x0052},
224 {0.256f, 0x8052},
225 {0.262f, 0x0053},
226 {0.268f, 0x8053},
227 {0.273f, 0x0054},
228 {0.279f, 0x8054},
229 {0.286f, 0x0055},
230 {0.292f, 0x8055},
231 {0.298f, 0x0056},
232 {0.305f, 0x8056},
233 {0.311f, 0x0057},
234 {0.318f, 0x8057},
235 {0.325f, 0x0058},
236 {0.332f, 0x8058},
237 {0.34f, 0x0059},
238 {0.347f, 0x8059},
239 {0.355f, 0x005a},
240 {0.362f, 0x805a},
241 {0.37f, 0x005b},
242 {0.378f, 0x805b},
243 {0.387f, 0x005c},
244 {0.395f, 0x805c},
245 {0.404f, 0x005d},
246 {0.413f, 0x805d},
247 {0.422f, 0x005e},
248 {0.431f, 0x805e},
249 {0.44f, 0x005f},
250 {0.45f, 0x805f},
251 {0.46f, 0x0060},
252 {0.47f, 0x8060},
253 {0.48f, 0x0061},
254 {0.491f, 0x8061},
255 {0.501f, 0x0062},
256 {0.512f, 0x8062},
257 {0.524f, 0x0063},
258 {0.535f, 0x8063},
259 {0.547f, 0x0064},
260 {0.559f, 0x8064},
261 {0.571f, 0x0065},
262 {0.584f, 0x8065},
263 {0.596f, 0x0066},
264 {0.609f, 0x8066},
265 {0.623f, 0x0067},
266 {0.636f, 0x8067},
267 {0.65f, 0x0068},
268 {0.665f, 0x8068},
269 {0.679f, 0x0069},
270 {0.694f, 0x8069},
271 {0.709f, 0x006a},
272 {0.725f, 0x806a},
273 {0.741f, 0x006b},
274 {0.757f, 0x806b},
275 {0.773f, 0x006c},
276 {0.79f, 0x806c},
277 {0.808f, 0x006d},
278 {0.825f, 0x806d},
279 {0.843f, 0x006e},
280 {0.862f, 0x806e},
281 {0.881f, 0x006f},
282 {0.9f, 0x806f},
283 {0.92f, 0x0070},
284 {0.94f, 0x8070},
285 {0.96f, 0x0071},
286 {0.981f, 0x8071},
287 {1.003f, 0x0072},
288 };
289
290 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
291 if (amplitude <= amplitude_value) {
292 return static_cast<u16>(code);
293 }
294 }
295
296 return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h
new file mode 100644
index 000000000..6c12b7925
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RumbleProtocol final : private JoyconCommonProtocol {
19public:
20 explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRumble(bool is_enabled);
23
24 DriverResult SendVibration(const VibrationValue& vibration);
25
26private:
27 u16 EncodeHighFrequency(f32 frequency) const;
28 u8 EncodeLowFrequency(f32 frequency) const;
29 u8 EncodeHighAmplitude(f32 amplitude) const;
30 u16 EncodeLowAmplitude(f32 amplitude) const;
31};
32
33} // namespace InputCommon::Joycon
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 61cfd0911..91aa96aa7 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -79,6 +79,17 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat
79 TriggerOnBatteryChange(identifier, value); 79 TriggerOnBatteryChange(identifier, value);
80} 80}
81 81
82void InputEngine::SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value) {
83 {
84 std::scoped_lock lock{mutex};
85 ControllerData& controller = controller_list.at(identifier);
86 if (!configuring) {
87 controller.color = value;
88 }
89 }
90 TriggerOnColorChange(identifier, value);
91}
92
82void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) { 93void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {
83 { 94 {
84 std::scoped_lock lock{mutex}; 95 std::scoped_lock lock{mutex};
@@ -176,6 +187,18 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif
176 return controller.battery; 187 return controller.battery;
177} 188}
178 189
190Common::Input::BodyColorStatus InputEngine::GetColor(const PadIdentifier& identifier) const {
191 std::scoped_lock lock{mutex};
192 const auto controller_iter = controller_list.find(identifier);
193 if (controller_iter == controller_list.cend()) {
194 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
195 identifier.pad, identifier.port);
196 return {};
197 }
198 const ControllerData& controller = controller_iter->second;
199 return controller.color;
200}
201
179BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const { 202BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
180 std::scoped_lock lock{mutex}; 203 std::scoped_lock lock{mutex};
181 const auto controller_iter = controller_list.find(identifier); 204 const auto controller_iter = controller_list.find(identifier);
@@ -328,6 +351,20 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
328 } 351 }
329} 352}
330 353
354void InputEngine::TriggerOnColorChange(const PadIdentifier& identifier,
355 [[maybe_unused]] Common::Input::BodyColorStatus value) {
356 std::scoped_lock lock{mutex_callback};
357 for (const auto& poller_pair : callback_list) {
358 const InputIdentifier& poller = poller_pair.second;
359 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Color, 0)) {
360 continue;
361 }
362 if (poller.callback.on_change) {
363 poller.callback.on_change();
364 }
365 }
366}
367
331void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion, 368void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
332 const BasicMotion& value) { 369 const BasicMotion& value) {
333 std::scoped_lock lock{mutex_callback}; 370 std::scoped_lock lock{mutex_callback};
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 6cbcf5207..50b5a3dc8 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -40,6 +40,7 @@ enum class EngineInputType {
40 Battery, 40 Battery,
41 Button, 41 Button,
42 Camera, 42 Camera,
43 Color,
43 HatButton, 44 HatButton,
44 Motion, 45 Motion,
45 Nfc, 46 Nfc,
@@ -104,14 +105,17 @@ public:
104 void EndConfiguration(); 105 void EndConfiguration();
105 106
106 // Sets a led pattern for a controller 107 // Sets a led pattern for a controller
107 virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier, 108 virtual Common::Input::DriverResult SetLeds(
108 [[maybe_unused]] const Common::Input::LedStatus& led_status) {} 109 [[maybe_unused]] const PadIdentifier& identifier,
110 [[maybe_unused]] const Common::Input::LedStatus& led_status) {
111 return Common::Input::DriverResult::NotSupported;
112 }
109 113
110 // Sets rumble to a controller 114 // Sets rumble to a controller
111 virtual Common::Input::VibrationError SetVibration( 115 virtual Common::Input::DriverResult SetVibration(
112 [[maybe_unused]] const PadIdentifier& identifier, 116 [[maybe_unused]] const PadIdentifier& identifier,
113 [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { 117 [[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
114 return Common::Input::VibrationError::NotSupported; 118 return Common::Input::DriverResult::NotSupported;
115 } 119 }
116 120
117 // Returns true if device supports vibrations 121 // Returns true if device supports vibrations
@@ -120,17 +124,17 @@ public:
120 } 124 }
121 125
122 // Sets polling mode to a controller 126 // Sets polling mode to a controller
123 virtual Common::Input::PollingError SetPollingMode( 127 virtual Common::Input::DriverResult SetPollingMode(
124 [[maybe_unused]] const PadIdentifier& identifier, 128 [[maybe_unused]] const PadIdentifier& identifier,
125 [[maybe_unused]] const Common::Input::PollingMode polling_mode) { 129 [[maybe_unused]] const Common::Input::PollingMode polling_mode) {
126 return Common::Input::PollingError::NotSupported; 130 return Common::Input::DriverResult::NotSupported;
127 } 131 }
128 132
129 // Sets camera format to a controller 133 // Sets camera format to a controller
130 virtual Common::Input::CameraError SetCameraFormat( 134 virtual Common::Input::DriverResult SetCameraFormat(
131 [[maybe_unused]] const PadIdentifier& identifier, 135 [[maybe_unused]] const PadIdentifier& identifier,
132 [[maybe_unused]] Common::Input::CameraFormat camera_format) { 136 [[maybe_unused]] Common::Input::CameraFormat camera_format) {
133 return Common::Input::CameraError::NotSupported; 137 return Common::Input::DriverResult::NotSupported;
134 } 138 }
135 139
136 // Returns success if nfc is supported 140 // Returns success if nfc is supported
@@ -199,6 +203,7 @@ public:
199 bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const; 203 bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
200 f32 GetAxis(const PadIdentifier& identifier, int axis) const; 204 f32 GetAxis(const PadIdentifier& identifier, int axis) const;
201 Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; 205 Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
206 Common::Input::BodyColorStatus GetColor(const PadIdentifier& identifier) const;
202 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; 207 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
203 Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; 208 Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
204 Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const; 209 Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
@@ -212,6 +217,7 @@ protected:
212 void SetHatButton(const PadIdentifier& identifier, int button, u8 value); 217 void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
213 void SetAxis(const PadIdentifier& identifier, int axis, f32 value); 218 void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
214 void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); 219 void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
220 void SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value);
215 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); 221 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
216 void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); 222 void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
217 void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value); 223 void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
@@ -227,6 +233,7 @@ private:
227 std::unordered_map<int, float> axes; 233 std::unordered_map<int, float> axes;
228 std::unordered_map<int, BasicMotion> motions; 234 std::unordered_map<int, BasicMotion> motions;
229 Common::Input::BatteryLevel battery{}; 235 Common::Input::BatteryLevel battery{};
236 Common::Input::BodyColorStatus color{};
230 Common::Input::CameraStatus camera{}; 237 Common::Input::CameraStatus camera{};
231 Common::Input::NfcStatus nfc{}; 238 Common::Input::NfcStatus nfc{};
232 }; 239 };
@@ -235,6 +242,8 @@ private:
235 void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value); 242 void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
236 void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value); 243 void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
237 void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); 244 void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
245 void TriggerOnColorChange(const PadIdentifier& identifier,
246 Common::Input::BodyColorStatus value);
238 void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, 247 void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
239 const BasicMotion& value); 248 const BasicMotion& value);
240 void TriggerOnCameraChange(const PadIdentifier& identifier, 249 void TriggerOnCameraChange(const PadIdentifier& identifier,
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index fb8be42e2..15cbf7e5f 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -498,6 +498,58 @@ private:
498 InputEngine* input_engine; 498 InputEngine* input_engine;
499}; 499};
500 500
501class InputFromColor final : public Common::Input::InputDevice {
502public:
503 explicit InputFromColor(PadIdentifier identifier_, InputEngine* input_engine_)
504 : identifier(identifier_), input_engine(input_engine_) {
505 UpdateCallback engine_callback{[this]() { OnChange(); }};
506 const InputIdentifier input_identifier{
507 .identifier = identifier,
508 .type = EngineInputType::Color,
509 .index = 0,
510 .callback = engine_callback,
511 };
512 last_color_value = {};
513 callback_key = input_engine->SetCallback(input_identifier);
514 }
515
516 ~InputFromColor() override {
517 input_engine->DeleteCallback(callback_key);
518 }
519
520 Common::Input::BodyColorStatus GetStatus() const {
521 return input_engine->GetColor(identifier);
522 }
523
524 void ForceUpdate() override {
525 const Common::Input::CallbackStatus status{
526 .type = Common::Input::InputType::Color,
527 .color_status = GetStatus(),
528 };
529
530 last_color_value = status.color_status;
531 TriggerOnChange(status);
532 }
533
534 void OnChange() {
535 const Common::Input::CallbackStatus status{
536 .type = Common::Input::InputType::Color,
537 .color_status = GetStatus(),
538 };
539
540 if (status.color_status.body != last_color_value.body) {
541 last_color_value = status.color_status;
542 TriggerOnChange(status);
543 }
544 }
545
546private:
547 const PadIdentifier identifier;
548 int callback_key;
549 Common::Input::BodyColorStatus last_color_value;
550 InputEngine* input_engine;
551};
552
501class InputFromMotion final : public Common::Input::InputDevice { 553class InputFromMotion final : public Common::Input::InputDevice {
502public: 554public:
503 explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_, 555 explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_,
@@ -754,11 +806,11 @@ public:
754 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) 806 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
755 : identifier(identifier_), input_engine(input_engine_) {} 807 : identifier(identifier_), input_engine(input_engine_) {}
756 808
757 void SetLED(const Common::Input::LedStatus& led_status) override { 809 Common::Input::DriverResult SetLED(const Common::Input::LedStatus& led_status) override {
758 input_engine->SetLeds(identifier, led_status); 810 return input_engine->SetLeds(identifier, led_status);
759 } 811 }
760 812
761 Common::Input::VibrationError SetVibration( 813 Common::Input::DriverResult SetVibration(
762 const Common::Input::VibrationStatus& vibration_status) override { 814 const Common::Input::VibrationStatus& vibration_status) override {
763 return input_engine->SetVibration(identifier, vibration_status); 815 return input_engine->SetVibration(identifier, vibration_status);
764 } 816 }
@@ -767,11 +819,12 @@ public:
767 return input_engine->IsVibrationEnabled(identifier); 819 return input_engine->IsVibrationEnabled(identifier);
768 } 820 }
769 821
770 Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override { 822 Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode) override {
771 return input_engine->SetPollingMode(identifier, polling_mode); 823 return input_engine->SetPollingMode(identifier, polling_mode);
772 } 824 }
773 825
774 Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override { 826 Common::Input::DriverResult SetCameraFormat(
827 Common::Input::CameraFormat camera_format) override {
775 return input_engine->SetCameraFormat(identifier, camera_format); 828 return input_engine->SetCameraFormat(identifier, camera_format);
776 } 829 }
777 830
@@ -966,6 +1019,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
966 return std::make_unique<InputFromBattery>(identifier, input_engine.get()); 1019 return std::make_unique<InputFromBattery>(identifier, input_engine.get());
967} 1020}
968 1021
1022std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateColorDevice(
1023 const Common::ParamPackage& params) {
1024 const PadIdentifier identifier = {
1025 .guid = Common::UUID{params.Get("guid", "")},
1026 .port = static_cast<std::size_t>(params.Get("port", 0)),
1027 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
1028 };
1029
1030 input_engine->PreSetController(identifier);
1031 return std::make_unique<InputFromColor>(identifier, input_engine.get());
1032}
1033
969std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice( 1034std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
970 Common::ParamPackage params) { 1035 Common::ParamPackage params) {
971 const PadIdentifier identifier = { 1036 const PadIdentifier identifier = {
@@ -1053,6 +1118,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
1053 if (params.Has("battery")) { 1118 if (params.Has("battery")) {
1054 return CreateBatteryDevice(params); 1119 return CreateBatteryDevice(params);
1055 } 1120 }
1121 if (params.Has("color")) {
1122 return CreateColorDevice(params);
1123 }
1056 if (params.Has("camera")) { 1124 if (params.Has("camera")) {
1057 return CreateCameraDevice(params); 1125 return CreateCameraDevice(params);
1058 } 1126 }
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
index d7db13ce4..e097e254c 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -191,6 +191,17 @@ private:
191 const Common::ParamPackage& params); 191 const Common::ParamPackage& params);
192 192
193 /** 193 /**
194 * Creates a color device from the parameters given.
195 * @param params contains parameters for creating the device:
196 * - "guid": text string for identifying controllers
197 * - "port": port of the connected device
198 * - "pad": slot of the connected controller
199 * @returns a unique input device with the parameters specified
200 */
201 std::unique_ptr<Common::Input::InputDevice> CreateColorDevice(
202 const Common::ParamPackage& params);
203
204 /**
194 * Creates a motion device from the parameters given. 205 * Creates a motion device from the parameters given.
195 * @param params contains parameters for creating the device: 206 * @param params contains parameters for creating the device:
196 * - "axis_x": the controller horizontal axis id to bind with the input 207 * - "axis_x": the controller horizontal axis id to bind with the input
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 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