Merge pull request #9492 from german77/joycon_release
Input_common: Implement custom joycon driver v2
This commit is contained in:
commit
a68af583ea
58 changed files with 5824 additions and 420 deletions
|
@ -51,6 +51,8 @@ enum class PollingMode {
|
|||
NFC,
|
||||
// Enable infrared camera polling
|
||||
IR,
|
||||
// Enable ring controller polling
|
||||
Ring,
|
||||
};
|
||||
|
||||
enum class CameraFormat {
|
||||
|
@ -62,21 +64,22 @@ enum class CameraFormat {
|
|||
None,
|
||||
};
|
||||
|
||||
// Vibration reply from the controller
|
||||
enum class VibrationError {
|
||||
None,
|
||||
// Different results that can happen from a device request
|
||||
enum class DriverResult {
|
||||
Success,
|
||||
WrongReply,
|
||||
Timeout,
|
||||
UnsupportedControllerType,
|
||||
HandleInUse,
|
||||
ErrorReadingData,
|
||||
ErrorWritingData,
|
||||
NoDeviceDetected,
|
||||
InvalidHandle,
|
||||
NotSupported,
|
||||
Disabled,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
// Polling mode reply from the controller
|
||||
enum class PollingError {
|
||||
None,
|
||||
NotSupported,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
// Nfc reply from the controller
|
||||
enum class NfcState {
|
||||
Success,
|
||||
|
@ -90,13 +93,6 @@ enum class NfcState {
|
|||
Unknown,
|
||||
};
|
||||
|
||||
// Ir camera reply from the controller
|
||||
enum class CameraError {
|
||||
None,
|
||||
NotSupported,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
// Hint for amplification curve to be used
|
||||
enum class VibrationAmplificationType {
|
||||
Linear,
|
||||
|
@ -190,6 +186,8 @@ struct TouchStatus {
|
|||
struct BodyColorStatus {
|
||||
u32 body{};
|
||||
u32 buttons{};
|
||||
u32 left_grip{};
|
||||
u32 right_grip{};
|
||||
};
|
||||
|
||||
// HD rumble data
|
||||
|
@ -228,17 +226,31 @@ enum class ButtonNames {
|
|||
Engine,
|
||||
// This will display the button by value instead of the button name
|
||||
Value,
|
||||
|
||||
// Joycon button names
|
||||
ButtonLeft,
|
||||
ButtonRight,
|
||||
ButtonDown,
|
||||
ButtonUp,
|
||||
TriggerZ,
|
||||
TriggerR,
|
||||
TriggerL,
|
||||
ButtonA,
|
||||
ButtonB,
|
||||
ButtonX,
|
||||
ButtonY,
|
||||
ButtonPlus,
|
||||
ButtonMinus,
|
||||
ButtonHome,
|
||||
ButtonCapture,
|
||||
ButtonStickL,
|
||||
ButtonStickR,
|
||||
TriggerL,
|
||||
TriggerZL,
|
||||
TriggerSL,
|
||||
TriggerR,
|
||||
TriggerZR,
|
||||
TriggerSR,
|
||||
|
||||
// GC button names
|
||||
TriggerZ,
|
||||
ButtonStart,
|
||||
|
||||
// DS4 button names
|
||||
|
@ -316,22 +328,24 @@ class OutputDevice {
|
|||
public:
|
||||
virtual ~OutputDevice() = default;
|
||||
|
||||
virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {}
|
||||
virtual DriverResult SetLED([[maybe_unused]] const LedStatus& led_status) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
|
||||
return VibrationError::NotSupported;
|
||||
virtual DriverResult SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
virtual bool IsVibrationEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
|
||||
return PollingError::NotSupported;
|
||||
virtual DriverResult SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
|
||||
return CameraError::NotSupported;
|
||||
virtual DriverResult SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState SupportsNfc() const {
|
||||
|
|
|
@ -483,6 +483,7 @@ struct Values {
|
|||
|
||||
Setting<bool> enable_raw_input{false, "enable_raw_input"};
|
||||
Setting<bool> controller_navigation{true, "controller_navigation"};
|
||||
Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"};
|
||||
|
||||
SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};
|
||||
SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <common/scope_exit.h>
|
||||
|
||||
#include "common/polyfill_ranges.h"
|
||||
#include "common/thread.h"
|
||||
|
@ -94,6 +95,7 @@ void EmulatedController::ReloadFromSettings() {
|
|||
motion_params[index] = Common::ParamPackage(player.motions[index]);
|
||||
}
|
||||
|
||||
controller.color_values = {};
|
||||
controller.colors_state.fullkey = {
|
||||
.body = GetNpadColor(player.body_color_left),
|
||||
.button = GetNpadColor(player.button_color_left),
|
||||
|
@ -107,6 +109,8 @@ void EmulatedController::ReloadFromSettings() {
|
|||
.button = GetNpadColor(player.button_color_right),
|
||||
};
|
||||
|
||||
ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs);
|
||||
|
||||
// Other or debug controller should always be a pro controller
|
||||
if (npad_id_type != NpadIdType::Other) {
|
||||
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
|
||||
|
@ -133,18 +137,28 @@ void EmulatedController::LoadDevices() {
|
|||
trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];
|
||||
trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR];
|
||||
|
||||
color_params[LeftIndex] = left_joycon;
|
||||
color_params[RightIndex] = right_joycon;
|
||||
color_params[LeftIndex].Set("color", true);
|
||||
color_params[RightIndex].Set("color", true);
|
||||
|
||||
battery_params[LeftIndex] = left_joycon;
|
||||
battery_params[RightIndex] = right_joycon;
|
||||
battery_params[LeftIndex].Set("battery", true);
|
||||
battery_params[RightIndex].Set("battery", true);
|
||||
|
||||
camera_params = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
camera_params[0] = right_joycon;
|
||||
camera_params[0].Set("camera", true);
|
||||
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
nfc_params[1] = right_joycon;
|
||||
nfc_params[1].Set("nfc", true);
|
||||
|
||||
output_params[LeftIndex] = left_joycon;
|
||||
output_params[RightIndex] = right_joycon;
|
||||
output_params[2] = camera_params;
|
||||
output_params[3] = nfc_params;
|
||||
output_params[2] = camera_params[1];
|
||||
output_params[3] = nfc_params[0];
|
||||
output_params[LeftIndex].Set("output", true);
|
||||
output_params[RightIndex].Set("output", true);
|
||||
output_params[2].Set("output", true);
|
||||
|
@ -160,8 +174,11 @@ void EmulatedController::LoadDevices() {
|
|||
Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(battery_params, battery_devices.begin(),
|
||||
Common::Input::CreateInputDevice);
|
||||
camera_devices = Common::Input::CreateInputDevice(camera_params);
|
||||
nfc_devices = Common::Input::CreateInputDevice(nfc_params);
|
||||
std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(ring_params, ring_analog_devices.begin(),
|
||||
Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(output_params, output_devices.begin(),
|
||||
Common::Input::CreateOutputDevice);
|
||||
|
||||
|
@ -323,6 +340,19 @@ void EmulatedController::ReloadInput() {
|
|||
battery_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < color_devices.size(); ++index) {
|
||||
if (!color_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
color_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||
SetColors(callback, index);
|
||||
},
|
||||
});
|
||||
color_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < motion_devices.size(); ++index) {
|
||||
if (!motion_devices[index]) {
|
||||
continue;
|
||||
|
@ -336,22 +366,37 @@ void EmulatedController::ReloadInput() {
|
|||
motion_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
if (camera_devices) {
|
||||
camera_devices->SetCallback({
|
||||
for (std::size_t index = 0; index < camera_devices.size(); ++index) {
|
||||
if (!camera_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
camera_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
|
||||
});
|
||||
camera_devices->ForceUpdate();
|
||||
camera_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
if (nfc_devices) {
|
||||
if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
|
||||
nfc_devices->SetCallback({
|
||||
for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) {
|
||||
if (!ring_analog_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
ring_analog_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
|
||||
});
|
||||
ring_analog_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < nfc_devices.size(); ++index) {
|
||||
if (!nfc_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
nfc_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
|
||||
});
|
||||
nfc_devices->ForceUpdate();
|
||||
}
|
||||
nfc_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
// Register TAS devices. No need to force update
|
||||
|
@ -421,6 +466,9 @@ void EmulatedController::UnloadInput() {
|
|||
for (auto& battery : battery_devices) {
|
||||
battery.reset();
|
||||
}
|
||||
for (auto& color : color_devices) {
|
||||
color.reset();
|
||||
}
|
||||
for (auto& output : output_devices) {
|
||||
output.reset();
|
||||
}
|
||||
|
@ -436,8 +484,15 @@ void EmulatedController::UnloadInput() {
|
|||
for (auto& stick : virtual_stick_devices) {
|
||||
stick.reset();
|
||||
}
|
||||
camera_devices.reset();
|
||||
nfc_devices.reset();
|
||||
for (auto& camera : camera_devices) {
|
||||
camera.reset();
|
||||
}
|
||||
for (auto& ring : ring_analog_devices) {
|
||||
ring.reset();
|
||||
}
|
||||
for (auto& nfc : nfc_devices) {
|
||||
nfc.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::EnableConfiguration() {
|
||||
|
@ -449,6 +504,11 @@ void EmulatedController::EnableConfiguration() {
|
|||
void EmulatedController::DisableConfiguration() {
|
||||
is_configuring = false;
|
||||
|
||||
// Get Joycon colors before turning on the controller
|
||||
for (const auto& color_device : color_devices) {
|
||||
color_device->ForceUpdate();
|
||||
}
|
||||
|
||||
// Apply temporary npad type to the real controller
|
||||
if (tmp_npad_type != npad_type) {
|
||||
if (is_connected) {
|
||||
|
@ -502,6 +562,9 @@ void EmulatedController::SaveCurrentConfig() {
|
|||
for (std::size_t index = 0; index < player.motions.size(); ++index) {
|
||||
player.motions[index] = motion_params[index].Serialize();
|
||||
}
|
||||
if (npad_id_type == NpadIdType::Player1) {
|
||||
Settings::values.ringcon_analogs = ring_params[0].Serialize();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::RestoreConfig() {
|
||||
|
@ -773,17 +836,21 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
|
|||
if (index >= controller.stick_values.size()) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto stick_value = TransformToStick(callback);
|
||||
|
||||
// Only read stick values that have the same uuid or are over the threshold to avoid flapping
|
||||
if (controller.stick_values[index].uuid != uuid) {
|
||||
const bool is_tas = uuid == TAS_UUID;
|
||||
if (is_tas && stick_value.x.value == 0 && stick_value.y.value == 0) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
if (!is_tas && !stick_value.down && !stick_value.up && !stick_value.left &&
|
||||
!stick_value.right) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -794,8 +861,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
|
|||
if (is_configuring) {
|
||||
controller.analog_stick_state.left = {};
|
||||
controller.analog_stick_state.right = {};
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Stick, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -827,9 +892,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
|
|||
controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
|
||||
break;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Stick, true);
|
||||
}
|
||||
|
||||
void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback,
|
||||
|
@ -837,7 +899,9 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
|
|||
if (index >= controller.trigger_values.size()) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto trigger_value = TransformToTrigger(callback);
|
||||
|
||||
// Only read trigger values that have the same uuid or are pressed once
|
||||
|
@ -853,13 +917,12 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
|
|||
if (is_configuring) {
|
||||
controller.gc_trigger_state.left = 0;
|
||||
controller.gc_trigger_state.right = 0;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Trigger, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only GC controllers have analog triggers
|
||||
if (npad_type != NpadStyleIndex::GameCube) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -876,9 +939,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
|
|||
controller.npad_button_state.zr.Assign(trigger.pressed.value);
|
||||
break;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Trigger, true);
|
||||
}
|
||||
|
||||
void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback,
|
||||
|
@ -886,7 +946,8 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
|
|||
if (index >= controller.motion_values.size()) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
auto& raw_status = controller.motion_values[index].raw_status;
|
||||
auto& emulated = controller.motion_values[index].emulated;
|
||||
|
||||
|
@ -907,8 +968,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
|
|||
force_update_motion = raw_status.force_update;
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Motion, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -918,9 +977,56 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
|
|||
motion.rotation = emulated.GetRotations();
|
||||
motion.orientation = emulated.GetOrientation();
|
||||
motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Motion, true);
|
||||
void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback,
|
||||
std::size_t index) {
|
||||
if (index >= controller.color_values.size()) {
|
||||
return;
|
||||
}
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Color, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
controller.color_values[index] = TransformToColor(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller.color_values[index].body == 0) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
controller.colors_state.fullkey = {
|
||||
.body = GetNpadColor(controller.color_values[index].body),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
if (npad_type == NpadStyleIndex::ProController) {
|
||||
controller.colors_state.left = {
|
||||
.body = GetNpadColor(controller.color_values[index].left_grip),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
controller.colors_state.right = {
|
||||
.body = GetNpadColor(controller.color_values[index].right_grip),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
} else {
|
||||
switch (index) {
|
||||
case LeftIndex:
|
||||
controller.colors_state.left = {
|
||||
.body = GetNpadColor(controller.color_values[index].body),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
break;
|
||||
case RightIndex:
|
||||
controller.colors_state.right = {
|
||||
.body = GetNpadColor(controller.color_values[index].body),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
|
||||
|
@ -928,12 +1034,11 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
|
|||
if (index >= controller.battery_values.size()) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
controller.battery_values[index] = TransformToBattery(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Battery, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -989,18 +1094,14 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
|
|||
};
|
||||
break;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Battery, true);
|
||||
}
|
||||
|
||||
void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
|
||||
std::unique_lock lock{mutex};
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
controller.camera_values = TransformToCamera(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::IrSensor, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1008,18 +1109,28 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
|
|||
controller.camera_state.format =
|
||||
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
|
||||
controller.camera_state.data = controller.camera_values.data;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::IrSensor, true);
|
||||
void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto force_value = TransformToStick(callback);
|
||||
|
||||
controller.ring_analog_value = force_value.x;
|
||||
|
||||
if (is_configuring) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller.ring_analog_state.force = force_value.x.value;
|
||||
}
|
||||
|
||||
void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
|
||||
std::unique_lock lock{mutex};
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
controller.nfc_values = TransformToNfc(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Nfc, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1027,9 +1138,6 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
|
|||
controller.nfc_values.state,
|
||||
controller.nfc_values.data,
|
||||
};
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Nfc, true);
|
||||
}
|
||||
|
||||
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
|
||||
|
@ -1061,7 +1169,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
|
|||
.type = type,
|
||||
};
|
||||
return output_devices[device_index]->SetVibration(status) ==
|
||||
Common::Input::VibrationError::None;
|
||||
Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
|
||||
|
@ -1083,16 +1191,32 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
|
|||
return output_devices[device_index]->IsVibrationEnabled();
|
||||
}
|
||||
|
||||
bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
|
||||
LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
|
||||
auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
Common::Input::DriverResult EmulatedController::SetPollingMode(
|
||||
EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
|
||||
LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
|
||||
|
||||
auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
|
||||
auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_output_device = output_devices[3];
|
||||
|
||||
const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
|
||||
const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode);
|
||||
if (device_index == EmulatedDeviceIndex::LeftIndex) {
|
||||
return left_output_device->SetPollingMode(polling_mode);
|
||||
}
|
||||
|
||||
return virtual_nfc_result == Common::Input::PollingError::None ||
|
||||
mapped_nfc_result == Common::Input::PollingError::None;
|
||||
if (device_index == EmulatedDeviceIndex::RightIndex) {
|
||||
const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
|
||||
const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
|
||||
|
||||
if (virtual_nfc_result == Common::Input::DriverResult::Success) {
|
||||
return virtual_nfc_result;
|
||||
}
|
||||
return mapped_nfc_result;
|
||||
}
|
||||
|
||||
left_output_device->SetPollingMode(polling_mode);
|
||||
right_output_device->SetPollingMode(polling_mode);
|
||||
nfc_output_device->SetPollingMode(polling_mode);
|
||||
return Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::SetCameraFormat(
|
||||
|
@ -1103,13 +1227,22 @@ bool EmulatedController::SetCameraFormat(
|
|||
auto& camera_output_device = output_devices[2];
|
||||
|
||||
if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
|
||||
camera_format)) == Common::Input::CameraError::None) {
|
||||
camera_format)) == Common::Input::DriverResult::Success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to Qt camera if native device doesn't have support
|
||||
return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
|
||||
camera_format)) == Common::Input::CameraError::None;
|
||||
camera_format)) == Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
Common::ParamPackage EmulatedController::GetRingParam() const {
|
||||
return ring_params[0];
|
||||
}
|
||||
|
||||
void EmulatedController::SetRingParam(Common::ParamPackage param) {
|
||||
ring_params[0] = std::move(param);
|
||||
ReloadInput();
|
||||
}
|
||||
|
||||
bool EmulatedController::HasNfc() const {
|
||||
|
@ -1263,39 +1396,35 @@ void EmulatedController::Connect(bool use_temporary_value) {
|
|||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
tmp_is_connected = true;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Connected, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_connected) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
is_connected = true;
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Connected, true);
|
||||
}
|
||||
|
||||
void EmulatedController::Disconnect() {
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
tmp_is_connected = false;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Disconnected, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_connected) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
is_connected = false;
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Disconnected, true);
|
||||
}
|
||||
|
||||
bool EmulatedController::IsConnected(bool get_temporary_value) const {
|
||||
|
@ -1320,19 +1449,21 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
|
|||
}
|
||||
|
||||
void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (is_configuring) {
|
||||
if (tmp_npad_type == npad_type_) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
tmp_npad_type = npad_type_;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Type, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (npad_type == npad_type_) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
if (is_connected) {
|
||||
|
@ -1340,9 +1471,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
|
|||
NpadIdTypeToIndex(npad_id_type));
|
||||
}
|
||||
npad_type = npad_type_;
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Type, true);
|
||||
}
|
||||
|
||||
LedPattern EmulatedController::GetLedPattern() const {
|
||||
|
@ -1403,6 +1531,10 @@ CameraValues EmulatedController::GetCameraValues() const {
|
|||
return controller.camera_values;
|
||||
}
|
||||
|
||||
RingAnalogValue EmulatedController::GetRingSensorValues() const {
|
||||
return controller.ring_analog_value;
|
||||
}
|
||||
|
||||
HomeButtonState EmulatedController::GetHomeButtons() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
|
@ -1436,7 +1568,7 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const {
|
|||
}
|
||||
|
||||
AnalogSticks EmulatedController::GetSticks() const {
|
||||
std::unique_lock lock{mutex};
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (is_configuring) {
|
||||
return {};
|
||||
|
@ -1486,6 +1618,10 @@ const CameraState& EmulatedController::GetCamera() const {
|
|||
return controller.camera_state;
|
||||
}
|
||||
|
||||
RingSensorForce EmulatedController::GetRingSensorForce() const {
|
||||
return controller.ring_analog_state;
|
||||
}
|
||||
|
||||
const NfcState& EmulatedController::GetNfc() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return controller.nfc_state;
|
||||
|
|
|
@ -35,19 +35,27 @@ using ControllerMotionDevices =
|
|||
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
|
||||
using TriggerDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
|
||||
using ColorDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using BatteryDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
|
||||
using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
|
||||
using CameraDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using RingAnalogDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using NfcDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
|
||||
|
||||
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
|
||||
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
|
||||
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
|
||||
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
|
||||
using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using CameraParams = Common::ParamPackage;
|
||||
using NfcParams = Common::ParamPackage;
|
||||
using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
|
||||
|
||||
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
|
||||
|
@ -58,6 +66,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native
|
|||
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
|
||||
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
|
||||
using CameraValues = Common::Input::CameraStatus;
|
||||
using RingAnalogValue = Common::Input::AnalogStatus;
|
||||
using NfcValues = Common::Input::NfcStatus;
|
||||
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
|
||||
|
||||
|
@ -84,6 +93,10 @@ struct CameraState {
|
|||
std::size_t sample{};
|
||||
};
|
||||
|
||||
struct RingSensorForce {
|
||||
f32 force;
|
||||
};
|
||||
|
||||
struct NfcState {
|
||||
Common::Input::NfcState state{};
|
||||
std::vector<u8> data{};
|
||||
|
@ -116,6 +129,7 @@ struct ControllerStatus {
|
|||
BatteryValues battery_values{};
|
||||
VibrationValues vibration_values{};
|
||||
CameraValues camera_values{};
|
||||
RingAnalogValue ring_analog_value{};
|
||||
NfcValues nfc_values{};
|
||||
|
||||
// Data for HID serices
|
||||
|
@ -129,6 +143,7 @@ struct ControllerStatus {
|
|||
ControllerColors colors_state{};
|
||||
BatteryLevelState battery_state{};
|
||||
CameraState camera_state{};
|
||||
RingSensorForce ring_analog_state{};
|
||||
NfcState nfc_state{};
|
||||
};
|
||||
|
||||
|
@ -141,6 +156,7 @@ enum class ControllerTriggerType {
|
|||
Battery,
|
||||
Vibration,
|
||||
IrSensor,
|
||||
RingController,
|
||||
Nfc,
|
||||
Connected,
|
||||
Disconnected,
|
||||
|
@ -294,6 +310,9 @@ public:
|
|||
/// Returns the latest camera status from the controller with parameters
|
||||
CameraValues GetCameraValues() const;
|
||||
|
||||
/// Returns the latest status of analog input from the ring sensor with parameters
|
||||
RingAnalogValue GetRingSensorValues() const;
|
||||
|
||||
/// Returns the latest status of button input for the hid::HomeButton service
|
||||
HomeButtonState GetHomeButtons() const;
|
||||
|
||||
|
@ -324,6 +343,9 @@ public:
|
|||
/// Returns the latest camera status from the controller
|
||||
const CameraState& GetCamera() const;
|
||||
|
||||
/// Returns the latest ringcon force sensor value
|
||||
RingSensorForce GetRingSensorForce() const;
|
||||
|
||||
/// Returns the latest ntag status from the controller
|
||||
const NfcState& GetNfc() const;
|
||||
|
||||
|
@ -341,10 +363,12 @@ public:
|
|||
|
||||
/**
|
||||
* Sets the desired data to be polled from a controller
|
||||
* @param device_index index of the controller to set the polling mode
|
||||
* @param polling_mode type of input desired buttons, gyro, nfc, ir, etc.
|
||||
* @return true if SetPollingMode was successfull
|
||||
* @return driver result from this command
|
||||
*/
|
||||
bool SetPollingMode(Common::Input::PollingMode polling_mode);
|
||||
Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index,
|
||||
Common::Input::PollingMode polling_mode);
|
||||
|
||||
/**
|
||||
* Sets the desired camera format to be polled from a controller
|
||||
|
@ -353,6 +377,15 @@ public:
|
|||
*/
|
||||
bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
|
||||
|
||||
// Returns the current mapped ring device
|
||||
Common::ParamPackage GetRingParam() const;
|
||||
|
||||
/**
|
||||
* Updates the current mapped ring device
|
||||
* @param param ParamPackage with ring sensor data to be mapped
|
||||
*/
|
||||
void SetRingParam(Common::ParamPackage param);
|
||||
|
||||
/// Returns true if the device has nfc support
|
||||
bool HasNfc() const;
|
||||
|
||||
|
@ -432,10 +465,17 @@ private:
|
|||
*/
|
||||
void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||
|
||||
/**
|
||||
* Updates the color status of the controller
|
||||
* @param callback A CallbackStatus containing the color status
|
||||
* @param index color ID of the to be updated
|
||||
*/
|
||||
void SetColors(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||
|
||||
/**
|
||||
* Updates the battery status of the controller
|
||||
* @param callback A CallbackStatus containing the battery status
|
||||
* @param index Button ID of the to be updated
|
||||
* @param index battery ID of the to be updated
|
||||
*/
|
||||
void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||
|
||||
|
@ -445,6 +485,12 @@ private:
|
|||
*/
|
||||
void SetCamera(const Common::Input::CallbackStatus& callback);
|
||||
|
||||
/**
|
||||
* Updates the ring analog sensor status of the ring controller
|
||||
* @param callback A CallbackStatus containing the force status
|
||||
*/
|
||||
void SetRingAnalog(const Common::Input::CallbackStatus& callback);
|
||||
|
||||
/**
|
||||
* Updates the nfc status of the controller
|
||||
* @param callback A CallbackStatus containing the nfc status
|
||||
|
@ -484,7 +530,9 @@ private:
|
|||
ControllerMotionParams motion_params;
|
||||
TriggerParams trigger_params;
|
||||
BatteryParams battery_params;
|
||||
ColorParams color_params;
|
||||
CameraParams camera_params;
|
||||
RingAnalogParams ring_params;
|
||||
NfcParams nfc_params;
|
||||
OutputParams output_params;
|
||||
|
||||
|
@ -493,7 +541,9 @@ private:
|
|||
ControllerMotionDevices motion_devices;
|
||||
TriggerDevices trigger_devices;
|
||||
BatteryDevices battery_devices;
|
||||
ColorDevices color_devices;
|
||||
CameraDevices camera_devices;
|
||||
RingAnalogDevices ring_analog_devices;
|
||||
NfcDevices nfc_devices;
|
||||
OutputDevices output_devices;
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ EmulatedDevices::EmulatedDevices() = default;
|
|||
EmulatedDevices::~EmulatedDevices() = default;
|
||||
|
||||
void EmulatedDevices::ReloadFromSettings() {
|
||||
ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
|
||||
ReloadInput();
|
||||
}
|
||||
|
||||
|
@ -66,8 +65,6 @@ void EmulatedDevices::ReloadInput() {
|
|||
key_index++;
|
||||
}
|
||||
|
||||
ring_analog_device = Common::Input::CreateInputDevice(ring_params);
|
||||
|
||||
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
|
||||
if (!mouse_button_devices[index]) {
|
||||
continue;
|
||||
|
@ -122,13 +119,6 @@ void EmulatedDevices::ReloadInput() {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (ring_analog_device) {
|
||||
ring_analog_device->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedDevices::UnloadInput() {
|
||||
|
@ -145,7 +135,6 @@ void EmulatedDevices::UnloadInput() {
|
|||
for (auto& button : keyboard_modifier_devices) {
|
||||
button.reset();
|
||||
}
|
||||
ring_analog_device.reset();
|
||||
}
|
||||
|
||||
void EmulatedDevices::EnableConfiguration() {
|
||||
|
@ -165,7 +154,6 @@ void EmulatedDevices::SaveCurrentConfig() {
|
|||
if (!is_configuring) {
|
||||
return;
|
||||
}
|
||||
Settings::values.ringcon_analogs = ring_params.Serialize();
|
||||
}
|
||||
|
||||
void EmulatedDevices::RestoreConfig() {
|
||||
|
@ -175,15 +163,6 @@ void EmulatedDevices::RestoreConfig() {
|
|||
ReloadFromSettings();
|
||||
}
|
||||
|
||||
Common::ParamPackage EmulatedDevices::GetRingParam() const {
|
||||
return ring_params;
|
||||
}
|
||||
|
||||
void EmulatedDevices::SetRingParam(Common::ParamPackage param) {
|
||||
ring_params = std::move(param);
|
||||
ReloadInput();
|
||||
}
|
||||
|
||||
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
|
||||
std::size_t index) {
|
||||
if (index >= device_status.keyboard_values.size()) {
|
||||
|
@ -430,23 +409,6 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
|
|||
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||
}
|
||||
|
||||
void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
|
||||
std::lock_guard lock{mutex};
|
||||
const auto force_value = TransformToStick(callback);
|
||||
|
||||
device_status.ring_analog_value = force_value.x;
|
||||
|
||||
if (is_configuring) {
|
||||
device_status.ring_analog_value = {};
|
||||
TriggerOnChange(DeviceTriggerType::RingController);
|
||||
return;
|
||||
}
|
||||
|
||||
device_status.ring_analog_state.force = force_value.x.value;
|
||||
|
||||
TriggerOnChange(DeviceTriggerType::RingController);
|
||||
}
|
||||
|
||||
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return device_status.keyboard_values;
|
||||
|
@ -462,10 +424,6 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
|
|||
return device_status.mouse_button_values;
|
||||
}
|
||||
|
||||
RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
|
||||
return device_status.ring_analog_value;
|
||||
}
|
||||
|
||||
KeyboardKey EmulatedDevices::GetKeyboard() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return device_status.keyboard_state;
|
||||
|
@ -491,10 +449,6 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
|
|||
return device_status.mouse_wheel_state;
|
||||
}
|
||||
|
||||
RingSensorForce EmulatedDevices::GetRingSensorForce() const {
|
||||
return device_status.ring_analog_state;
|
||||
}
|
||||
|
||||
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
|
||||
std::scoped_lock lock{callback_mutex};
|
||||
for (const auto& poller_pair : callback_list) {
|
||||
|
|
|
@ -26,11 +26,9 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice
|
|||
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
|
||||
Settings::NativeMouseWheel::NumMouseWheels>;
|
||||
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
|
||||
using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
|
||||
|
||||
using MouseButtonParams =
|
||||
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
|
||||
using RingAnalogParams = Common::ParamPackage;
|
||||
|
||||
using KeyboardValues =
|
||||
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
|
||||
|
@ -41,17 +39,12 @@ using MouseButtonValues =
|
|||
using MouseAnalogValues =
|
||||
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
|
||||
using MouseStickValue = Common::Input::TouchStatus;
|
||||
using RingAnalogValue = Common::Input::AnalogStatus;
|
||||
|
||||
struct MousePosition {
|
||||
f32 x;
|
||||
f32 y;
|
||||
};
|
||||
|
||||
struct RingSensorForce {
|
||||
f32 force;
|
||||
};
|
||||
|
||||
struct DeviceStatus {
|
||||
// Data from input_common
|
||||
KeyboardValues keyboard_values{};
|
||||
|
@ -59,7 +52,6 @@ struct DeviceStatus {
|
|||
MouseButtonValues mouse_button_values{};
|
||||
MouseAnalogValues mouse_analog_values{};
|
||||
MouseStickValue mouse_stick_value{};
|
||||
RingAnalogValue ring_analog_value{};
|
||||
|
||||
// Data for HID serices
|
||||
KeyboardKey keyboard_state{};
|
||||
|
@ -67,7 +59,6 @@ struct DeviceStatus {
|
|||
MouseButton mouse_button_state{};
|
||||
MousePosition mouse_position_state{};
|
||||
AnalogStickState mouse_wheel_state{};
|
||||
RingSensorForce ring_analog_state{};
|
||||
};
|
||||
|
||||
enum class DeviceTriggerType {
|
||||
|
@ -138,9 +129,6 @@ public:
|
|||
/// Returns the latest status of button input from the mouse with parameters
|
||||
MouseButtonValues GetMouseButtonsValues() const;
|
||||
|
||||
/// Returns the latest status of analog input from the ring sensor with parameters
|
||||
RingAnalogValue GetRingSensorValues() const;
|
||||
|
||||
/// Returns the latest status of button input from the keyboard
|
||||
KeyboardKey GetKeyboard() const;
|
||||
|
||||
|
@ -156,9 +144,6 @@ public:
|
|||
/// Returns the latest mouse wheel change
|
||||
AnalogStickState GetMouseWheel() const;
|
||||
|
||||
/// Returns the latest ringcon force sensor value
|
||||
RingSensorForce GetRingSensorForce() const;
|
||||
|
||||
/**
|
||||
* Adds a callback to the list of events
|
||||
* @param update_callback InterfaceUpdateCallback that will be triggered
|
||||
|
@ -224,14 +209,11 @@ private:
|
|||
|
||||
bool is_configuring{false};
|
||||
|
||||
RingAnalogParams ring_params;
|
||||
|
||||
KeyboardDevices keyboard_devices;
|
||||
KeyboardModifierDevices keyboard_modifier_devices;
|
||||
MouseButtonDevices mouse_button_devices;
|
||||
MouseAnalogDevices mouse_analog_devices;
|
||||
MouseStickDevice mouse_stick_device;
|
||||
RingAnalogDevice ring_analog_device;
|
||||
|
||||
mutable std::mutex mutex;
|
||||
mutable std::mutex callback_mutex;
|
||||
|
|
|
@ -304,6 +304,18 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
|
|||
return nfc;
|
||||
}
|
||||
|
||||
Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) {
|
||||
switch (callback.type) {
|
||||
case Common::Input::InputType::Color:
|
||||
return callback.color_status;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type);
|
||||
return {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
|
||||
const auto& properties = analog.properties;
|
||||
float& raw_value = analog.raw_value;
|
||||
|
|
|
@ -88,10 +88,18 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
|
|||
* Converts raw input data into a valid nfc status.
|
||||
*
|
||||
* @param callback Supported callbacks: Nfc.
|
||||
* @return A valid CameraObject object.
|
||||
* @return A valid data tag vector.
|
||||
*/
|
||||
Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
|
||||
|
||||
/**
|
||||
* Converts raw input data into a valid color status.
|
||||
*
|
||||
* @param callback Supported callbacks: Color.
|
||||
* @return A valid Color object.
|
||||
*/
|
||||
Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback);
|
||||
|
||||
/**
|
||||
* Converts raw analog data into a valid analog value
|
||||
* @param analog An analog object containing raw data and properties
|
||||
|
|
|
@ -272,6 +272,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
}
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconLeft:
|
||||
shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
|
||||
shared_memory->fullkey_color.fullkey = body_colors.left;
|
||||
shared_memory->joycon_color.attribute = ColorAttribute::Ok;
|
||||
shared_memory->joycon_color.left = body_colors.left;
|
||||
shared_memory->battery_level_dual = battery_level.left.battery_level;
|
||||
|
@ -285,6 +287,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||
shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
|
||||
shared_memory->fullkey_color.fullkey = body_colors.right;
|
||||
shared_memory->joycon_color.attribute = ColorAttribute::Ok;
|
||||
shared_memory->joycon_color.right = body_colors.right;
|
||||
shared_memory->battery_level_right = battery_level.right.battery_level;
|
||||
|
@ -332,6 +336,20 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
|
||||
controller.is_connected = true;
|
||||
controller.device->Connect();
|
||||
controller.device->SetLedPattern();
|
||||
if (controller_type == Core::HID::NpadStyleIndex::JoyconDual) {
|
||||
if (controller.is_dual_left_connected) {
|
||||
controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::LeftIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
}
|
||||
if (controller.is_dual_right_connected) {
|
||||
controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
}
|
||||
} else {
|
||||
controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
|
||||
Common::Input::PollingMode::Active);
|
||||
}
|
||||
SignalStyleSetChangedEvent(npad_id);
|
||||
WriteEmptyEntry(controller.shared_memory);
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {
|
|||
|
||||
const auto parameters{rp.PopRaw<Parameters>()};
|
||||
|
||||
LOG_INFO(Service_HID,
|
||||
LOG_DEBUG(Service_HID,
|
||||
"called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
|
||||
"player_number={}, is_valid={}, inval={}, applet_resource_user_id{}",
|
||||
parameters.enable, parameters.bus_handle.abstracted_pad_id,
|
||||
|
@ -326,7 +326,7 @@ void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) {
|
|||
IPC::RequestParser rp{ctx};
|
||||
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||
|
||||
LOG_INFO(Service_HID,
|
||||
LOG_DEBUG(Service_HID,
|
||||
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
|
||||
"is_valid={}",
|
||||
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hid/emulated_devices.h"
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
|
@ -12,16 +12,20 @@ namespace Service::HID {
|
|||
RingController::RingController(Core::HID::HIDCore& hid_core_,
|
||||
KernelHelpers::ServiceContext& service_context_)
|
||||
: HidbusBase(service_context_) {
|
||||
input = hid_core_.GetEmulatedDevices();
|
||||
input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
}
|
||||
|
||||
RingController::~RingController() = default;
|
||||
|
||||
void RingController::OnInit() {
|
||||
input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Ring);
|
||||
return;
|
||||
}
|
||||
|
||||
void RingController::OnRelease() {
|
||||
input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||
|
||||
namespace Core::HID {
|
||||
class EmulatedDevices;
|
||||
class EmulatedController;
|
||||
} // namespace Core::HID
|
||||
|
||||
namespace Service::HID {
|
||||
|
@ -248,6 +248,6 @@ private:
|
|||
.zero = {.value = idle_value, .crc = 225},
|
||||
};
|
||||
|
||||
Core::HID::EmulatedDevices* input;
|
||||
Core::HID::EmulatedController* input;
|
||||
};
|
||||
} // namespace Service::HID
|
||||
|
|
|
@ -108,6 +108,8 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
|
|||
auto result = IsIrCameraHandleValid(parameters.camera_handle);
|
||||
if (result.IsSuccess()) {
|
||||
// TODO: Stop Image processor
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
result = ResultSuccess;
|
||||
}
|
||||
|
||||
|
@ -139,6 +141,8 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
|
|||
MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
|
||||
auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
|
||||
image_transfer_processor.SetConfig(parameters.processor_config);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::IR);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
@ -170,6 +174,8 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
|
|||
auto& image_transfer_processor =
|
||||
GetProcessor<ClusteringProcessor>(parameters.camera_handle);
|
||||
image_transfer_processor.SetConfig(parameters.processor_config);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::IR);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
@ -219,6 +225,8 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
|
|||
GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
|
||||
image_transfer_processor.SetConfig(parameters.processor_config);
|
||||
image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::IR);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
@ -294,6 +302,8 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
|
|||
auto& image_transfer_processor =
|
||||
GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
|
||||
image_transfer_processor.SetConfig(parameters.processor_config);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::IR);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
@ -343,6 +353,8 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
|
|||
MakeProcessor<PointingProcessor>(camera_handle, device);
|
||||
auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
|
||||
image_transfer_processor.SetConfig(processor_config);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::IR);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
@ -453,6 +465,8 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
|
|||
GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
|
||||
image_transfer_processor.SetConfig(parameters.processor_config);
|
||||
image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::IR);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
@ -479,6 +493,8 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
|
|||
MakeProcessor<IrLedProcessor>(camera_handle, device);
|
||||
auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
|
||||
image_transfer_processor.SetConfig(processor_config);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::IR);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
@ -504,6 +520,8 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
|
|||
auto result = IsIrCameraHandleValid(parameters.camera_handle);
|
||||
if (result.IsSuccess()) {
|
||||
// TODO: Stop image processor async
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
result = ResultSuccess;
|
||||
}
|
||||
|
||||
|
|
|
@ -130,7 +130,9 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
|
|||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
|
||||
if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::NFC) !=
|
||||
Common::Input::DriverResult::Success) {
|
||||
LOG_ERROR(Service_NFC, "Nfc not supported");
|
||||
return NfcDisabled;
|
||||
}
|
||||
|
@ -141,7 +143,8 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
|
|||
}
|
||||
|
||||
Result NfcDevice::StopDetection() {
|
||||
npad_device->SetPollingMode(Common::Input::PollingMode::Active);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
|
||||
if (device_state == NFP::DeviceState::Initialized) {
|
||||
return ResultSuccess;
|
||||
|
|
|
@ -152,7 +152,9 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
|
|||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
|
||||
if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::NFC) !=
|
||||
Common::Input::DriverResult::Success) {
|
||||
LOG_ERROR(Service_NFP, "Nfc not supported");
|
||||
return NfcDisabled;
|
||||
}
|
||||
|
@ -163,7 +165,8 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
|
|||
}
|
||||
|
||||
Result NfpDevice::StopDetection() {
|
||||
npad_device->SetPollingMode(Common::Input::PollingMode::Active);
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
|
||||
if (device_state == DeviceState::Initialized) {
|
||||
return ResultSuccess;
|
||||
|
|
|
@ -51,8 +51,29 @@ endif()
|
|||
|
||||
if (ENABLE_SDL2)
|
||||
target_sources(input_common PRIVATE
|
||||
drivers/joycon.cpp
|
||||
drivers/joycon.h
|
||||
drivers/sdl_driver.cpp
|
||||
drivers/sdl_driver.h
|
||||
helpers/joycon_driver.cpp
|
||||
helpers/joycon_driver.h
|
||||
helpers/joycon_protocol/calibration.cpp
|
||||
helpers/joycon_protocol/calibration.h
|
||||
helpers/joycon_protocol/common_protocol.cpp
|
||||
helpers/joycon_protocol/common_protocol.h
|
||||
helpers/joycon_protocol/generic_functions.cpp
|
||||
helpers/joycon_protocol/generic_functions.h
|
||||
helpers/joycon_protocol/joycon_types.h
|
||||
helpers/joycon_protocol/irs.cpp
|
||||
helpers/joycon_protocol/irs.h
|
||||
helpers/joycon_protocol/nfc.cpp
|
||||
helpers/joycon_protocol/nfc.h
|
||||
helpers/joycon_protocol/poller.cpp
|
||||
helpers/joycon_protocol/poller.h
|
||||
helpers/joycon_protocol/ringcon.cpp
|
||||
helpers/joycon_protocol/ringcon.h
|
||||
helpers/joycon_protocol/rumble.cpp
|
||||
helpers/joycon_protocol/rumble.h
|
||||
)
|
||||
target_link_libraries(input_common PRIVATE SDL2::SDL2)
|
||||
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
||||
|
|
|
@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const {
|
|||
}
|
||||
}
|
||||
|
||||
Common::Input::CameraError Camera::SetCameraFormat(
|
||||
Common::Input::DriverResult Camera::SetCameraFormat(
|
||||
[[maybe_unused]] const PadIdentifier& identifier_,
|
||||
const Common::Input::CameraFormat camera_format) {
|
||||
status.format = camera_format;
|
||||
return Common::Input::CameraError::None;
|
||||
return Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
||||
|
|
|
@ -22,7 +22,7 @@ public:
|
|||
std::size_t getImageWidth() const;
|
||||
std::size_t getImageHeight() const;
|
||||
|
||||
Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
|
||||
Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
|
||||
Common::Input::CameraFormat camera_format) override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
|
|||
return true;
|
||||
}
|
||||
|
||||
Common::Input::VibrationError GCAdapter::SetVibration(
|
||||
Common::Input::DriverResult GCAdapter::SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
|
||||
const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
|
||||
const auto processed_amplitude =
|
||||
|
@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration(
|
|||
pads[identifier.port].rumble_amplitude = processed_amplitude;
|
||||
|
||||
if (!rumble_enabled) {
|
||||
return Common::Input::VibrationError::Disabled;
|
||||
return Common::Input::DriverResult::Disabled;
|
||||
}
|
||||
return Common::Input::VibrationError::None;
|
||||
return Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
explicit GCAdapter(std::string input_engine_);
|
||||
~GCAdapter() override;
|
||||
|
||||
Common::Input::VibrationError SetVibration(
|
||||
Common::Input::DriverResult SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||
|
||||
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
|
||||
|
|
677
src/input_common/drivers/joycon.cpp
Normal file
677
src/input_common/drivers/joycon.cpp
Normal file
|
@ -0,0 +1,677 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "input_common/drivers/joycon.h"
|
||||
#include "input_common/helpers/joycon_driver.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
|
||||
// Avoid conflicting with SDL driver
|
||||
if (!Settings::values.enable_joycon_driver) {
|
||||
return;
|
||||
}
|
||||
LOG_INFO(Input, "Joycon driver Initialization started");
|
||||
const int init_res = SDL_hid_init();
|
||||
if (init_res == 0) {
|
||||
Setup();
|
||||
} else {
|
||||
LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
|
||||
}
|
||||
}
|
||||
|
||||
Joycons::~Joycons() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Joycons::Reset() {
|
||||
scan_thread = {};
|
||||
for (const auto& device : left_joycons) {
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
device->Stop();
|
||||
}
|
||||
for (const auto& device : right_joycons) {
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
device->Stop();
|
||||
}
|
||||
SDL_hid_exit();
|
||||
}
|
||||
|
||||
void Joycons::Setup() {
|
||||
u32 port = 0;
|
||||
PreSetController(GetIdentifier(0, Joycon::ControllerType::None));
|
||||
for (auto& device : left_joycons) {
|
||||
PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
|
||||
device = std::make_shared<Joycon::JoyconDriver>(port++);
|
||||
}
|
||||
port = 0;
|
||||
for (auto& device : right_joycons) {
|
||||
PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
|
||||
device = std::make_shared<Joycon::JoyconDriver>(port++);
|
||||
}
|
||||
|
||||
scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
|
||||
}
|
||||
|
||||
void Joycons::ScanThread(std::stop_token stop_token) {
|
||||
constexpr u16 nintendo_vendor_id = 0x057e;
|
||||
Common::SetCurrentThreadName("JoyconScanThread");
|
||||
while (!stop_token.stop_requested()) {
|
||||
SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
|
||||
SDL_hid_device_info* cur_dev = devs;
|
||||
|
||||
while (cur_dev) {
|
||||
if (IsDeviceNew(cur_dev)) {
|
||||
LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
|
||||
cur_dev->product_id);
|
||||
RegisterNewDevice(cur_dev);
|
||||
}
|
||||
cur_dev = cur_dev->next;
|
||||
}
|
||||
|
||||
SDL_hid_free_enumeration(devs);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
|
||||
Joycon::ControllerType type{};
|
||||
Joycon::SerialNumber serial_number{};
|
||||
|
||||
const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
|
||||
if (result != Joycon::DriverResult::Success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
|
||||
if (result2 != Joycon::DriverResult::Success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) {
|
||||
if (!device) {
|
||||
return false;
|
||||
}
|
||||
if (!device->IsConnected()) {
|
||||
return false;
|
||||
}
|
||||
if (device->GetHandleSerialNumber() != serial_number) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Check if device already exist
|
||||
switch (type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
for (const auto& device : left_joycons) {
|
||||
if (is_handle_identical(device)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
for (const auto& device : right_joycons) {
|
||||
if (is_handle_identical(device)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
|
||||
Joycon::ControllerType type{};
|
||||
auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
|
||||
auto handle = GetNextFreeHandle(type);
|
||||
if (handle == nullptr) {
|
||||
LOG_WARNING(Input, "No free handles available");
|
||||
return;
|
||||
}
|
||||
if (result == Joycon::DriverResult::Success) {
|
||||
result = handle->RequestDeviceAccess(device_info);
|
||||
}
|
||||
if (result == Joycon::DriverResult::Success) {
|
||||
LOG_WARNING(Input, "Initialize device");
|
||||
|
||||
const std::size_t port = handle->GetDevicePort();
|
||||
const Joycon::JoyconCallbacks callbacks{
|
||||
.on_battery_data = {[this, port, type](Joycon::Battery value) {
|
||||
OnBatteryUpdate(port, type, value);
|
||||
}},
|
||||
.on_color_data = {[this, port, type](Joycon::Color value) {
|
||||
OnColorUpdate(port, type, value);
|
||||
}},
|
||||
.on_button_data = {[this, port, type](int id, bool value) {
|
||||
OnButtonUpdate(port, type, id, value);
|
||||
}},
|
||||
.on_stick_data = {[this, port, type](int id, f32 value) {
|
||||
OnStickUpdate(port, type, id, value);
|
||||
}},
|
||||
.on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) {
|
||||
OnMotionUpdate(port, type, id, value);
|
||||
}},
|
||||
.on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
|
||||
.on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
|
||||
OnAmiiboUpdate(port, amiibo_data);
|
||||
}},
|
||||
.on_camera_data = {[this, port](const std::vector<u8>& camera_data,
|
||||
Joycon::IrsResolution format) {
|
||||
OnCameraUpdate(port, camera_data, format);
|
||||
}},
|
||||
};
|
||||
|
||||
handle->InitializeDevice();
|
||||
handle->SetCallbacks(callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
|
||||
Joycon::ControllerType type) const {
|
||||
if (type == Joycon::ControllerType::Left) {
|
||||
const auto unconnected_device =
|
||||
std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); });
|
||||
if (unconnected_device != left_joycons.end()) {
|
||||
return *unconnected_device;
|
||||
}
|
||||
}
|
||||
if (type == Joycon::ControllerType::Right) {
|
||||
const auto unconnected_device = std::ranges::find_if(
|
||||
right_joycons, [](auto& device) { return !device->IsConnected(); });
|
||||
|
||||
if (unconnected_device != right_joycons.end()) {
|
||||
return *unconnected_device;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
|
||||
const auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return handle->IsVibrationEnabled();
|
||||
}
|
||||
|
||||
Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier,
|
||||
const Common::Input::VibrationStatus& vibration) {
|
||||
const Joycon::VibrationValue native_vibration{
|
||||
.low_amplitude = vibration.low_amplitude,
|
||||
.low_frequency = vibration.low_frequency,
|
||||
.high_amplitude = vibration.high_amplitude,
|
||||
.high_frequency = vibration.high_frequency,
|
||||
};
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return Common::Input::DriverResult::InvalidHandle;
|
||||
}
|
||||
|
||||
handle->SetVibration(native_vibration);
|
||||
return Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
|
||||
const Common::Input::LedStatus& led_status) {
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return Common::Input::DriverResult::InvalidHandle;
|
||||
}
|
||||
int led_config = led_status.led_1 ? 1 : 0;
|
||||
led_config += led_status.led_2 ? 2 : 0;
|
||||
led_config += led_status.led_3 ? 4 : 0;
|
||||
led_config += led_status.led_4 ? 8 : 0;
|
||||
|
||||
return static_cast<Common::Input::DriverResult>(
|
||||
handle->SetLedConfig(static_cast<u8>(led_config)));
|
||||
}
|
||||
|
||||
Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier,
|
||||
Common::Input::CameraFormat camera_format) {
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return Common::Input::DriverResult::InvalidHandle;
|
||||
}
|
||||
return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig(
|
||||
Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format)));
|
||||
};
|
||||
|
||||
Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
|
||||
return Common::Input::NfcState::Success;
|
||||
};
|
||||
|
||||
Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
|
||||
const std::vector<u8>& data) {
|
||||
return Common::Input::NfcState::NotSupported;
|
||||
};
|
||||
|
||||
Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
|
||||
const Common::Input::PollingMode polling_mode) {
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
LOG_ERROR(Input, "Invalid handle {}", identifier.port);
|
||||
return Common::Input::DriverResult::InvalidHandle;
|
||||
}
|
||||
|
||||
switch (polling_mode) {
|
||||
case Common::Input::PollingMode::Active:
|
||||
return static_cast<Common::Input::DriverResult>(handle->SetActiveMode());
|
||||
case Common::Input::PollingMode::Pasive:
|
||||
return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode());
|
||||
case Common::Input::PollingMode::IR:
|
||||
return static_cast<Common::Input::DriverResult>(handle->SetIrMode());
|
||||
case Common::Input::PollingMode::NFC:
|
||||
return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
|
||||
case Common::Input::PollingMode::Ring:
|
||||
return static_cast<Common::Input::DriverResult>(handle->SetRingConMode());
|
||||
default:
|
||||
return Common::Input::DriverResult::NotSupported;
|
||||
}
|
||||
}
|
||||
|
||||
void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
|
||||
Joycon::Battery value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
if (value.charging != 0) {
|
||||
SetBattery(identifier, Common::Input::BatteryLevel::Charging);
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Input::BatteryLevel battery{};
|
||||
switch (value.status) {
|
||||
case 0:
|
||||
battery = Common::Input::BatteryLevel::Empty;
|
||||
break;
|
||||
case 1:
|
||||
battery = Common::Input::BatteryLevel::Critical;
|
||||
break;
|
||||
case 2:
|
||||
battery = Common::Input::BatteryLevel::Low;
|
||||
break;
|
||||
case 3:
|
||||
battery = Common::Input::BatteryLevel::Medium;
|
||||
break;
|
||||
case 4:
|
||||
default:
|
||||
battery = Common::Input::BatteryLevel::Full;
|
||||
break;
|
||||
}
|
||||
SetBattery(identifier, battery);
|
||||
}
|
||||
|
||||
void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
|
||||
const Joycon::Color& value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
Common::Input::BodyColorStatus color{
|
||||
.body = value.body,
|
||||
.buttons = value.buttons,
|
||||
.left_grip = value.left_grip,
|
||||
.right_grip = value.right_grip,
|
||||
};
|
||||
SetColor(identifier, color);
|
||||
}
|
||||
|
||||
void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
SetButton(identifier, id, value);
|
||||
}
|
||||
|
||||
void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
SetAxis(identifier, id, value);
|
||||
}
|
||||
|
||||
void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
|
||||
const Joycon::MotionData& value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
BasicMotion motion_data{
|
||||
.gyro_x = value.gyro_x,
|
||||
.gyro_y = value.gyro_y,
|
||||
.gyro_z = value.gyro_z,
|
||||
.accel_x = value.accel_x,
|
||||
.accel_y = value.accel_y,
|
||||
.accel_z = value.accel_z,
|
||||
.delta_timestamp = 15000,
|
||||
};
|
||||
SetMotion(identifier, id, motion_data);
|
||||
}
|
||||
|
||||
void Joycons::OnRingConUpdate(f32 ring_data) {
|
||||
// To simplify ring detection it will always be mapped to an empty identifier for all
|
||||
// controllers
|
||||
constexpr PadIdentifier identifier = {
|
||||
.guid = Common::UUID{},
|
||||
.port = 0,
|
||||
.pad = 0,
|
||||
};
|
||||
SetAxis(identifier, 100, ring_data);
|
||||
}
|
||||
|
||||
void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
|
||||
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
|
||||
const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
|
||||
: Common::Input::NfcState::NewAmiibo;
|
||||
SetNfc(identifier, {nfc_state, amiibo_data});
|
||||
}
|
||||
|
||||
void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
|
||||
Joycon::IrsResolution format) {
|
||||
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
|
||||
SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data});
|
||||
}
|
||||
|
||||
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
|
||||
auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
|
||||
if (!device) {
|
||||
return false;
|
||||
}
|
||||
if (!device->IsConnected()) {
|
||||
return false;
|
||||
}
|
||||
if (device->GetDevicePort() == identifier.port) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
|
||||
|
||||
if (type == Joycon::ControllerType::Left) {
|
||||
const auto matching_device = std::ranges::find_if(
|
||||
left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
|
||||
|
||||
if (matching_device != left_joycons.end()) {
|
||||
return *matching_device;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == Joycon::ControllerType::Right) {
|
||||
const auto matching_device = std::ranges::find_if(
|
||||
right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
|
||||
|
||||
if (matching_device != right_joycons.end()) {
|
||||
return *matching_device;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
|
||||
const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)};
|
||||
return {
|
||||
.guid = Common::UUID{guid},
|
||||
.port = port,
|
||||
.pad = static_cast<std::size_t>(type),
|
||||
};
|
||||
}
|
||||
|
||||
Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
return {
|
||||
{"engine", GetEngineName()},
|
||||
{"guid", identifier.guid.RawString()},
|
||||
{"port", std::to_string(identifier.port)},
|
||||
{"pad", std::to_string(identifier.pad)},
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices{};
|
||||
|
||||
auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
if (!device->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType());
|
||||
std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
|
||||
device->GetDevicePort() + 1);
|
||||
param.Set("display", std::move(name));
|
||||
devices.emplace_back(param);
|
||||
};
|
||||
|
||||
for (const auto& controller : left_joycons) {
|
||||
add_entry(controller);
|
||||
}
|
||||
for (const auto& controller : right_joycons) {
|
||||
add_entry(controller);
|
||||
}
|
||||
|
||||
// List dual joycon pairs
|
||||
for (std::size_t i = 0; i < MaxSupportedControllers; i++) {
|
||||
if (!left_joycons[i] || !right_joycons[i]) {
|
||||
continue;
|
||||
}
|
||||
if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) {
|
||||
continue;
|
||||
}
|
||||
auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType());
|
||||
const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType());
|
||||
const auto type = Joycon::ControllerType::Dual;
|
||||
std::string name = fmt::format("{} {}", JoyconName(type), i + 1);
|
||||
|
||||
main_param.Set("display", std::move(name));
|
||||
main_param.Set("guid2", second_param.Get("guid", ""));
|
||||
main_param.Set("pad", std::to_string(static_cast<size_t>(type)));
|
||||
devices.emplace_back(main_param);
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
|
||||
static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>,
|
||||
18>
|
||||
switch_to_joycon_button = {
|
||||
std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true},
|
||||
{Settings::NativeButton::B, Joycon::PadButton::B, true},
|
||||
{Settings::NativeButton::X, Joycon::PadButton::X, true},
|
||||
{Settings::NativeButton::Y, Joycon::PadButton::Y, true},
|
||||
{Settings::NativeButton::DLeft, Joycon::PadButton::Left, false},
|
||||
{Settings::NativeButton::DUp, Joycon::PadButton::Up, false},
|
||||
{Settings::NativeButton::DRight, Joycon::PadButton::Right, false},
|
||||
{Settings::NativeButton::DDown, Joycon::PadButton::Down, false},
|
||||
{Settings::NativeButton::L, Joycon::PadButton::L, false},
|
||||
{Settings::NativeButton::R, Joycon::PadButton::R, true},
|
||||
{Settings::NativeButton::ZL, Joycon::PadButton::ZL, false},
|
||||
{Settings::NativeButton::ZR, Joycon::PadButton::ZR, true},
|
||||
{Settings::NativeButton::Plus, Joycon::PadButton::Plus, true},
|
||||
{Settings::NativeButton::Minus, Joycon::PadButton::Minus, false},
|
||||
{Settings::NativeButton::Home, Joycon::PadButton::Home, true},
|
||||
{Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false},
|
||||
{Settings::NativeButton::LStick, Joycon::PadButton::StickL, false},
|
||||
{Settings::NativeButton::RStick, Joycon::PadButton::StickR, true},
|
||||
};
|
||||
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ButtonMapping mapping{};
|
||||
for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) {
|
||||
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
|
||||
auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
|
||||
if (pad == Joycon::ControllerType::Dual) {
|
||||
pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left;
|
||||
}
|
||||
|
||||
Common::ParamPackage button_params = GetParamPackage(port, pad);
|
||||
button_params.Set("button", static_cast<int>(joycon_button));
|
||||
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||
}
|
||||
|
||||
// Map SL and SR buttons for left joycons
|
||||
if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) {
|
||||
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
|
||||
Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left);
|
||||
|
||||
Common::ParamPackage sl_button_params = button_params;
|
||||
Common::ParamPackage sr_button_params = button_params;
|
||||
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL));
|
||||
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR));
|
||||
mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
|
||||
mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
|
||||
}
|
||||
|
||||
// Map SL and SR buttons for right joycons
|
||||
if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) {
|
||||
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
|
||||
Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right);
|
||||
|
||||
Common::ParamPackage sl_button_params = button_params;
|
||||
Common::ParamPackage sr_button_params = button_params;
|
||||
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL));
|
||||
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR));
|
||||
mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
|
||||
mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
|
||||
auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
|
||||
auto pad_right = pad_left;
|
||||
if (pad_left == Joycon::ControllerType::Dual) {
|
||||
pad_left = Joycon::ControllerType::Left;
|
||||
pad_right = Joycon::ControllerType::Right;
|
||||
}
|
||||
|
||||
AnalogMapping mapping = {};
|
||||
Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left);
|
||||
left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
|
||||
left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
|
||||
Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right);
|
||||
right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
|
||||
right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
|
||||
auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
|
||||
auto pad_right = pad_left;
|
||||
if (pad_left == Joycon::ControllerType::Dual) {
|
||||
pad_left = Joycon::ControllerType::Left;
|
||||
pad_right = Joycon::ControllerType::Right;
|
||||
}
|
||||
|
||||
MotionMapping mapping = {};
|
||||
Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left);
|
||||
left_motion_params.Set("motion", 0);
|
||||
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
|
||||
Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right);
|
||||
right_Motion_params.Set("motion", 1);
|
||||
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
|
||||
const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
|
||||
switch (button) {
|
||||
case Joycon::PadButton::Left:
|
||||
return Common::Input::ButtonNames::ButtonLeft;
|
||||
case Joycon::PadButton::Right:
|
||||
return Common::Input::ButtonNames::ButtonRight;
|
||||
case Joycon::PadButton::Down:
|
||||
return Common::Input::ButtonNames::ButtonDown;
|
||||
case Joycon::PadButton::Up:
|
||||
return Common::Input::ButtonNames::ButtonUp;
|
||||
case Joycon::PadButton::LeftSL:
|
||||
case Joycon::PadButton::RightSL:
|
||||
return Common::Input::ButtonNames::TriggerSL;
|
||||
case Joycon::PadButton::LeftSR:
|
||||
case Joycon::PadButton::RightSR:
|
||||
return Common::Input::ButtonNames::TriggerSR;
|
||||
case Joycon::PadButton::L:
|
||||
return Common::Input::ButtonNames::TriggerL;
|
||||
case Joycon::PadButton::R:
|
||||
return Common::Input::ButtonNames::TriggerR;
|
||||
case Joycon::PadButton::ZL:
|
||||
return Common::Input::ButtonNames::TriggerZL;
|
||||
case Joycon::PadButton::ZR:
|
||||
return Common::Input::ButtonNames::TriggerZR;
|
||||
case Joycon::PadButton::A:
|
||||
return Common::Input::ButtonNames::ButtonA;
|
||||
case Joycon::PadButton::B:
|
||||
return Common::Input::ButtonNames::ButtonB;
|
||||
case Joycon::PadButton::X:
|
||||
return Common::Input::ButtonNames::ButtonX;
|
||||
case Joycon::PadButton::Y:
|
||||
return Common::Input::ButtonNames::ButtonY;
|
||||
case Joycon::PadButton::Plus:
|
||||
return Common::Input::ButtonNames::ButtonPlus;
|
||||
case Joycon::PadButton::Minus:
|
||||
return Common::Input::ButtonNames::ButtonMinus;
|
||||
case Joycon::PadButton::Home:
|
||||
return Common::Input::ButtonNames::ButtonHome;
|
||||
case Joycon::PadButton::Capture:
|
||||
return Common::Input::ButtonNames::ButtonCapture;
|
||||
case Joycon::PadButton::StickL:
|
||||
return Common::Input::ButtonNames::ButtonStickL;
|
||||
case Joycon::PadButton::StickR:
|
||||
return Common::Input::ButtonNames::ButtonStickR;
|
||||
default:
|
||||
return Common::Input::ButtonNames::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
|
||||
if (params.Has("button")) {
|
||||
return GetUIButtonName(params);
|
||||
}
|
||||
if (params.Has("axis")) {
|
||||
return Common::Input::ButtonNames::Value;
|
||||
}
|
||||
if (params.Has("motion")) {
|
||||
return Common::Input::ButtonNames::Engine;
|
||||
}
|
||||
|
||||
return Common::Input::ButtonNames::Invalid;
|
||||
}
|
||||
|
||||
std::string Joycons::JoyconName(Joycon::ControllerType type) const {
|
||||
switch (type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
return "Left Joycon";
|
||||
case Joycon::ControllerType::Right:
|
||||
return "Right Joycon";
|
||||
case Joycon::ControllerType::Pro:
|
||||
return "Pro Controller";
|
||||
case Joycon::ControllerType::Grip:
|
||||
return "Grip Controller";
|
||||
case Joycon::ControllerType::Dual:
|
||||
return "Dual Joycon";
|
||||
default:
|
||||
return "Unknown Joycon";
|
||||
}
|
||||
}
|
||||
} // namespace InputCommon
|
111
src/input_common/drivers/joycon.h
Normal file
111
src/input_common/drivers/joycon.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <SDL_hidapi.h>
|
||||
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
using SerialNumber = std::array<u8, 15>;
|
||||
struct Battery;
|
||||
struct Color;
|
||||
struct MotionData;
|
||||
enum class ControllerType;
|
||||
enum class DriverResult;
|
||||
enum class IrsResolution;
|
||||
class JoyconDriver;
|
||||
} // namespace InputCommon::Joycon
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class Joycons final : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit Joycons(const std::string& input_engine_);
|
||||
|
||||
~Joycons();
|
||||
|
||||
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
|
||||
Common::Input::DriverResult SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||
|
||||
Common::Input::DriverResult SetLeds(const PadIdentifier& identifier,
|
||||
const Common::Input::LedStatus& led_status) override;
|
||||
|
||||
Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
|
||||
Common::Input::CameraFormat camera_format) override;
|
||||
|
||||
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
|
||||
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
|
||||
const std::vector<u8>& data) override;
|
||||
|
||||
Common::Input::DriverResult SetPollingMode(
|
||||
const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
|
||||
|
||||
/// Used for automapping features
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
|
||||
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
|
||||
|
||||
private:
|
||||
static constexpr std::size_t MaxSupportedControllers = 8;
|
||||
|
||||
/// For shutting down, clear all data, join all threads, release usb devices
|
||||
void Reset();
|
||||
|
||||
/// Registers controllers, clears all data and starts the scan thread
|
||||
void Setup();
|
||||
|
||||
/// Actively searchs for new devices
|
||||
void ScanThread(std::stop_token stop_token);
|
||||
|
||||
/// Returns true if device is valid and not registered
|
||||
bool IsDeviceNew(SDL_hid_device_info* device_info) const;
|
||||
|
||||
/// Tries to connect to the new device
|
||||
void RegisterNewDevice(SDL_hid_device_info* device_info);
|
||||
|
||||
/// Returns the next free handle
|
||||
std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const;
|
||||
|
||||
void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value);
|
||||
void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value);
|
||||
void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value);
|
||||
void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value);
|
||||
void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
|
||||
const Joycon::MotionData& value);
|
||||
void OnRingConUpdate(f32 ring_data);
|
||||
void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
|
||||
void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
|
||||
Joycon::IrsResolution format);
|
||||
|
||||
/// Returns a JoyconHandle corresponding to a PadIdentifier
|
||||
std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
|
||||
|
||||
/// Returns a PadIdentifier corresponding to the port number and joycon type
|
||||
PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
|
||||
|
||||
/// Returns a ParamPackage corresponding to the port number and joycon type
|
||||
Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const;
|
||||
|
||||
std::string JoyconName(std::size_t port) const;
|
||||
|
||||
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
||||
|
||||
/// Returns the name of the device in text format
|
||||
std::string JoyconName(Joycon::ControllerType type) const;
|
||||
|
||||
std::jthread scan_thread;
|
||||
|
||||
// Joycon types are split by type to ease supporting dualjoycon configurations
|
||||
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
|
||||
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
|
@ -334,6 +334,15 @@ void SDLDriver::InitJoystick(int joystick_index) {
|
|||
|
||||
const auto guid = GetGUID(sdl_joystick);
|
||||
|
||||
if (Settings::values.enable_joycon_driver) {
|
||||
if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
|
||||
(guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
|
||||
LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
|
||||
SDL_JoystickClose(sdl_joystick);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::scoped_lock lock{joystick_map_mutex};
|
||||
if (joystick_map.find(guid) == joystick_map.end()) {
|
||||
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
|
|||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
||||
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
|
||||
// not a generic one
|
||||
// Disable hidapi drivers for switch controllers when the custom joycon driver is enabled
|
||||
if (Settings::values.enable_joycon_driver) {
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
|
||||
} else {
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
|
||||
}
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
|
||||
|
||||
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
|
||||
// driver on Linux.
|
||||
|
@ -548,7 +561,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
|
|||
return devices;
|
||||
}
|
||||
|
||||
Common::Input::VibrationError SDLDriver::SetVibration(
|
||||
Common::Input::DriverResult SDLDriver::SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
|
||||
const auto joystick =
|
||||
GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
|
||||
|
@ -582,7 +595,7 @@ Common::Input::VibrationError SDLDriver::SetVibration(
|
|||
.vibration = new_vibration,
|
||||
});
|
||||
|
||||
return Common::Input::VibrationError::None;
|
||||
return Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
|
||||
|
|
|
@ -63,7 +63,7 @@ public:
|
|||
|
||||
bool IsStickInverted(const Common::ParamPackage& params) override;
|
||||
|
||||
Common::Input::VibrationError SetVibration(
|
||||
Common::Input::DriverResult SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||
|
||||
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
|
||||
|
|
|
@ -22,22 +22,23 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(
|
|||
|
||||
VirtualAmiibo::~VirtualAmiibo() = default;
|
||||
|
||||
Common::Input::PollingError VirtualAmiibo::SetPollingMode(
|
||||
Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
|
||||
[[maybe_unused]] const PadIdentifier& identifier_,
|
||||
const Common::Input::PollingMode polling_mode_) {
|
||||
polling_mode = polling_mode_;
|
||||
|
||||
if (polling_mode == Common::Input::PollingMode::NFC) {
|
||||
switch (polling_mode) {
|
||||
case Common::Input::PollingMode::NFC:
|
||||
if (state == State::Initialized) {
|
||||
state = State::WaitingForAmiibo;
|
||||
}
|
||||
} else {
|
||||
return Common::Input::DriverResult::Success;
|
||||
default:
|
||||
if (state == State::AmiiboIsOpen) {
|
||||
CloseAmiibo();
|
||||
}
|
||||
return Common::Input::DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
return Common::Input::PollingError::None;
|
||||
}
|
||||
|
||||
Common::Input::NfcState VirtualAmiibo::SupportsNfc(
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
~VirtualAmiibo() override;
|
||||
|
||||
// Sets polling mode to a controller
|
||||
Common::Input::PollingError SetPollingMode(
|
||||
Common::Input::DriverResult SetPollingMode(
|
||||
const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
|
||||
|
||||
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
|
||||
|
|
572
src/input_common/helpers/joycon_driver.cpp
Normal file
572
src/input_common/helpers/joycon_driver.cpp
Normal file
|
@ -0,0 +1,572 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "common/thread.h"
|
||||
#include "input_common/helpers/joycon_driver.h"
|
||||
#include "input_common/helpers/joycon_protocol/calibration.h"
|
||||
#include "input_common/helpers/joycon_protocol/generic_functions.h"
|
||||
#include "input_common/helpers/joycon_protocol/irs.h"
|
||||
#include "input_common/helpers/joycon_protocol/nfc.h"
|
||||
#include "input_common/helpers/joycon_protocol/poller.h"
|
||||
#include "input_common/helpers/joycon_protocol/ringcon.h"
|
||||
#include "input_common/helpers/joycon_protocol/rumble.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
|
||||
hidapi_handle = std::make_shared<JoyconHandle>();
|
||||
}
|
||||
|
||||
JoyconDriver::~JoyconDriver() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void JoyconDriver::Stop() {
|
||||
is_connected = false;
|
||||
input_thread = {};
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
handle_device_type = ControllerType::None;
|
||||
GetDeviceType(device_info, handle_device_type);
|
||||
if (handle_device_type == ControllerType::None) {
|
||||
return DriverResult::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
hidapi_handle->handle =
|
||||
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
|
||||
std::memcpy(&handle_serial_number, device_info->serial_number, 15);
|
||||
if (!hidapi_handle->handle) {
|
||||
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
|
||||
device_info->vendor_id, device_info->product_id);
|
||||
return DriverResult::HandleInUse;
|
||||
}
|
||||
SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::InitializeDevice() {
|
||||
if (!hidapi_handle->handle) {
|
||||
return DriverResult::InvalidHandle;
|
||||
}
|
||||
std::scoped_lock lock{mutex};
|
||||
disable_input_thread = true;
|
||||
|
||||
// Reset Counters
|
||||
error_counter = 0;
|
||||
hidapi_handle->packet_counter = 0;
|
||||
|
||||
// Reset external device status
|
||||
starlink_connected = false;
|
||||
ring_connected = false;
|
||||
amiibo_detected = false;
|
||||
|
||||
// Set HW default configuration
|
||||
vibration_enabled = true;
|
||||
motion_enabled = true;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = false;
|
||||
irs_enabled = false;
|
||||
gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
|
||||
gyro_performance = Joycon::GyroPerformance::HZ833;
|
||||
accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
|
||||
accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
|
||||
|
||||
// Initialize HW Protocols
|
||||
calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
|
||||
generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
|
||||
irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
|
||||
nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
|
||||
ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
|
||||
rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
|
||||
|
||||
// Get fixed joycon info
|
||||
generic_protocol->GetVersionNumber(version);
|
||||
generic_protocol->GetColor(color);
|
||||
if (handle_device_type == ControllerType::Pro) {
|
||||
// Some 3rd party controllers aren't pro controllers
|
||||
generic_protocol->GetControllerType(device_type);
|
||||
} else {
|
||||
device_type = handle_device_type;
|
||||
}
|
||||
generic_protocol->GetSerialNumber(serial_number);
|
||||
supported_features = GetSupportedFeatures();
|
||||
|
||||
// Get Calibration data
|
||||
calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
|
||||
calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
|
||||
calibration_protocol->GetImuCalibration(motion_calibration);
|
||||
|
||||
// Set led status
|
||||
generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
|
||||
|
||||
// Apply HW configuration
|
||||
SetPollingMode();
|
||||
|
||||
// Initialize joycon poller
|
||||
joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
|
||||
right_stick_calibration, motion_calibration);
|
||||
|
||||
// Start pooling for data
|
||||
is_connected = true;
|
||||
if (!input_thread_running) {
|
||||
input_thread =
|
||||
std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
|
||||
}
|
||||
|
||||
disable_input_thread = false;
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
void JoyconDriver::InputThread(std::stop_token stop_token) {
|
||||
LOG_INFO(Input, "Joycon Adapter input thread started");
|
||||
Common::SetCurrentThreadName("JoyconInput");
|
||||
input_thread_running = true;
|
||||
|
||||
// Max update rate is 5ms, ensure we are always able to read a bit faster
|
||||
constexpr int ThreadDelay = 2;
|
||||
std::vector<u8> buffer(MaxBufferSize);
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
int status = 0;
|
||||
|
||||
if (!IsInputThreadValid()) {
|
||||
input_thread.request_stop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// By disabling the input thread we can ensure custom commands will succeed as no package is
|
||||
// skipped
|
||||
if (!disable_input_thread) {
|
||||
status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
|
||||
ThreadDelay);
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
|
||||
}
|
||||
|
||||
if (IsPayloadCorrect(status, buffer)) {
|
||||
OnNewData(buffer);
|
||||
}
|
||||
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
is_connected = false;
|
||||
input_thread_running = false;
|
||||
LOG_INFO(Input, "Joycon Adapter input thread stopped");
|
||||
}
|
||||
|
||||
void JoyconDriver::OnNewData(std::span<u8> buffer) {
|
||||
const auto report_mode = static_cast<InputReport>(buffer[0]);
|
||||
|
||||
// Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
|
||||
// experience
|
||||
switch (report_mode) {
|
||||
case InputReport::STANDARD_FULL_60HZ:
|
||||
case InputReport::NFC_IR_MODE_60HZ:
|
||||
case InputReport::SIMPLE_HID_MODE: {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto new_delta_time = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
|
||||
delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
|
||||
last_update = now;
|
||||
joycon_poller->UpdateColor(color);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const MotionStatus motion_status{
|
||||
.is_enabled = motion_enabled,
|
||||
.delta_time = delta_time,
|
||||
.gyro_sensitivity = gyro_sensitivity,
|
||||
.accelerometer_sensitivity = accelerometer_sensitivity,
|
||||
};
|
||||
|
||||
// TODO: Remove this when calibration is properly loaded and not calculated
|
||||
if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) {
|
||||
InputReportActive data{};
|
||||
memcpy(&data, buffer.data(), sizeof(InputReportActive));
|
||||
calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
|
||||
}
|
||||
|
||||
const RingStatus ring_status{
|
||||
.is_enabled = ring_connected,
|
||||
.default_value = ring_calibration.default_value,
|
||||
.max_value = ring_calibration.max_value,
|
||||
.min_value = ring_calibration.min_value,
|
||||
};
|
||||
|
||||
if (irs_protocol->IsEnabled()) {
|
||||
irs_protocol->RequestImage(buffer);
|
||||
joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
|
||||
}
|
||||
|
||||
if (nfc_protocol->IsEnabled()) {
|
||||
if (amiibo_detected) {
|
||||
if (!nfc_protocol->HasAmiibo()) {
|
||||
joycon_poller->UpdateAmiibo({});
|
||||
amiibo_detected = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!amiibo_detected) {
|
||||
std::vector<u8> data(0x21C);
|
||||
const auto result = nfc_protocol->ScanAmiibo(data);
|
||||
if (result == DriverResult::Success) {
|
||||
joycon_poller->UpdateAmiibo(data);
|
||||
amiibo_detected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (report_mode) {
|
||||
case InputReport::STANDARD_FULL_60HZ:
|
||||
joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
|
||||
break;
|
||||
case InputReport::NFC_IR_MODE_60HZ:
|
||||
joycon_poller->ReadNfcIRMode(buffer, motion_status);
|
||||
break;
|
||||
case InputReport::SIMPLE_HID_MODE:
|
||||
joycon_poller->ReadPassiveMode(buffer);
|
||||
break;
|
||||
case InputReport::SUBCMD_REPLY:
|
||||
LOG_DEBUG(Input, "Unhandled command reply");
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetPollingMode() {
|
||||
disable_input_thread = true;
|
||||
|
||||
rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
|
||||
|
||||
if (motion_enabled && supported_features.motion) {
|
||||
generic_protocol->EnableImu(true);
|
||||
generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
|
||||
accelerometer_sensitivity, accelerometer_performance);
|
||||
} else {
|
||||
generic_protocol->EnableImu(false);
|
||||
}
|
||||
|
||||
if (irs_protocol->IsEnabled()) {
|
||||
irs_protocol->DisableIrs();
|
||||
}
|
||||
|
||||
if (nfc_protocol->IsEnabled()) {
|
||||
amiibo_detected = false;
|
||||
nfc_protocol->DisableNfc();
|
||||
}
|
||||
|
||||
if (ring_protocol->IsEnabled()) {
|
||||
ring_connected = false;
|
||||
ring_protocol->DisableRingCon();
|
||||
}
|
||||
|
||||
if (irs_enabled && supported_features.irs) {
|
||||
auto result = irs_protocol->EnableIrs();
|
||||
if (result == DriverResult::Success) {
|
||||
disable_input_thread = false;
|
||||
return result;
|
||||
}
|
||||
irs_protocol->DisableIrs();
|
||||
LOG_ERROR(Input, "Error enabling IRS");
|
||||
}
|
||||
|
||||
if (nfc_enabled && supported_features.nfc) {
|
||||
auto result = nfc_protocol->EnableNfc();
|
||||
if (result == DriverResult::Success) {
|
||||
result = nfc_protocol->StartNFCPollingMode();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
disable_input_thread = false;
|
||||
return result;
|
||||
}
|
||||
nfc_protocol->DisableNfc();
|
||||
LOG_ERROR(Input, "Error enabling NFC");
|
||||
}
|
||||
|
||||
if (hidbus_enabled && supported_features.hidbus) {
|
||||
auto result = ring_protocol->EnableRingCon();
|
||||
if (result == DriverResult::Success) {
|
||||
result = ring_protocol->StartRingconPolling();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
ring_connected = true;
|
||||
disable_input_thread = false;
|
||||
return result;
|
||||
}
|
||||
ring_connected = false;
|
||||
ring_protocol->DisableRingCon();
|
||||
LOG_ERROR(Input, "Error enabling Ringcon");
|
||||
}
|
||||
|
||||
if (passive_enabled && supported_features.passive) {
|
||||
const auto result = generic_protocol->EnablePassiveMode();
|
||||
if (result == DriverResult::Success) {
|
||||
disable_input_thread = false;
|
||||
return result;
|
||||
}
|
||||
LOG_ERROR(Input, "Error enabling passive mode");
|
||||
}
|
||||
|
||||
// Default Mode
|
||||
const auto result = generic_protocol->EnableActiveMode();
|
||||
if (result != DriverResult::Success) {
|
||||
LOG_ERROR(Input, "Error enabling active mode");
|
||||
}
|
||||
|
||||
disable_input_thread = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
|
||||
SupportedFeatures features{
|
||||
.passive = true,
|
||||
.motion = true,
|
||||
.vibration = true,
|
||||
};
|
||||
|
||||
if (device_type == ControllerType::Right) {
|
||||
features.nfc = true;
|
||||
features.irs = true;
|
||||
features.hidbus = true;
|
||||
}
|
||||
|
||||
if (device_type == ControllerType::Pro) {
|
||||
features.nfc = true;
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
bool JoyconDriver::IsInputThreadValid() const {
|
||||
if (!is_connected.load()) {
|
||||
return false;
|
||||
}
|
||||
if (hidapi_handle->handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// Controller is not responding. Terminate connection
|
||||
if (error_counter > MaxErrorCount) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
|
||||
if (status <= -1) {
|
||||
error_counter++;
|
||||
return false;
|
||||
}
|
||||
// There's no new data
|
||||
if (status == 0) {
|
||||
return false;
|
||||
}
|
||||
// No reply ever starts with zero
|
||||
if (buffer[0] == 0x00) {
|
||||
error_counter++;
|
||||
return false;
|
||||
}
|
||||
error_counter = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (disable_input_thread) {
|
||||
return DriverResult::HandleInUse;
|
||||
}
|
||||
return rumble_protocol->SendVibration(vibration);
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (disable_input_thread) {
|
||||
return DriverResult::HandleInUse;
|
||||
}
|
||||
return generic_protocol->SetLedPattern(led_pattern);
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (disable_input_thread) {
|
||||
return DriverResult::HandleInUse;
|
||||
}
|
||||
disable_input_thread = true;
|
||||
const auto result = irs_protocol->SetIrsConfig(mode_, format_);
|
||||
disable_input_thread = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetPasiveMode() {
|
||||
std::scoped_lock lock{mutex};
|
||||
motion_enabled = false;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = true;
|
||||
irs_enabled = false;
|
||||
return SetPollingMode();
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetActiveMode() {
|
||||
if (is_ring_disabled_by_irs) {
|
||||
is_ring_disabled_by_irs = false;
|
||||
SetActiveMode();
|
||||
return SetRingConMode();
|
||||
}
|
||||
|
||||
std::scoped_lock lock{mutex};
|
||||
motion_enabled = true;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = false;
|
||||
irs_enabled = false;
|
||||
return SetPollingMode();
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetIrMode() {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (!supported_features.irs) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
if (ring_connected) {
|
||||
is_ring_disabled_by_irs = true;
|
||||
}
|
||||
|
||||
motion_enabled = false;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = false;
|
||||
irs_enabled = true;
|
||||
return SetPollingMode();
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetNfcMode() {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (!supported_features.nfc) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
motion_enabled = true;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = true;
|
||||
passive_enabled = false;
|
||||
irs_enabled = false;
|
||||
return SetPollingMode();
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetRingConMode() {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (!supported_features.hidbus) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
motion_enabled = true;
|
||||
hidbus_enabled = true;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = false;
|
||||
irs_enabled = false;
|
||||
|
||||
const auto result = SetPollingMode();
|
||||
|
||||
if (!ring_connected) {
|
||||
return DriverResult::NoDeviceDetected;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool JoyconDriver::IsConnected() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return is_connected.load();
|
||||
}
|
||||
|
||||
bool JoyconDriver::IsVibrationEnabled() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return vibration_enabled;
|
||||
}
|
||||
|
||||
FirmwareVersion JoyconDriver::GetDeviceVersion() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return version;
|
||||
}
|
||||
|
||||
Color JoyconDriver::GetDeviceColor() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return color;
|
||||
}
|
||||
|
||||
std::size_t JoyconDriver::GetDevicePort() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return port;
|
||||
}
|
||||
|
||||
ControllerType JoyconDriver::GetDeviceType() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return device_type;
|
||||
}
|
||||
|
||||
ControllerType JoyconDriver::GetHandleDeviceType() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return handle_device_type;
|
||||
}
|
||||
|
||||
SerialNumber JoyconDriver::GetSerialNumber() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return serial_number;
|
||||
}
|
||||
|
||||
SerialNumber JoyconDriver::GetHandleSerialNumber() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return handle_serial_number;
|
||||
}
|
||||
|
||||
void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
|
||||
joycon_poller->SetCallbacks(callbacks);
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
|
||||
ControllerType& controller_type) {
|
||||
static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{
|
||||
std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
|
||||
{0x2007, ControllerType::Right},
|
||||
};
|
||||
constexpr u16 nintendo_vendor_id = 0x057e;
|
||||
|
||||
controller_type = ControllerType::None;
|
||||
if (device_info->vendor_id != nintendo_vendor_id) {
|
||||
return DriverResult::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
for (const auto& [product_id, type] : supported_devices) {
|
||||
if (device_info->product_id == static_cast<u16>(product_id)) {
|
||||
controller_type = type;
|
||||
return Joycon::DriverResult::Success;
|
||||
}
|
||||
}
|
||||
return Joycon::DriverResult::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
|
||||
SerialNumber& serial_number) {
|
||||
if (device_info->serial_number == nullptr) {
|
||||
return DriverResult::Unknown;
|
||||
}
|
||||
std::memcpy(&serial_number, device_info->serial_number, 15);
|
||||
return Joycon::DriverResult::Success;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
150
src/input_common/helpers/joycon_driver.h
Normal file
150
src/input_common/helpers/joycon_driver.h
Normal file
|
@ -0,0 +1,150 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
class CalibrationProtocol;
|
||||
class GenericProtocol;
|
||||
class IrsProtocol;
|
||||
class NfcProtocol;
|
||||
class JoyconPoller;
|
||||
class RingConProtocol;
|
||||
class RumbleProtocol;
|
||||
|
||||
class JoyconDriver final {
|
||||
public:
|
||||
explicit JoyconDriver(std::size_t port_);
|
||||
|
||||
~JoyconDriver();
|
||||
|
||||
DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
|
||||
DriverResult InitializeDevice();
|
||||
void Stop();
|
||||
|
||||
bool IsConnected() const;
|
||||
bool IsVibrationEnabled() const;
|
||||
|
||||
FirmwareVersion GetDeviceVersion() const;
|
||||
Color GetDeviceColor() const;
|
||||
std::size_t GetDevicePort() const;
|
||||
ControllerType GetDeviceType() const;
|
||||
ControllerType GetHandleDeviceType() const;
|
||||
SerialNumber GetSerialNumber() const;
|
||||
SerialNumber GetHandleSerialNumber() const;
|
||||
|
||||
DriverResult SetVibration(const VibrationValue& vibration);
|
||||
DriverResult SetLedConfig(u8 led_pattern);
|
||||
DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
|
||||
DriverResult SetPasiveMode();
|
||||
DriverResult SetActiveMode();
|
||||
DriverResult SetIrMode();
|
||||
DriverResult SetNfcMode();
|
||||
DriverResult SetRingConMode();
|
||||
|
||||
void SetCallbacks(const JoyconCallbacks& callbacks);
|
||||
|
||||
// Returns device type from hidapi handle
|
||||
static DriverResult GetDeviceType(SDL_hid_device_info* device_info,
|
||||
ControllerType& controller_type);
|
||||
|
||||
// Returns serial number from hidapi handle
|
||||
static DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
|
||||
SerialNumber& serial_number);
|
||||
|
||||
private:
|
||||
struct SupportedFeatures {
|
||||
bool passive{};
|
||||
bool hidbus{};
|
||||
bool irs{};
|
||||
bool motion{};
|
||||
bool nfc{};
|
||||
bool vibration{};
|
||||
};
|
||||
|
||||
/// Main thread, actively request new data from the handle
|
||||
void InputThread(std::stop_token stop_token);
|
||||
|
||||
/// Called everytime a valid package arrives
|
||||
void OnNewData(std::span<u8> buffer);
|
||||
|
||||
/// Updates device configuration to enable or disable features
|
||||
DriverResult SetPollingMode();
|
||||
|
||||
/// Returns true if input thread is valid and doesn't need to be stopped
|
||||
bool IsInputThreadValid() const;
|
||||
|
||||
/// Returns true if the data should be interpreted. Otherwise the error counter is incremented
|
||||
bool IsPayloadCorrect(int status, std::span<const u8> buffer);
|
||||
|
||||
/// Returns a list of supported features that can be enabled on this device
|
||||
SupportedFeatures GetSupportedFeatures();
|
||||
|
||||
// Protocol Features
|
||||
std::unique_ptr<CalibrationProtocol> calibration_protocol;
|
||||
std::unique_ptr<GenericProtocol> generic_protocol;
|
||||
std::unique_ptr<IrsProtocol> irs_protocol;
|
||||
std::unique_ptr<NfcProtocol> nfc_protocol;
|
||||
std::unique_ptr<JoyconPoller> joycon_poller;
|
||||
std::unique_ptr<RingConProtocol> ring_protocol;
|
||||
std::unique_ptr<RumbleProtocol> rumble_protocol;
|
||||
|
||||
// Connection status
|
||||
std::atomic<bool> is_connected{};
|
||||
u64 delta_time;
|
||||
std::size_t error_counter{};
|
||||
std::shared_ptr<JoyconHandle> hidapi_handle;
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||
|
||||
// External device status
|
||||
bool starlink_connected{};
|
||||
bool ring_connected{};
|
||||
bool amiibo_detected{};
|
||||
bool is_ring_disabled_by_irs{};
|
||||
|
||||
// Harware configuration
|
||||
u8 leds{};
|
||||
ReportMode mode{};
|
||||
bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
|
||||
bool hidbus_enabled{}; // External device support
|
||||
bool irs_enabled{}; // Infrared camera input
|
||||
bool motion_enabled{}; // Enables motion input
|
||||
bool nfc_enabled{}; // Enables Amiibo detection
|
||||
bool vibration_enabled{}; // Allows vibrations
|
||||
|
||||
// Calibration data
|
||||
GyroSensitivity gyro_sensitivity{};
|
||||
GyroPerformance gyro_performance{};
|
||||
AccelerometerSensitivity accelerometer_sensitivity{};
|
||||
AccelerometerPerformance accelerometer_performance{};
|
||||
JoyStickCalibration left_stick_calibration{};
|
||||
JoyStickCalibration right_stick_calibration{};
|
||||
MotionCalibration motion_calibration{};
|
||||
RingCalibration ring_calibration{};
|
||||
|
||||
// Fixed joycon info
|
||||
FirmwareVersion version{};
|
||||
Color color{};
|
||||
std::size_t port{};
|
||||
ControllerType device_type{}; // Device type reported by controller
|
||||
ControllerType handle_device_type{}; // Device type reported by hidapi
|
||||
SerialNumber serial_number{}; // Serial number reported by controller
|
||||
SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
|
||||
SupportedFeatures supported_features{};
|
||||
|
||||
// Thread related
|
||||
mutable std::mutex mutex;
|
||||
std::jthread input_thread;
|
||||
bool input_thread_running{};
|
||||
bool disable_input_thread{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
184
src/input_common/helpers/joycon_protocol/calibration.cpp
Normal file
184
src/input_common/helpers/joycon_protocol/calibration.cpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/calibration.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
|
||||
ScopedSetBlocking sb(this);
|
||||
std::vector<u8> buffer;
|
||||
DriverResult result{DriverResult::Success};
|
||||
calibration = {};
|
||||
|
||||
result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
|
||||
if (has_user_calibration) {
|
||||
result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer);
|
||||
} else {
|
||||
result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
|
||||
calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
|
||||
calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
|
||||
calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
|
||||
calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
|
||||
calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
|
||||
}
|
||||
|
||||
// Nintendo fix for drifting stick
|
||||
// result = ReadSPI(0x60, 0x86 ,buffer, 16);
|
||||
// calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
|
||||
|
||||
// Set a valid default calibration if data is missing
|
||||
ValidateCalibration(calibration);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
|
||||
ScopedSetBlocking sb(this);
|
||||
std::vector<u8> buffer;
|
||||
DriverResult result{DriverResult::Success};
|
||||
calibration = {};
|
||||
|
||||
result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
|
||||
if (has_user_calibration) {
|
||||
result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer);
|
||||
} else {
|
||||
result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
|
||||
calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
|
||||
calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
|
||||
calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
|
||||
calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
|
||||
calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
|
||||
}
|
||||
|
||||
// Nintendo fix for drifting stick
|
||||
// buffer = ReadSPI(0x60, 0x98 , 16);
|
||||
// joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
|
||||
|
||||
// Set a valid default calibration if data is missing
|
||||
ValidateCalibration(calibration);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
|
||||
ScopedSetBlocking sb(this);
|
||||
std::vector<u8> buffer;
|
||||
DriverResult result{DriverResult::Success};
|
||||
calibration = {};
|
||||
|
||||
result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
|
||||
if (has_user_calibration) {
|
||||
result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer);
|
||||
} else {
|
||||
result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
IMUCalibration device_calibration{};
|
||||
memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration));
|
||||
calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0];
|
||||
calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1];
|
||||
calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2];
|
||||
|
||||
calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0];
|
||||
calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1];
|
||||
calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2];
|
||||
|
||||
calibration.gyro[0].offset = device_calibration.gyroscope_offset[0];
|
||||
calibration.gyro[1].offset = device_calibration.gyroscope_offset[1];
|
||||
calibration.gyro[2].offset = device_calibration.gyroscope_offset[2];
|
||||
|
||||
calibration.gyro[0].scale = device_calibration.gyroscope_scale[0];
|
||||
calibration.gyro[1].scale = device_calibration.gyroscope_scale[1];
|
||||
calibration.gyro[2].scale = device_calibration.gyroscope_scale[2];
|
||||
}
|
||||
|
||||
ValidateCalibration(calibration);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
|
||||
s16 current_value) {
|
||||
// TODO: Get default calibration form ring itself
|
||||
if (ring_data_max == 0 && ring_data_min == 0) {
|
||||
ring_data_max = current_value + 800;
|
||||
ring_data_min = current_value - 800;
|
||||
ring_data_default = current_value;
|
||||
}
|
||||
ring_data_max = std::max(ring_data_max, current_value);
|
||||
ring_data_min = std::min(ring_data_min, current_value);
|
||||
calibration = {
|
||||
.default_value = ring_data_default,
|
||||
.max_value = ring_data_max,
|
||||
.min_value = ring_data_min,
|
||||
};
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
|
||||
constexpr u16 DefaultStickCenter{2048};
|
||||
constexpr u16 DefaultStickRange{1740};
|
||||
|
||||
if (calibration.x.center == 0xFFF || calibration.x.center == 0) {
|
||||
calibration.x.center = DefaultStickCenter;
|
||||
}
|
||||
if (calibration.x.max == 0xFFF || calibration.x.max == 0) {
|
||||
calibration.x.max = DefaultStickRange;
|
||||
}
|
||||
if (calibration.x.min == 0xFFF || calibration.x.min == 0) {
|
||||
calibration.x.min = DefaultStickRange;
|
||||
}
|
||||
|
||||
if (calibration.y.center == 0xFFF || calibration.y.center == 0) {
|
||||
calibration.y.center = DefaultStickCenter;
|
||||
}
|
||||
if (calibration.y.max == 0xFFF || calibration.y.max == 0) {
|
||||
calibration.y.max = DefaultStickRange;
|
||||
}
|
||||
if (calibration.y.min == 0xFFF || calibration.y.min == 0) {
|
||||
calibration.y.min = DefaultStickRange;
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
|
||||
for (auto& sensor : calibration.accelerometer) {
|
||||
if (sensor.scale == 0) {
|
||||
sensor.scale = 0x4000;
|
||||
}
|
||||
}
|
||||
for (auto& sensor : calibration.gyro) {
|
||||
if (sensor.scale == 0) {
|
||||
sensor.scale = 0x3be7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
64
src/input_common/helpers/joycon_protocol/calibration.h
Normal file
64
src/input_common/helpers/joycon_protocol/calibration.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
enum class DriverResult;
|
||||
struct JoyStickCalibration;
|
||||
struct IMUCalibration;
|
||||
struct JoyconHandle;
|
||||
} // namespace InputCommon::Joycon
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
/// Driver functions related to retrieving calibration data from the device
|
||||
class CalibrationProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the left stick calibration from memory
|
||||
* @param is_factory_calibration if true factory values will be returned
|
||||
* @returns JoyStickCalibration of the left joystick
|
||||
*/
|
||||
DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the right stick calibration from memory
|
||||
* @param is_factory_calibration if true factory values will be returned
|
||||
* @returns JoyStickCalibration of the right joystick
|
||||
*/
|
||||
DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the motion calibration from memory
|
||||
* @returns ImuCalibration of the motion sensor
|
||||
*/
|
||||
DriverResult GetImuCalibration(MotionCalibration& calibration);
|
||||
|
||||
/**
|
||||
* Calculates on run time the proper calibration of the ring controller
|
||||
* @returns RingCalibration of the ring sensor
|
||||
*/
|
||||
DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
|
||||
|
||||
private:
|
||||
void ValidateCalibration(JoyStickCalibration& calibration);
|
||||
void ValidateCalibration(MotionCalibration& calibration);
|
||||
|
||||
s16 ring_data_max = 0;
|
||||
s16 ring_data_default = 0;
|
||||
s16 ring_data_min = 0;
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
299
src/input_common/helpers/joycon_protocol/common_protocol.cpp
Normal file
299
src/input_common/helpers/joycon_protocol/common_protocol.cpp
Normal file
|
@ -0,0 +1,299 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
|
||||
: hidapi_handle{std::move(hidapi_handle_)} {}
|
||||
|
||||
u8 JoyconCommonProtocol::GetCounter() {
|
||||
hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
|
||||
return hidapi_handle->packet_counter;
|
||||
}
|
||||
|
||||
void JoyconCommonProtocol::SetBlocking() {
|
||||
SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
|
||||
}
|
||||
|
||||
void JoyconCommonProtocol::SetNonBlocking() {
|
||||
SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
|
||||
std::vector<u8> buffer;
|
||||
const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer);
|
||||
controller_type = ControllerType::None;
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
controller_type = static_cast<ControllerType>(buffer[0]);
|
||||
// Fallback to 3rd party pro controllers
|
||||
if (controller_type == ControllerType::None) {
|
||||
controller_type = ControllerType::Pro;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
|
||||
ControllerType controller_type{ControllerType::None};
|
||||
const auto result = GetDeviceType(controller_type);
|
||||
if (result != DriverResult::Success || controller_type == ControllerType::None) {
|
||||
return DriverResult::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
hidapi_handle->handle =
|
||||
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
|
||||
|
||||
if (!hidapi_handle->handle) {
|
||||
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
|
||||
device_info->vendor_id, device_info->product_id);
|
||||
return DriverResult::HandleInUse;
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
|
||||
const std::array<u8, 1> buffer{static_cast<u8>(report_mode)};
|
||||
return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer);
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
|
||||
const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
|
||||
|
||||
if (result == -1) {
|
||||
return DriverResult::ErrorWritingData;
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
|
||||
constexpr int timeout_mili = 66;
|
||||
constexpr int MaxTries = 15;
|
||||
int tries = 0;
|
||||
output.resize(MaxSubCommandResponseSize);
|
||||
|
||||
do {
|
||||
int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
|
||||
MaxSubCommandResponseSize, timeout_mili);
|
||||
|
||||
if (result < 1) {
|
||||
LOG_ERROR(Input, "No response from joycon");
|
||||
}
|
||||
if (tries++ > MaxTries) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
|
||||
|
||||
if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
|
||||
std::vector<u8>& output) {
|
||||
std::vector<u8> local_buffer(MaxResponseSize);
|
||||
|
||||
local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
|
||||
local_buffer[1] = GetCounter();
|
||||
local_buffer[10] = static_cast<u8>(sc);
|
||||
for (std::size_t i = 0; i < buffer.size(); ++i) {
|
||||
local_buffer[11 + i] = buffer[i];
|
||||
}
|
||||
|
||||
auto result = SendData(local_buffer);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = GetSubCommandResponse(sc, output);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
|
||||
std::vector<u8> output;
|
||||
return SendSubCommand(sc, buffer, output);
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
|
||||
std::vector<u8> local_buffer(MaxResponseSize);
|
||||
|
||||
local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
|
||||
local_buffer[1] = GetCounter();
|
||||
local_buffer[10] = static_cast<u8>(sc);
|
||||
for (std::size_t i = 0; i < buffer.size(); ++i) {
|
||||
local_buffer[11 + i] = buffer[i];
|
||||
}
|
||||
|
||||
return SendData(local_buffer);
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
|
||||
std::vector<u8> local_buffer(MaxResponseSize);
|
||||
|
||||
local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
|
||||
local_buffer[1] = GetCounter();
|
||||
|
||||
memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
|
||||
|
||||
return SendData(local_buffer);
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
|
||||
constexpr std::size_t MaxTries = 10;
|
||||
std::size_t tries = 0;
|
||||
std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size};
|
||||
std::vector<u8> local_buffer(size + 20);
|
||||
|
||||
buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
|
||||
buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
|
||||
do {
|
||||
const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tries++ > MaxTries) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
|
||||
|
||||
// Remove header from output
|
||||
output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
|
||||
const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
|
||||
const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
LOG_ERROR(Input, "SendMCUData failed with error {}", result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
|
||||
LOG_DEBUG(Input, "ConfigureMCU");
|
||||
std::array<u8, sizeof(MCUConfig)> config_buffer;
|
||||
memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
|
||||
config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
|
||||
|
||||
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
LOG_ERROR(Input, "Set MCU config failed with error {}", result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
|
||||
std::vector<u8>& output) {
|
||||
const int report_mode = static_cast<u8>(report_mode_);
|
||||
constexpr int TimeoutMili = 200;
|
||||
constexpr int MaxTries = 9;
|
||||
int tries = 0;
|
||||
output.resize(0x170);
|
||||
|
||||
do {
|
||||
int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
|
||||
|
||||
if (result < 1) {
|
||||
LOG_ERROR(Input, "No response from joycon attempt {}", tries);
|
||||
}
|
||||
if (tries++ > MaxTries) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (output[0] != report_mode || output[49] == 0xFF);
|
||||
|
||||
if (output[0] != report_mode || output[49] == 0xFF) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
|
||||
std::span<const u8> buffer,
|
||||
std::vector<u8>& output) {
|
||||
std::vector<u8> local_buffer(MaxResponseSize);
|
||||
|
||||
local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
|
||||
local_buffer[1] = GetCounter();
|
||||
local_buffer[9] = static_cast<u8>(sc);
|
||||
for (std::size_t i = 0; i < buffer.size(); ++i) {
|
||||
local_buffer[10 + i] = buffer[i];
|
||||
}
|
||||
|
||||
auto result = SendData(local_buffer);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = GetMCUDataResponse(report_mode, output);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
|
||||
std::vector<u8> output;
|
||||
constexpr std::size_t MaxTries{8};
|
||||
std::size_t tries{};
|
||||
|
||||
do {
|
||||
const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
|
||||
const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tries++ > MaxTries) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
} while (output[49] != 1 || output[56] != static_cast<u8>(mode));
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
// crc-8-ccitt / polynomial 0x07 look up table
|
||||
constexpr std::array<u8, 256> mcu_crc8_table = {
|
||||
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
|
||||
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
|
||||
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
|
||||
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
|
||||
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
|
||||
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
|
||||
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
|
||||
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
|
||||
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
|
||||
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
|
||||
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
|
||||
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
|
||||
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
|
||||
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
|
||||
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
|
||||
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
|
||||
|
||||
u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
|
||||
u8 crc8 = 0x0;
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
|
||||
}
|
||||
return crc8;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
173
src/input_common/helpers/joycon_protocol/common_protocol.h
Normal file
173
src/input_common/helpers/joycon_protocol/common_protocol.h
Normal file
|
@ -0,0 +1,173 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
/// Joycon driver functions that handle low level communication
|
||||
class JoyconCommonProtocol {
|
||||
public:
|
||||
explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
|
||||
|
||||
/**
|
||||
* Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
|
||||
* data to read before returning.
|
||||
*/
|
||||
void SetBlocking();
|
||||
|
||||
/**
|
||||
* Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
|
||||
* immediately with a value of 0 if there is no data to be read
|
||||
*/
|
||||
void SetNonBlocking();
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon type from device
|
||||
* @returns controller type of the joycon
|
||||
*/
|
||||
DriverResult GetDeviceType(ControllerType& controller_type);
|
||||
|
||||
/**
|
||||
* Verifies and sets the joycon_handle if device is valid
|
||||
* @param device info from the driver
|
||||
* @returns success if the device is valid
|
||||
*/
|
||||
DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
|
||||
|
||||
/**
|
||||
* Sends a request to set the polling mode of the joycon
|
||||
* @param report_mode polling mode to be set
|
||||
*/
|
||||
DriverResult SetReportMode(Joycon::ReportMode report_mode);
|
||||
|
||||
/**
|
||||
* Sends data to the joycon device
|
||||
* @param buffer data to be send
|
||||
*/
|
||||
DriverResult SendData(std::span<const u8> buffer);
|
||||
|
||||
/**
|
||||
* Waits for incoming data of the joycon device that matchs the subcommand
|
||||
* @param sub_command type of data to be returned
|
||||
* @returns a buffer containing the responce
|
||||
*/
|
||||
DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Sends a sub command to the device and waits for it's reply
|
||||
* @param sc sub command to be send
|
||||
* @param buffer data to be send
|
||||
* @returns output buffer containing the responce
|
||||
*/
|
||||
DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Sends a sub command to the device and waits for it's reply and ignores the output
|
||||
* @param sc sub command to be send
|
||||
* @param buffer data to be send
|
||||
*/
|
||||
DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer);
|
||||
|
||||
/**
|
||||
* Sends a mcu command to the device
|
||||
* @param sc sub command to be send
|
||||
* @param buffer data to be send
|
||||
*/
|
||||
DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer);
|
||||
|
||||
/**
|
||||
* Sends vibration data to the joycon
|
||||
* @param buffer data to be send
|
||||
*/
|
||||
DriverResult SendVibrationReport(std::span<const u8> buffer);
|
||||
|
||||
/**
|
||||
* Reads the SPI memory stored on the joycon
|
||||
* @param Initial address location
|
||||
* @param size in bytes to be read
|
||||
* @returns output buffer containing the responce
|
||||
*/
|
||||
DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Enables MCU chip on the joycon
|
||||
* @param enable if true the chip will be enabled
|
||||
*/
|
||||
DriverResult EnableMCU(bool enable);
|
||||
|
||||
/**
|
||||
* Configures the MCU to the correspoinding mode
|
||||
* @param MCUConfig configuration
|
||||
*/
|
||||
DriverResult ConfigureMCU(const MCUConfig& config);
|
||||
|
||||
/**
|
||||
* Waits until there's MCU data available. On timeout returns error
|
||||
* @param report mode of the expected reply
|
||||
* @returns a buffer containing the responce
|
||||
*/
|
||||
DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Sends data to the MCU chip and waits for it's reply
|
||||
* @param report mode of the expected reply
|
||||
* @param sub command to be send
|
||||
* @param buffer data to be send
|
||||
* @returns output buffer containing the responce
|
||||
*/
|
||||
DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
|
||||
std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Wait's until the MCU chip is on the specified mode
|
||||
* @param report mode of the expected reply
|
||||
* @param MCUMode configuration
|
||||
*/
|
||||
DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
|
||||
|
||||
/**
|
||||
* Calculates the checksum from the MCU data
|
||||
* @param buffer containing the data to be send
|
||||
* @param size of the buffer in bytes
|
||||
* @returns byte with the correct checksum
|
||||
*/
|
||||
u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Increments and returns the packet counter of the handle
|
||||
* @param joycon_handle device to send the data
|
||||
* @returns packet counter value
|
||||
*/
|
||||
u8 GetCounter();
|
||||
|
||||
std::shared_ptr<JoyconHandle> hidapi_handle;
|
||||
};
|
||||
|
||||
class ScopedSetBlocking {
|
||||
public:
|
||||
explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} {
|
||||
m_self->SetBlocking();
|
||||
}
|
||||
|
||||
~ScopedSetBlocking() {
|
||||
m_self->SetNonBlocking();
|
||||
}
|
||||
|
||||
private:
|
||||
JoyconCommonProtocol* m_self{};
|
||||
};
|
||||
} // namespace InputCommon::Joycon
|
125
src/input_common/helpers/joycon_protocol/generic_functions.cpp
Normal file
125
src/input_common/helpers/joycon_protocol/generic_functions.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/generic_functions.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult GenericProtocol::EnablePassiveMode() {
|
||||
ScopedSetBlocking sb(this);
|
||||
return SetReportMode(ReportMode::SIMPLE_HID_MODE);
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::EnableActiveMode() {
|
||||
ScopedSetBlocking sb(this);
|
||||
return SetReportMode(ReportMode::STANDARD_FULL_60HZ);
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
|
||||
ScopedSetBlocking sb(this);
|
||||
std::vector<u8> output;
|
||||
|
||||
const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
|
||||
|
||||
device_info = {};
|
||||
if (result == DriverResult::Success) {
|
||||
memcpy(&device_info, output.data(), sizeof(DeviceInfo));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) {
|
||||
return GetDeviceType(controller_type);
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::EnableImu(bool enable) {
|
||||
ScopedSetBlocking sb(this);
|
||||
const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
|
||||
return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
|
||||
AccelerometerSensitivity asen,
|
||||
AccelerometerPerformance afrec) {
|
||||
ScopedSetBlocking sb(this);
|
||||
const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
|
||||
static_cast<u8>(gfrec), static_cast<u8>(afrec)};
|
||||
return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer);
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetBattery(u32& battery_level) {
|
||||
// This function is meant to request the high resolution battery status
|
||||
battery_level = 0;
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetColor(Color& color) {
|
||||
ScopedSetBlocking sb(this);
|
||||
std::vector<u8> buffer;
|
||||
const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
|
||||
|
||||
color = {};
|
||||
if (result == DriverResult::Success) {
|
||||
color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
|
||||
color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
|
||||
color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]);
|
||||
color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
|
||||
ScopedSetBlocking sb(this);
|
||||
std::vector<u8> buffer;
|
||||
const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
|
||||
|
||||
serial_number = {};
|
||||
if (result == DriverResult::Success) {
|
||||
memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetTemperature(u32& temperature) {
|
||||
// Not all devices have temperature sensor
|
||||
temperature = 25;
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
|
||||
DeviceInfo device_info{};
|
||||
|
||||
const auto result = GetDeviceInfo(device_info);
|
||||
version = device_info.firmware;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetHomeLight() {
|
||||
ScopedSetBlocking sb(this);
|
||||
static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
|
||||
return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer);
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetLedBusy() {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetLedPattern(u8 leds) {
|
||||
ScopedSetBlocking sb(this);
|
||||
const std::array<u8, 1> buffer{leds};
|
||||
return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer);
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
|
||||
return SetLedPattern(static_cast<u8>(leds << 4));
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
108
src/input_common/helpers/joycon_protocol/generic_functions.h
Normal file
108
src/input_common/helpers/joycon_protocol/generic_functions.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
/// Joycon driver functions that easily implemented
|
||||
class GenericProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
/// Enables passive mode. This mode only sends button data on change. Sticks will return digital
|
||||
/// data instead of analog. Motion will be disabled
|
||||
DriverResult EnablePassiveMode();
|
||||
|
||||
/// Enables active mode. This mode will return the current status every 5-15ms
|
||||
DriverResult EnableActiveMode();
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon firmware and mac from handle
|
||||
* @returns controller device info
|
||||
*/
|
||||
DriverResult GetDeviceInfo(DeviceInfo& controller_type);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon type from handle
|
||||
* @returns controller type of the joycon
|
||||
*/
|
||||
DriverResult GetControllerType(ControllerType& controller_type);
|
||||
|
||||
/**
|
||||
* Enables motion input
|
||||
* @param enable if true motion data will be enabled
|
||||
*/
|
||||
DriverResult EnableImu(bool enable);
|
||||
|
||||
/**
|
||||
* Configures the motion sensor with the specified parameters
|
||||
* @param gsen gyroscope sensor sensitvity in degrees per second
|
||||
* @param gfrec gyroscope sensor frequency in hertz
|
||||
* @param asen accelerometer sensitivity in G force
|
||||
* @param afrec accelerometer frequency in hertz
|
||||
*/
|
||||
DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
|
||||
AccelerometerSensitivity asen, AccelerometerPerformance afrec);
|
||||
|
||||
/**
|
||||
* Request battery level from the device
|
||||
* @returns battery level
|
||||
*/
|
||||
DriverResult GetBattery(u32& battery_level);
|
||||
|
||||
/**
|
||||
* Request joycon colors from the device
|
||||
* @returns colors of the body and buttons
|
||||
*/
|
||||
DriverResult GetColor(Color& color);
|
||||
|
||||
/**
|
||||
* Request joycon serial number from the device
|
||||
* @returns 16 byte serial number
|
||||
*/
|
||||
DriverResult GetSerialNumber(SerialNumber& serial_number);
|
||||
|
||||
/**
|
||||
* Request joycon serial number from the device
|
||||
* @returns 16 byte serial number
|
||||
*/
|
||||
DriverResult GetTemperature(u32& temperature);
|
||||
|
||||
/**
|
||||
* Request joycon serial number from the device
|
||||
* @returns 16 byte serial number
|
||||
*/
|
||||
DriverResult GetVersionNumber(FirmwareVersion& version);
|
||||
|
||||
/**
|
||||
* Sets home led behaviour
|
||||
*/
|
||||
DriverResult SetHomeLight();
|
||||
|
||||
/**
|
||||
* Sets home led into a slow breathing state
|
||||
*/
|
||||
DriverResult SetLedBusy();
|
||||
|
||||
/**
|
||||
* Sets the 4 player leds on the joycon on a solid state
|
||||
* @params bit flag containing the led state
|
||||
*/
|
||||
DriverResult SetLedPattern(u8 leds);
|
||||
|
||||
/**
|
||||
* Sets the 4 player leds on the joycon on a blinking state
|
||||
* @returns bit flag containing the led state
|
||||
*/
|
||||
DriverResult SetLedBlinkPattern(u8 leds);
|
||||
};
|
||||
} // namespace InputCommon::Joycon
|
298
src/input_common/helpers/joycon_protocol/irs.cpp
Normal file
298
src/input_common/helpers/joycon_protocol/irs.cpp
Normal file
|
@ -0,0 +1,298 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/irs.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult IrsProtocol::EnableIrs() {
|
||||
LOG_INFO(Input, "Enable IRS");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(true);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
const MCUConfig config{
|
||||
.command = MCUCommand::ConfigureMCU,
|
||||
.sub_command = MCUSubCommand::SetMCUMode,
|
||||
.mode = MCUMode::IR,
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
result = ConfigureMCU(config);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = ConfigureIrs();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WriteRegistersStep1();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WriteRegistersStep2();
|
||||
}
|
||||
|
||||
is_enabled = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult IrsProtocol::DisableIrs() {
|
||||
LOG_DEBUG(Input, "Disable IRS");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(false);
|
||||
}
|
||||
|
||||
is_enabled = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) {
|
||||
irs_mode = mode;
|
||||
switch (format) {
|
||||
case IrsResolution::Size320x240:
|
||||
resolution_code = IrsResolutionCode::Size320x240;
|
||||
fragments = IrsFragments::Size320x240;
|
||||
resolution = IrsResolution::Size320x240;
|
||||
break;
|
||||
case IrsResolution::Size160x120:
|
||||
resolution_code = IrsResolutionCode::Size160x120;
|
||||
fragments = IrsFragments::Size160x120;
|
||||
resolution = IrsResolution::Size160x120;
|
||||
break;
|
||||
case IrsResolution::Size80x60:
|
||||
resolution_code = IrsResolutionCode::Size80x60;
|
||||
fragments = IrsFragments::Size80x60;
|
||||
resolution = IrsResolution::Size80x60;
|
||||
break;
|
||||
case IrsResolution::Size20x15:
|
||||
resolution_code = IrsResolutionCode::Size20x15;
|
||||
fragments = IrsFragments::Size20x15;
|
||||
resolution = IrsResolution::Size20x15;
|
||||
break;
|
||||
case IrsResolution::Size40x30:
|
||||
default:
|
||||
resolution_code = IrsResolutionCode::Size40x30;
|
||||
fragments = IrsFragments::Size40x30;
|
||||
resolution = IrsResolution::Size40x30;
|
||||
break;
|
||||
}
|
||||
|
||||
// Restart feature
|
||||
if (is_enabled) {
|
||||
DisableIrs();
|
||||
return EnableIrs();
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) {
|
||||
const u8 next_packet_fragment =
|
||||
static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1));
|
||||
|
||||
if (buffer[0] == 0x31 && buffer[49] == 0x03) {
|
||||
u8 new_packet_fragment = buffer[52];
|
||||
if (new_packet_fragment == next_packet_fragment) {
|
||||
packet_fragment = next_packet_fragment;
|
||||
memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300);
|
||||
|
||||
return RequestFrame(packet_fragment);
|
||||
}
|
||||
|
||||
if (new_packet_fragment == packet_fragment) {
|
||||
return RequestFrame(packet_fragment);
|
||||
}
|
||||
|
||||
return ResendFrame(next_packet_fragment);
|
||||
}
|
||||
|
||||
return RequestFrame(packet_fragment);
|
||||
}
|
||||
|
||||
DriverResult IrsProtocol::ConfigureIrs() {
|
||||
LOG_DEBUG(Input, "Configure IRS");
|
||||
constexpr std::size_t max_tries = 28;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
const IrsConfigure irs_configuration{
|
||||
.command = MCUCommand::ConfigureIR,
|
||||
.sub_command = MCUSubCommand::SetDeviceMode,
|
||||
.irs_mode = IrsMode::ImageTransfer,
|
||||
.number_of_fragments = fragments,
|
||||
.mcu_major_version = 0x0500,
|
||||
.mcu_minor_version = 0x1800,
|
||||
.crc = {},
|
||||
};
|
||||
buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
|
||||
|
||||
std::array<u8, sizeof(IrsConfigure)> request_data{};
|
||||
memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
do {
|
||||
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ >= max_tries) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
} while (output[15] != 0x0b);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult IrsProtocol::WriteRegistersStep1() {
|
||||
LOG_DEBUG(Input, "WriteRegistersStep1");
|
||||
DriverResult result{DriverResult::Success};
|
||||
constexpr std::size_t max_tries = 28;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
const IrsWriteRegisters irs_registers{
|
||||
.command = MCUCommand::ConfigureIR,
|
||||
.sub_command = MCUSubCommand::WriteDeviceRegisters,
|
||||
.number_of_registers = 0x9,
|
||||
.registers =
|
||||
{
|
||||
IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)},
|
||||
{IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)},
|
||||
{IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)},
|
||||
{IrRegistersAddress::ExposureTime, 0x00},
|
||||
{IrRegistersAddress::Leds, static_cast<u8>(leds)},
|
||||
{IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)},
|
||||
{IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)},
|
||||
{IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)},
|
||||
{IrRegistersAddress::WhitePixelThreshold, 0xc8},
|
||||
},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
|
||||
memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
|
||||
std::array<u8, 38> mcu_request{0x02};
|
||||
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
|
||||
mcu_request[37] = 0xFF;
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
do {
|
||||
result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
|
||||
|
||||
// First time we need to set the report mode
|
||||
if (result == DriverResult::Success && tries == 0) {
|
||||
result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
|
||||
}
|
||||
if (result == DriverResult::Success && tries == 0) {
|
||||
GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
|
||||
}
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ >= max_tries) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
} while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult IrsProtocol::WriteRegistersStep2() {
|
||||
LOG_DEBUG(Input, "WriteRegistersStep2");
|
||||
constexpr std::size_t max_tries = 28;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
const IrsWriteRegisters irs_registers{
|
||||
.command = MCUCommand::ConfigureIR,
|
||||
.sub_command = MCUSubCommand::WriteDeviceRegisters,
|
||||
.number_of_registers = 0x8,
|
||||
.registers =
|
||||
{
|
||||
IrsRegister{IrRegistersAddress::LedIntensitiyMSB,
|
||||
static_cast<u8>(led_intensity >> 8)},
|
||||
{IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)},
|
||||
{IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)},
|
||||
{IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)},
|
||||
{IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)},
|
||||
{IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)},
|
||||
{IrRegistersAddress::UpdateTime, 0x2d},
|
||||
{IrRegistersAddress::FinalizeConfig, 0x01},
|
||||
},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
|
||||
memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
do {
|
||||
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ >= max_tries) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
} while (output[15] != 0x13 && output[15] != 0x23);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult IrsProtocol::RequestFrame(u8 frame) {
|
||||
std::array<u8, 38> mcu_request{};
|
||||
mcu_request[3] = frame;
|
||||
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
|
||||
mcu_request[37] = 0xFF;
|
||||
return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
|
||||
}
|
||||
|
||||
DriverResult IrsProtocol::ResendFrame(u8 frame) {
|
||||
std::array<u8, 38> mcu_request{};
|
||||
mcu_request[1] = 0x1;
|
||||
mcu_request[2] = frame;
|
||||
mcu_request[3] = 0x0;
|
||||
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
|
||||
mcu_request[37] = 0xFF;
|
||||
return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
|
||||
}
|
||||
|
||||
std::vector<u8> IrsProtocol::GetImage() const {
|
||||
return buf_image;
|
||||
}
|
||||
|
||||
IrsResolution IrsProtocol::GetIrsFormat() const {
|
||||
return resolution;
|
||||
}
|
||||
|
||||
bool IrsProtocol::IsEnabled() const {
|
||||
return is_enabled;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
63
src/input_common/helpers/joycon_protocol/irs.h
Normal file
63
src/input_common/helpers/joycon_protocol/irs.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
class IrsProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
DriverResult EnableIrs();
|
||||
|
||||
DriverResult DisableIrs();
|
||||
|
||||
DriverResult SetIrsConfig(IrsMode mode, IrsResolution format);
|
||||
|
||||
DriverResult RequestImage(std::span<u8> buffer);
|
||||
|
||||
std::vector<u8> GetImage() const;
|
||||
|
||||
IrsResolution GetIrsFormat() const;
|
||||
|
||||
bool IsEnabled() const;
|
||||
|
||||
private:
|
||||
DriverResult ConfigureIrs();
|
||||
|
||||
DriverResult WriteRegistersStep1();
|
||||
DriverResult WriteRegistersStep2();
|
||||
|
||||
DriverResult RequestFrame(u8 frame);
|
||||
DriverResult ResendFrame(u8 frame);
|
||||
|
||||
IrsMode irs_mode{IrsMode::ImageTransfer};
|
||||
IrsResolution resolution{IrsResolution::Size40x30};
|
||||
IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30};
|
||||
IrsFragments fragments{IrsFragments::Size40x30};
|
||||
IrLeds leds{IrLeds::BrightAndDim};
|
||||
IrExLedFilter led_filter{IrExLedFilter::Enabled};
|
||||
IrImageFlip image_flip{IrImageFlip::Normal};
|
||||
u8 digital_gain{0x01};
|
||||
u16 exposure{0x2490};
|
||||
u16 led_intensity{0x0f10};
|
||||
u32 denoise{0x012344};
|
||||
|
||||
u8 packet_fragment{};
|
||||
std::vector<u8> buf_image; // 8bpp greyscale image.
|
||||
|
||||
bool is_enabled{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
612
src/input_common/helpers/joycon_protocol/joycon_types.h
Normal file
612
src/input_common/helpers/joycon_protocol/joycon_types.h
Normal file
|
@ -0,0 +1,612 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <SDL_hidapi.h>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
constexpr u32 MaxErrorCount = 50;
|
||||
constexpr u32 MaxBufferSize = 368;
|
||||
constexpr u32 MaxResponseSize = 49;
|
||||
constexpr u32 MaxSubCommandResponseSize = 64;
|
||||
constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
|
||||
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
using SerialNumber = std::array<u8, 15>;
|
||||
|
||||
enum class ControllerType {
|
||||
None,
|
||||
Left,
|
||||
Right,
|
||||
Pro,
|
||||
Grip,
|
||||
Dual,
|
||||
};
|
||||
|
||||
enum class PadAxes {
|
||||
LeftStickX,
|
||||
LeftStickY,
|
||||
RightStickX,
|
||||
RightStickY,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class PadMotion {
|
||||
LeftMotion,
|
||||
RightMotion,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class PadButton : u32 {
|
||||
Down = 0x000001,
|
||||
Up = 0x000002,
|
||||
Right = 0x000004,
|
||||
Left = 0x000008,
|
||||
LeftSR = 0x000010,
|
||||
LeftSL = 0x000020,
|
||||
L = 0x000040,
|
||||
ZL = 0x000080,
|
||||
Y = 0x000100,
|
||||
X = 0x000200,
|
||||
B = 0x000400,
|
||||
A = 0x000800,
|
||||
RightSR = 0x001000,
|
||||
RightSL = 0x002000,
|
||||
R = 0x004000,
|
||||
ZR = 0x008000,
|
||||
Minus = 0x010000,
|
||||
Plus = 0x020000,
|
||||
StickR = 0x040000,
|
||||
StickL = 0x080000,
|
||||
Home = 0x100000,
|
||||
Capture = 0x200000,
|
||||
};
|
||||
|
||||
enum class PasivePadButton : u32 {
|
||||
Down_A = 0x0001,
|
||||
Right_X = 0x0002,
|
||||
Left_B = 0x0004,
|
||||
Up_Y = 0x0008,
|
||||
SL = 0x0010,
|
||||
SR = 0x0020,
|
||||
Minus = 0x0100,
|
||||
Plus = 0x0200,
|
||||
StickL = 0x0400,
|
||||
StickR = 0x0800,
|
||||
Home = 0x1000,
|
||||
Capture = 0x2000,
|
||||
L_R = 0x4000,
|
||||
ZL_ZR = 0x8000,
|
||||
};
|
||||
|
||||
enum class OutputReport : u8 {
|
||||
RUMBLE_AND_SUBCMD = 0x01,
|
||||
FW_UPDATE_PKT = 0x03,
|
||||
RUMBLE_ONLY = 0x10,
|
||||
MCU_DATA = 0x11,
|
||||
USB_CMD = 0x80,
|
||||
};
|
||||
|
||||
enum class InputReport : u8 {
|
||||
SUBCMD_REPLY = 0x21,
|
||||
STANDARD_FULL_60HZ = 0x30,
|
||||
NFC_IR_MODE_60HZ = 0x31,
|
||||
SIMPLE_HID_MODE = 0x3F,
|
||||
INPUT_USB_RESPONSE = 0x81,
|
||||
};
|
||||
|
||||
enum class FeatureReport : u8 {
|
||||
Last_SUBCMD = 0x02,
|
||||
OTA_GW_UPGRADE = 0x70,
|
||||
SETUP_MEM_READ = 0x71,
|
||||
MEM_READ = 0x72,
|
||||
ERASE_MEM_SECTOR = 0x73,
|
||||
MEM_WRITE = 0x74,
|
||||
LAUNCH = 0x75,
|
||||
};
|
||||
|
||||
enum class SubCommand : u8 {
|
||||
STATE = 0x00,
|
||||
MANUAL_BT_PAIRING = 0x01,
|
||||
REQ_DEV_INFO = 0x02,
|
||||
SET_REPORT_MODE = 0x03,
|
||||
TRIGGERS_ELAPSED = 0x04,
|
||||
GET_PAGE_LIST_STATE = 0x05,
|
||||
SET_HCI_STATE = 0x06,
|
||||
RESET_PAIRING_INFO = 0x07,
|
||||
LOW_POWER_MODE = 0x08,
|
||||
SPI_FLASH_READ = 0x10,
|
||||
SPI_FLASH_WRITE = 0x11,
|
||||
RESET_MCU = 0x20,
|
||||
SET_MCU_CONFIG = 0x21,
|
||||
SET_MCU_STATE = 0x22,
|
||||
SET_PLAYER_LIGHTS = 0x30,
|
||||
GET_PLAYER_LIGHTS = 0x31,
|
||||
SET_HOME_LIGHT = 0x38,
|
||||
ENABLE_IMU = 0x40,
|
||||
SET_IMU_SENSITIVITY = 0x41,
|
||||
WRITE_IMU_REG = 0x42,
|
||||
READ_IMU_REG = 0x43,
|
||||
ENABLE_VIBRATION = 0x48,
|
||||
GET_REGULATED_VOLTAGE = 0x50,
|
||||
SET_EXTERNAL_CONFIG = 0x58,
|
||||
UNKNOWN_RINGCON = 0x59,
|
||||
UNKNOWN_RINGCON2 = 0x5A,
|
||||
UNKNOWN_RINGCON3 = 0x5C,
|
||||
};
|
||||
|
||||
enum class UsbSubCommand : u8 {
|
||||
CONN_STATUS = 0x01,
|
||||
HADSHAKE = 0x02,
|
||||
BAUDRATE_3M = 0x03,
|
||||
NO_TIMEOUT = 0x04,
|
||||
EN_TIMEOUT = 0x05,
|
||||
RESET = 0x06,
|
||||
PRE_HANDSHAKE = 0x91,
|
||||
SEND_UART = 0x92,
|
||||
};
|
||||
|
||||
enum class CalMagic : u8 {
|
||||
USR_MAGIC_0 = 0xB2,
|
||||
USR_MAGIC_1 = 0xA1,
|
||||
USRR_MAGI_SIZE = 2,
|
||||
};
|
||||
|
||||
enum class CalAddr {
|
||||
SERIAL_NUMBER = 0X6000,
|
||||
DEVICE_TYPE = 0X6012,
|
||||
COLOR_EXIST = 0X601B,
|
||||
FACT_LEFT_DATA = 0X603d,
|
||||
FACT_RIGHT_DATA = 0X6046,
|
||||
COLOR_DATA = 0X6050,
|
||||
FACT_IMU_DATA = 0X6020,
|
||||
USER_LEFT_MAGIC = 0X8010,
|
||||
USER_LEFT_DATA = 0X8012,
|
||||
USER_RIGHT_MAGIC = 0X801B,
|
||||
USER_RIGHT_DATA = 0X801D,
|
||||
USER_IMU_MAGIC = 0X8026,
|
||||
USER_IMU_DATA = 0X8028,
|
||||
};
|
||||
|
||||
enum class ReportMode : u8 {
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
|
||||
ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
|
||||
MCU_UPDATE_STATE = 0x23,
|
||||
STANDARD_FULL_60HZ = 0x30,
|
||||
NFC_IR_MODE_60HZ = 0x31,
|
||||
SIMPLE_HID_MODE = 0x3F,
|
||||
};
|
||||
|
||||
enum class GyroSensitivity : u8 {
|
||||
DPS250,
|
||||
DPS500,
|
||||
DPS1000,
|
||||
DPS2000, // Default
|
||||
};
|
||||
|
||||
enum class AccelerometerSensitivity : u8 {
|
||||
G8, // Default
|
||||
G4,
|
||||
G2,
|
||||
G16,
|
||||
};
|
||||
|
||||
enum class GyroPerformance : u8 {
|
||||
HZ833,
|
||||
HZ208, // Default
|
||||
};
|
||||
|
||||
enum class AccelerometerPerformance : u8 {
|
||||
HZ200,
|
||||
HZ100, // Default
|
||||
};
|
||||
|
||||
enum class MCUCommand : u8 {
|
||||
ConfigureMCU = 0x21,
|
||||
ConfigureIR = 0x23,
|
||||
};
|
||||
|
||||
enum class MCUSubCommand : u8 {
|
||||
SetMCUMode = 0x0,
|
||||
SetDeviceMode = 0x1,
|
||||
ReadDeviceMode = 0x02,
|
||||
WriteDeviceRegisters = 0x4,
|
||||
};
|
||||
|
||||
enum class MCUMode : u8 {
|
||||
Suspend = 0,
|
||||
Standby = 1,
|
||||
Ringcon = 3,
|
||||
NFC = 4,
|
||||
IR = 5,
|
||||
MaybeFWUpdate = 6,
|
||||
};
|
||||
|
||||
enum class MCURequest : u8 {
|
||||
GetMCUStatus = 1,
|
||||
GetNFCData = 2,
|
||||
GetIRData = 3,
|
||||
};
|
||||
|
||||
enum class MCUReport : u8 {
|
||||
Empty = 0x00,
|
||||
StateReport = 0x01,
|
||||
IRData = 0x03,
|
||||
BusyInitializing = 0x0b,
|
||||
IRStatus = 0x13,
|
||||
IRRegisters = 0x1b,
|
||||
NFCState = 0x2a,
|
||||
NFCReadData = 0x3a,
|
||||
EmptyAwaitingCmd = 0xff,
|
||||
};
|
||||
|
||||
enum class MCUPacketFlag : u8 {
|
||||
MorePacketsRemaining = 0x00,
|
||||
LastCommandPacket = 0x08,
|
||||
};
|
||||
|
||||
enum class NFCReadCommand : u8 {
|
||||
CancelAll = 0x00,
|
||||
StartPolling = 0x01,
|
||||
StopPolling = 0x02,
|
||||
StartWaitingRecieve = 0x04,
|
||||
Ntag = 0x06,
|
||||
Mifare = 0x0F,
|
||||
};
|
||||
|
||||
enum class NFCTagType : u8 {
|
||||
AllTags = 0x00,
|
||||
Ntag215 = 0x01,
|
||||
};
|
||||
|
||||
enum class NFCPages {
|
||||
Block0 = 0,
|
||||
Block45 = 45,
|
||||
Block135 = 135,
|
||||
Block231 = 231,
|
||||
};
|
||||
|
||||
enum class NFCStatus : u8 {
|
||||
LastPackage = 0x04,
|
||||
TagLost = 0x07,
|
||||
};
|
||||
|
||||
enum class IrsMode : u8 {
|
||||
None = 0x02,
|
||||
Moment = 0x03,
|
||||
Dpd = 0x04,
|
||||
Clustering = 0x06,
|
||||
ImageTransfer = 0x07,
|
||||
Silhouette = 0x08,
|
||||
TeraImage = 0x09,
|
||||
SilhouetteTeraImage = 0x0A,
|
||||
};
|
||||
|
||||
enum class IrsResolution {
|
||||
Size320x240,
|
||||
Size160x120,
|
||||
Size80x60,
|
||||
Size40x30,
|
||||
Size20x15,
|
||||
None,
|
||||
};
|
||||
|
||||
enum class IrsResolutionCode : u8 {
|
||||
Size320x240 = 0x00, // Full pixel array
|
||||
Size160x120 = 0x50, // Sensor Binning [2 X 2]
|
||||
Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2]
|
||||
Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4]
|
||||
Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4]
|
||||
};
|
||||
|
||||
// Size of image divided by 300
|
||||
enum class IrsFragments : u8 {
|
||||
Size20x15 = 0x00,
|
||||
Size40x30 = 0x03,
|
||||
Size80x60 = 0x0f,
|
||||
Size160x120 = 0x3f,
|
||||
Size320x240 = 0xFF,
|
||||
};
|
||||
|
||||
enum class IrLeds : u8 {
|
||||
BrightAndDim = 0x00,
|
||||
Bright = 0x20,
|
||||
Dim = 0x10,
|
||||
None = 0x30,
|
||||
};
|
||||
|
||||
enum class IrExLedFilter : u8 {
|
||||
Disabled = 0x00,
|
||||
Enabled = 0x03,
|
||||
};
|
||||
|
||||
enum class IrImageFlip : u8 {
|
||||
Normal = 0x00,
|
||||
Inverted = 0x02,
|
||||
};
|
||||
|
||||
enum class IrRegistersAddress : u16 {
|
||||
UpdateTime = 0x0400,
|
||||
FinalizeConfig = 0x0700,
|
||||
LedFilter = 0x0e00,
|
||||
Leds = 0x1000,
|
||||
LedIntensitiyMSB = 0x1100,
|
||||
LedIntensitiyLSB = 0x1200,
|
||||
ImageFlip = 0x2d00,
|
||||
Resolution = 0x2e00,
|
||||
DigitalGainLSB = 0x2e01,
|
||||
DigitalGainMSB = 0x2f01,
|
||||
ExposureLSB = 0x3001,
|
||||
ExposureMSB = 0x3101,
|
||||
ExposureTime = 0x3201,
|
||||
WhitePixelThreshold = 0x4301,
|
||||
DenoiseSmoothing = 0x6701,
|
||||
DenoiseEdge = 0x6801,
|
||||
DenoiseColor = 0x6901,
|
||||
};
|
||||
|
||||
enum class DriverResult {
|
||||
Success,
|
||||
WrongReply,
|
||||
Timeout,
|
||||
UnsupportedControllerType,
|
||||
HandleInUse,
|
||||
ErrorReadingData,
|
||||
ErrorWritingData,
|
||||
NoDeviceDetected,
|
||||
InvalidHandle,
|
||||
NotSupported,
|
||||
Disabled,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct MotionSensorCalibration {
|
||||
s16 offset;
|
||||
s16 scale;
|
||||
};
|
||||
|
||||
struct MotionCalibration {
|
||||
std::array<MotionSensorCalibration, 3> accelerometer;
|
||||
std::array<MotionSensorCalibration, 3> gyro;
|
||||
};
|
||||
|
||||
// Basic motion data containing data from the sensors and a timestamp in microseconds
|
||||
struct MotionData {
|
||||
float gyro_x{};
|
||||
float gyro_y{};
|
||||
float gyro_z{};
|
||||
float accel_x{};
|
||||
float accel_y{};
|
||||
float accel_z{};
|
||||
u64 delta_timestamp{};
|
||||
};
|
||||
|
||||
struct JoyStickAxisCalibration {
|
||||
u16 max{1};
|
||||
u16 min{1};
|
||||
u16 center{0};
|
||||
};
|
||||
|
||||
struct JoyStickCalibration {
|
||||
JoyStickAxisCalibration x;
|
||||
JoyStickAxisCalibration y;
|
||||
};
|
||||
|
||||
struct RingCalibration {
|
||||
s16 default_value;
|
||||
s16 max_value;
|
||||
s16 min_value;
|
||||
};
|
||||
|
||||
struct Color {
|
||||
u32 body;
|
||||
u32 buttons;
|
||||
u32 left_grip;
|
||||
u32 right_grip;
|
||||
};
|
||||
|
||||
struct Battery {
|
||||
union {
|
||||
u8 raw{};
|
||||
|
||||
BitField<0, 4, u8> unknown;
|
||||
BitField<4, 1, u8> charging;
|
||||
BitField<5, 3, u8> status;
|
||||
};
|
||||
};
|
||||
|
||||
struct VibrationValue {
|
||||
f32 low_amplitude;
|
||||
f32 low_frequency;
|
||||
f32 high_amplitude;
|
||||
f32 high_frequency;
|
||||
};
|
||||
|
||||
struct JoyconHandle {
|
||||
SDL_hid_device* handle = nullptr;
|
||||
u8 packet_counter{};
|
||||
};
|
||||
|
||||
struct MCUConfig {
|
||||
MCUCommand command;
|
||||
MCUSubCommand sub_command;
|
||||
MCUMode mode;
|
||||
INSERT_PADDING_BYTES(0x22);
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct InputReportPassive {
|
||||
InputReport report_mode;
|
||||
u16 button_input;
|
||||
u8 stick_state;
|
||||
std::array<u8, 10> unknown_data;
|
||||
};
|
||||
static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
|
||||
|
||||
struct InputReportActive {
|
||||
InputReport report_mode;
|
||||
u8 packet_id;
|
||||
Battery battery_status;
|
||||
std::array<u8, 3> button_input;
|
||||
std::array<u8, 3> left_stick_state;
|
||||
std::array<u8, 3> right_stick_state;
|
||||
u8 vibration_code;
|
||||
std::array<s16, 6 * 2> motion_input;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
s16 ring_input;
|
||||
};
|
||||
static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
|
||||
|
||||
struct InputReportNfcIr {
|
||||
InputReport report_mode;
|
||||
u8 packet_id;
|
||||
Battery battery_status;
|
||||
std::array<u8, 3> button_input;
|
||||
std::array<u8, 3> left_stick_state;
|
||||
std::array<u8, 3> right_stick_state;
|
||||
u8 vibration_code;
|
||||
std::array<s16, 6 * 2> motion_input;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
};
|
||||
static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
|
||||
#pragma pack(pop)
|
||||
|
||||
struct IMUCalibration {
|
||||
std::array<s16, 3> accelerometer_offset;
|
||||
std::array<s16, 3> accelerometer_scale;
|
||||
std::array<s16, 3> gyroscope_offset;
|
||||
std::array<s16, 3> gyroscope_scale;
|
||||
};
|
||||
static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
|
||||
|
||||
struct NFCReadBlock {
|
||||
u8 start;
|
||||
u8 end;
|
||||
};
|
||||
static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
|
||||
|
||||
struct NFCReadBlockCommand {
|
||||
u8 block_count{};
|
||||
std::array<NFCReadBlock, 4> blocks{};
|
||||
};
|
||||
static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
|
||||
|
||||
struct NFCReadCommandData {
|
||||
u8 unknown;
|
||||
u8 uuid_length;
|
||||
u8 unknown_2;
|
||||
std::array<u8, 6> uid;
|
||||
NFCTagType tag_type;
|
||||
NFCReadBlockCommand read_block;
|
||||
};
|
||||
static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
|
||||
|
||||
struct NFCPollingCommandData {
|
||||
u8 enable_mifare;
|
||||
u8 unknown_1;
|
||||
u8 unknown_2;
|
||||
u8 unknown_3;
|
||||
u8 unknown_4;
|
||||
};
|
||||
static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
|
||||
|
||||
struct NFCRequestState {
|
||||
MCUSubCommand sub_command;
|
||||
NFCReadCommand command_argument;
|
||||
u8 packet_id;
|
||||
INSERT_PADDING_BYTES(0x1);
|
||||
MCUPacketFlag packet_flag;
|
||||
u8 data_length;
|
||||
union {
|
||||
std::array<u8, 0x1F> raw_data;
|
||||
NFCReadCommandData nfc_read;
|
||||
NFCPollingCommandData nfc_polling;
|
||||
};
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
|
||||
|
||||
struct IrsConfigure {
|
||||
MCUCommand command;
|
||||
MCUSubCommand sub_command;
|
||||
IrsMode irs_mode;
|
||||
IrsFragments number_of_fragments;
|
||||
u16 mcu_major_version;
|
||||
u16 mcu_minor_version;
|
||||
INSERT_PADDING_BYTES(0x1D);
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct IrsRegister {
|
||||
IrRegistersAddress address;
|
||||
u8 value;
|
||||
};
|
||||
static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
|
||||
|
||||
struct IrsWriteRegisters {
|
||||
MCUCommand command;
|
||||
MCUSubCommand sub_command;
|
||||
u8 number_of_registers;
|
||||
std::array<IrsRegister, 9> registers;
|
||||
INSERT_PADDING_BYTES(0x7);
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
|
||||
#pragma pack(pop)
|
||||
|
||||
struct FirmwareVersion {
|
||||
u8 major;
|
||||
u8 minor;
|
||||
};
|
||||
static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
|
||||
|
||||
struct DeviceInfo {
|
||||
FirmwareVersion firmware;
|
||||
MacAddress mac_address;
|
||||
};
|
||||
static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
|
||||
|
||||
struct MotionStatus {
|
||||
bool is_enabled;
|
||||
u64 delta_time;
|
||||
GyroSensitivity gyro_sensitivity;
|
||||
AccelerometerSensitivity accelerometer_sensitivity;
|
||||
};
|
||||
|
||||
struct RingStatus {
|
||||
bool is_enabled;
|
||||
s16 default_value;
|
||||
s16 max_value;
|
||||
s16 min_value;
|
||||
};
|
||||
|
||||
struct JoyconCallbacks {
|
||||
std::function<void(Battery)> on_battery_data;
|
||||
std::function<void(Color)> on_color_data;
|
||||
std::function<void(int, bool)> on_button_data;
|
||||
std::function<void(int, f32)> on_stick_data;
|
||||
std::function<void(int, const MotionData&)> on_motion_data;
|
||||
std::function<void(f32)> on_ring_data;
|
||||
std::function<void(const std::vector<u8>&)> on_amiibo_data;
|
||||
std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
400
src/input_common/helpers/joycon_protocol/nfc.cpp
Normal file
400
src/input_common/helpers/joycon_protocol/nfc.cpp
Normal file
|
@ -0,0 +1,400 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/nfc.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult NfcProtocol::EnableNfc() {
|
||||
LOG_INFO(Input, "Enable NFC");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(true);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
const MCUConfig config{
|
||||
.command = MCUCommand::ConfigureMCU,
|
||||
.sub_command = MCUSubCommand::SetMCUMode,
|
||||
.mode = MCUMode::NFC,
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
result = ConfigureMCU(config);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::DisableNfc() {
|
||||
LOG_DEBUG(Input, "Disable NFC");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(false);
|
||||
}
|
||||
|
||||
is_enabled = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::StartNFCPollingMode() {
|
||||
LOG_DEBUG(Input, "Start NFC pooling Mode");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIsReady();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
is_enabled = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
|
||||
LOG_DEBUG(Input, "Start NFC pooling Mode");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = StartPolling(tag_data);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = ReadTag(tag_data);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIsReady();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = StartPolling(tag_data);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = GetAmiiboData(data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool NfcProtocol::HasAmiibo() {
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = StartPolling(tag_data);
|
||||
}
|
||||
|
||||
return result == DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::WaitUntilNfcIsReady() {
|
||||
constexpr std::size_t timeout_limit = 10;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
do {
|
||||
auto result = SendStartWaitingRecieveRequest(output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ > timeout_limit) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 ||
|
||||
output[56] != 0x00);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
|
||||
LOG_DEBUG(Input, "Start Polling for tag");
|
||||
constexpr std::size_t timeout_limit = 7;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
do {
|
||||
const auto result = SendStartPollingRequest(output);
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ > timeout_limit) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09);
|
||||
|
||||
data.type = output[62];
|
||||
data.uuid.resize(output[64]);
|
||||
memcpy(data.uuid.data(), output.data() + 65, data.uuid.size());
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
|
||||
constexpr std::size_t timeout_limit = 10;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
std::string uuid_string;
|
||||
for (auto& content : data.uuid) {
|
||||
uuid_string += fmt::format(" {:02x}", content);
|
||||
}
|
||||
|
||||
LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
|
||||
|
||||
tries = 0;
|
||||
NFCPages ntag_pages = NFCPages::Block0;
|
||||
// Read Tag data
|
||||
while (true) {
|
||||
auto result = SendReadAmiiboRequest(output, ntag_pages);
|
||||
const auto mcu_report = static_cast<MCUReport>(output[49]);
|
||||
const auto nfc_status = static_cast<NFCStatus>(output[56]);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
|
||||
nfc_status == NFCStatus::TagLost) {
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
|
||||
if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) {
|
||||
if (data.type != 2) {
|
||||
continue;
|
||||
}
|
||||
switch (output[74]) {
|
||||
case 0:
|
||||
ntag_pages = NFCPages::Block135;
|
||||
break;
|
||||
case 3:
|
||||
ntag_pages = NFCPages::Block45;
|
||||
break;
|
||||
case 4:
|
||||
ntag_pages = NFCPages::Block231;
|
||||
break;
|
||||
default:
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
|
||||
// finished
|
||||
SendStopPollingRequest(output);
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
// Ignore other state reports
|
||||
if (mcu_report == MCUReport::NFCState) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tries++ > timeout_limit) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
|
||||
constexpr std::size_t timeout_limit = 10;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
NFCPages ntag_pages = NFCPages::Block135;
|
||||
std::size_t ntag_buffer_pos = 0;
|
||||
// Read Tag data
|
||||
while (true) {
|
||||
auto result = SendReadAmiiboRequest(output, ntag_pages);
|
||||
const auto mcu_report = static_cast<MCUReport>(output[49]);
|
||||
const auto nfc_status = static_cast<NFCStatus>(output[56]);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
|
||||
nfc_status == NFCStatus::TagLost) {
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
|
||||
if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) {
|
||||
std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
|
||||
if (output[52] == 0x01) {
|
||||
memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60);
|
||||
ntag_buffer_pos += payload_size - 60;
|
||||
} else {
|
||||
memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
|
||||
LOG_INFO(Input, "Finished reading amiibo");
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
// Ignore other state reports
|
||||
if (mcu_report == MCUReport::NFCState) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tries++ > timeout_limit) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
|
||||
NFCRequestState request{
|
||||
.sub_command = MCUSubCommand::ReadDeviceMode,
|
||||
.command_argument = NFCReadCommand::StartPolling,
|
||||
.packet_id = 0x0,
|
||||
.packet_flag = MCUPacketFlag::LastCommandPacket,
|
||||
.data_length = sizeof(NFCPollingCommandData),
|
||||
.nfc_polling =
|
||||
{
|
||||
.enable_mifare = 0x01,
|
||||
.unknown_1 = 0x00,
|
||||
.unknown_2 = 0x00,
|
||||
.unknown_3 = 0x2c,
|
||||
.unknown_4 = 0x01,
|
||||
},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::array<u8, sizeof(NFCRequestState)> request_data{};
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
|
||||
NFCRequestState request{
|
||||
.sub_command = MCUSubCommand::ReadDeviceMode,
|
||||
.command_argument = NFCReadCommand::StopPolling,
|
||||
.packet_id = 0x0,
|
||||
.packet_flag = MCUPacketFlag::LastCommandPacket,
|
||||
.data_length = 0,
|
||||
.raw_data = {},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::array<u8, sizeof(NFCRequestState)> request_data{};
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) {
|
||||
NFCRequestState request{
|
||||
.sub_command = MCUSubCommand::ReadDeviceMode,
|
||||
.command_argument = NFCReadCommand::StartWaitingRecieve,
|
||||
.packet_id = 0x0,
|
||||
.packet_flag = MCUPacketFlag::LastCommandPacket,
|
||||
.data_length = 0,
|
||||
.raw_data = {},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::vector<u8> request_data(sizeof(NFCRequestState));
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) {
|
||||
NFCRequestState request{
|
||||
.sub_command = MCUSubCommand::ReadDeviceMode,
|
||||
.command_argument = NFCReadCommand::Ntag,
|
||||
.packet_id = 0x0,
|
||||
.packet_flag = MCUPacketFlag::LastCommandPacket,
|
||||
.data_length = sizeof(NFCReadCommandData),
|
||||
.nfc_read =
|
||||
{
|
||||
.unknown = 0xd0,
|
||||
.uuid_length = 0x07,
|
||||
.unknown_2 = 0x00,
|
||||
.uid = {},
|
||||
.tag_type = NFCTagType::AllTags,
|
||||
.read_block = GetReadBlockCommand(ntag_pages),
|
||||
},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::array<u8, sizeof(NFCRequestState)> request_data{};
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
|
||||
}
|
||||
|
||||
NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
|
||||
switch (pages) {
|
||||
case NFCPages::Block0:
|
||||
return {
|
||||
.block_count = 1,
|
||||
};
|
||||
case NFCPages::Block45:
|
||||
return {
|
||||
.block_count = 1,
|
||||
.blocks =
|
||||
{
|
||||
NFCReadBlock{0x00, 0x2C},
|
||||
},
|
||||
};
|
||||
case NFCPages::Block135:
|
||||
return {
|
||||
.block_count = 3,
|
||||
.blocks =
|
||||
{
|
||||
NFCReadBlock{0x00, 0x3b},
|
||||
{0x3c, 0x77},
|
||||
{0x78, 0x86},
|
||||
},
|
||||
};
|
||||
case NFCPages::Block231:
|
||||
return {
|
||||
.block_count = 4,
|
||||
.blocks =
|
||||
{
|
||||
NFCReadBlock{0x00, 0x3b},
|
||||
{0x3c, 0x77},
|
||||
{0x78, 0x83},
|
||||
{0xb4, 0xe6},
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
bool NfcProtocol::IsEnabled() const {
|
||||
return is_enabled;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
61
src/input_common/helpers/joycon_protocol/nfc.h
Normal file
61
src/input_common/helpers/joycon_protocol/nfc.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
class NfcProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
DriverResult EnableNfc();
|
||||
|
||||
DriverResult DisableNfc();
|
||||
|
||||
DriverResult StartNFCPollingMode();
|
||||
|
||||
DriverResult ScanAmiibo(std::vector<u8>& data);
|
||||
|
||||
bool HasAmiibo();
|
||||
|
||||
bool IsEnabled() const;
|
||||
|
||||
private:
|
||||
struct TagFoundData {
|
||||
u8 type;
|
||||
std::vector<u8> uuid;
|
||||
};
|
||||
|
||||
DriverResult WaitUntilNfcIsReady();
|
||||
|
||||
DriverResult StartPolling(TagFoundData& data);
|
||||
|
||||
DriverResult ReadTag(const TagFoundData& data);
|
||||
|
||||
DriverResult GetAmiiboData(std::vector<u8>& data);
|
||||
|
||||
DriverResult SendStartPollingRequest(std::vector<u8>& output);
|
||||
|
||||
DriverResult SendStopPollingRequest(std::vector<u8>& output);
|
||||
|
||||
DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
|
||||
|
||||
DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages);
|
||||
|
||||
NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
|
||||
|
||||
bool is_enabled{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
341
src/input_common/helpers/joycon_protocol/poller.cpp
Normal file
341
src/input_common/helpers/joycon_protocol/poller.cpp
Normal file
|
@ -0,0 +1,341 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/poller.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
|
||||
JoyStickCalibration right_stick_calibration_,
|
||||
MotionCalibration motion_calibration_)
|
||||
: device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
|
||||
right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
|
||||
|
||||
void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
|
||||
callbacks = std::move(callbacks_);
|
||||
}
|
||||
|
||||
void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
|
||||
const RingStatus& ring_status) {
|
||||
InputReportActive data{};
|
||||
memcpy(&data, buffer.data(), sizeof(InputReportActive));
|
||||
|
||||
switch (device_type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
UpdateActiveLeftPadInput(data, motion_status);
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
UpdateActiveRightPadInput(data, motion_status);
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
UpdateActiveProPadInput(data, motion_status);
|
||||
break;
|
||||
case Joycon::ControllerType::Grip:
|
||||
case Joycon::ControllerType::Dual:
|
||||
case Joycon::ControllerType::None:
|
||||
break;
|
||||
}
|
||||
|
||||
if (ring_status.is_enabled) {
|
||||
UpdateRing(data.ring_input, ring_status);
|
||||
}
|
||||
|
||||
callbacks.on_battery_data(data.battery_status);
|
||||
}
|
||||
|
||||
void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
|
||||
InputReportPassive data{};
|
||||
memcpy(&data, buffer.data(), sizeof(InputReportPassive));
|
||||
|
||||
switch (device_type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
UpdatePasiveLeftPadInput(data);
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
UpdatePasiveRightPadInput(data);
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
UpdatePasiveProPadInput(data);
|
||||
break;
|
||||
case Joycon::ControllerType::Grip:
|
||||
case Joycon::ControllerType::Dual:
|
||||
case Joycon::ControllerType::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
|
||||
// This mode is compatible with the active mode
|
||||
ReadActiveMode(buffer, motion_status, {});
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateColor(const Color& color) {
|
||||
callbacks.on_color_data(color);
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) {
|
||||
callbacks.on_amiibo_data(amiibo_data);
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
|
||||
callbacks.on_camera_data(camera_data, format);
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
|
||||
float normalized_value = static_cast<float>(value - ring_status.default_value);
|
||||
if (normalized_value > 0) {
|
||||
normalized_value = normalized_value /
|
||||
static_cast<float>(ring_status.max_value - ring_status.default_value);
|
||||
}
|
||||
if (normalized_value < 0) {
|
||||
normalized_value = normalized_value /
|
||||
static_cast<float>(ring_status.default_value - ring_status.min_value);
|
||||
}
|
||||
callbacks.on_ring_data(normalized_value);
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) {
|
||||
static constexpr std::array<Joycon::PadButton, 11> left_buttons{
|
||||
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
|
||||
Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
|
||||
Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
|
||||
Joycon::PadButton::Capture, Joycon::PadButton::StickL,
|
||||
};
|
||||
|
||||
const u32 raw_button =
|
||||
static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
|
||||
for (std::size_t i = 0; i < left_buttons.size(); ++i) {
|
||||
const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(left_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_left_axis_x =
|
||||
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
|
||||
const u16 raw_left_axis_y =
|
||||
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
|
||||
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
|
||||
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
|
||||
|
||||
if (motion_status.is_enabled) {
|
||||
auto left_motion = GetMotionInput(input, motion_status);
|
||||
// Rotate motion axis to the correct direction
|
||||
left_motion.accel_y = -left_motion.accel_y;
|
||||
left_motion.accel_z = -left_motion.accel_z;
|
||||
left_motion.gyro_x = -left_motion.gyro_x;
|
||||
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) {
|
||||
static constexpr std::array<Joycon::PadButton, 11> right_buttons{
|
||||
Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
|
||||
Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
|
||||
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
|
||||
Joycon::PadButton::Home, Joycon::PadButton::StickR,
|
||||
};
|
||||
|
||||
const u32 raw_button =
|
||||
static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
|
||||
for (std::size_t i = 0; i < right_buttons.size(); ++i) {
|
||||
const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(right_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_right_axis_x =
|
||||
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
|
||||
const u16 raw_right_axis_y =
|
||||
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
|
||||
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
|
||||
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
|
||||
|
||||
if (motion_status.is_enabled) {
|
||||
auto right_motion = GetMotionInput(input, motion_status);
|
||||
// Rotate motion axis to the correct direction
|
||||
right_motion.accel_x = -right_motion.accel_x;
|
||||
right_motion.accel_y = -right_motion.accel_y;
|
||||
right_motion.gyro_z = -right_motion.gyro_z;
|
||||
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) {
|
||||
static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
|
||||
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
|
||||
Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
|
||||
Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
|
||||
Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
|
||||
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
|
||||
Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
|
||||
};
|
||||
|
||||
const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
|
||||
(input.button_input[1] << 16));
|
||||
for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
|
||||
const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(pro_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_left_axis_x =
|
||||
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
|
||||
const u16 raw_left_axis_y =
|
||||
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
|
||||
const u16 raw_right_axis_x =
|
||||
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
|
||||
const u16 raw_right_axis_y =
|
||||
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
|
||||
|
||||
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
|
||||
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
|
||||
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
|
||||
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
|
||||
|
||||
if (motion_status.is_enabled) {
|
||||
auto pro_motion = GetMotionInput(input, motion_status);
|
||||
pro_motion.gyro_x = -pro_motion.gyro_x;
|
||||
pro_motion.accel_y = -pro_motion.accel_y;
|
||||
pro_motion.accel_z = -pro_motion.accel_z;
|
||||
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
|
||||
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
|
||||
static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
|
||||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
|
||||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
|
||||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
|
||||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
|
||||
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
|
||||
Joycon::PasivePadButton::StickL,
|
||||
};
|
||||
|
||||
for (auto left_button : left_buttons) {
|
||||
const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
|
||||
const int button = static_cast<int>(left_button);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
|
||||
static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
|
||||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
|
||||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
|
||||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
|
||||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
|
||||
Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
|
||||
Joycon::PasivePadButton::StickR,
|
||||
};
|
||||
|
||||
for (auto right_button : right_buttons) {
|
||||
const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
|
||||
const int button = static_cast<int>(right_button);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
|
||||
static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
|
||||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
|
||||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
|
||||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
|
||||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
|
||||
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
|
||||
Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
|
||||
Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
|
||||
};
|
||||
|
||||
for (auto pro_button : pro_buttons) {
|
||||
const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
|
||||
const int button = static_cast<int>(pro_button);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
}
|
||||
|
||||
f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
|
||||
const f32 value = static_cast<f32>(raw_value - calibration.center);
|
||||
if (value > 0.0f) {
|
||||
return value / calibration.max;
|
||||
}
|
||||
return value / calibration.min;
|
||||
}
|
||||
|
||||
f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
|
||||
AccelerometerSensitivity sensitivity) const {
|
||||
const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
|
||||
switch (sensitivity) {
|
||||
case Joycon::AccelerometerSensitivity::G2:
|
||||
return value / 4.0f;
|
||||
case Joycon::AccelerometerSensitivity::G4:
|
||||
return value / 2.0f;
|
||||
case Joycon::AccelerometerSensitivity::G8:
|
||||
return value;
|
||||
case Joycon::AccelerometerSensitivity::G16:
|
||||
return value * 2.0f;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
|
||||
GyroSensitivity sensitivity) const {
|
||||
const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
|
||||
switch (sensitivity) {
|
||||
case Joycon::GyroSensitivity::DPS250:
|
||||
return value / 8.0f;
|
||||
case Joycon::GyroSensitivity::DPS500:
|
||||
return value / 4.0f;
|
||||
case Joycon::GyroSensitivity::DPS1000:
|
||||
return value / 2.0f;
|
||||
case Joycon::GyroSensitivity::DPS2000:
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
|
||||
const InputReportActive& input) const {
|
||||
return input.motion_input[(sensor * 3) + axis];
|
||||
}
|
||||
|
||||
MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) const {
|
||||
MotionData motion{};
|
||||
const auto& accel_cal = motion_calibration.accelerometer;
|
||||
const auto& gyro_cal = motion_calibration.gyro;
|
||||
const s16 raw_accel_x = input.motion_input[1];
|
||||
const s16 raw_accel_y = input.motion_input[0];
|
||||
const s16 raw_accel_z = input.motion_input[2];
|
||||
const s16 raw_gyro_x = input.motion_input[4];
|
||||
const s16 raw_gyro_y = input.motion_input[3];
|
||||
const s16 raw_gyro_z = input.motion_input[5];
|
||||
|
||||
motion.delta_timestamp = motion_status.delta_time;
|
||||
motion.accel_x =
|
||||
GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
|
||||
motion.accel_y =
|
||||
GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
|
||||
motion.accel_z =
|
||||
GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
|
||||
motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
|
||||
motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
|
||||
motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
|
||||
|
||||
// TODO(German77): Return all three samples data
|
||||
return motion;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
81
src/input_common/helpers/joycon_protocol/poller.h
Normal file
81
src/input_common/helpers/joycon_protocol/poller.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <span>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
// Handles input packages and triggers the corresponding input events
|
||||
class JoyconPoller {
|
||||
public:
|
||||
JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
|
||||
JoyStickCalibration right_stick_calibration_,
|
||||
MotionCalibration motion_calibration_);
|
||||
|
||||
void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
|
||||
|
||||
/// Handles data from passive packages
|
||||
void ReadPassiveMode(std::span<u8> buffer);
|
||||
|
||||
/// Handles data from active packages
|
||||
void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
|
||||
const RingStatus& ring_status);
|
||||
|
||||
/// Handles data from nfc or ir packages
|
||||
void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
|
||||
|
||||
void UpdateColor(const Color& color);
|
||||
void UpdateRing(s16 value, const RingStatus& ring_status);
|
||||
void UpdateAmiibo(const std::vector<u8>& amiibo_data);
|
||||
void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
|
||||
|
||||
private:
|
||||
void UpdateActiveLeftPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status);
|
||||
void UpdateActiveRightPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status);
|
||||
void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
|
||||
|
||||
void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
|
||||
void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
|
||||
void UpdatePasiveProPadInput(const InputReportPassive& buffer);
|
||||
|
||||
/// Returns a calibrated joystick axis from raw axis data
|
||||
f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
|
||||
|
||||
/// Returns a calibrated accelerometer axis from raw motion data
|
||||
f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
|
||||
AccelerometerSensitivity sensitivity) const;
|
||||
|
||||
/// Returns a calibrated gyro axis from raw motion data
|
||||
f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
|
||||
GyroSensitivity sensitivity) const;
|
||||
|
||||
/// Returns a raw motion value from a buffer
|
||||
s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
|
||||
|
||||
/// Returns motion data from a buffer
|
||||
MotionData GetMotionInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) const;
|
||||
|
||||
ControllerType device_type{};
|
||||
|
||||
// Device calibration
|
||||
JoyStickCalibration left_stick_calibration{};
|
||||
JoyStickCalibration right_stick_calibration{};
|
||||
MotionCalibration motion_calibration{};
|
||||
|
||||
Joycon::JoyconCallbacks callbacks{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
117
src/input_common/helpers/joycon_protocol/ringcon.cpp
Normal file
117
src/input_common/helpers/joycon_protocol/ringcon.cpp
Normal file
|
@ -0,0 +1,117 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/ringcon.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult RingConProtocol::EnableRingCon() {
|
||||
LOG_DEBUG(Input, "Enable Ringcon");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(true);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
const MCUConfig config{
|
||||
.command = MCUCommand::ConfigureMCU,
|
||||
.sub_command = MCUSubCommand::SetDeviceMode,
|
||||
.mode = MCUMode::Standby,
|
||||
.crc = {},
|
||||
};
|
||||
result = ConfigureMCU(config);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult RingConProtocol::DisableRingCon() {
|
||||
LOG_DEBUG(Input, "Disable RingCon");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(false);
|
||||
}
|
||||
|
||||
is_enabled = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult RingConProtocol::StartRingconPolling() {
|
||||
LOG_DEBUG(Input, "Enable Ringcon");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
bool is_connected = false;
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = IsRingConnected(is_connected);
|
||||
}
|
||||
if (result == DriverResult::Success && is_connected) {
|
||||
LOG_INFO(Input, "Ringcon detected");
|
||||
result = ConfigureRing();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
is_enabled = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
|
||||
LOG_DEBUG(Input, "IsRingConnected");
|
||||
constexpr std::size_t max_tries = 28;
|
||||
constexpr u8 ring_controller_id = 0x20;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
is_connected = false;
|
||||
|
||||
do {
|
||||
std::array<u8, 1> empty_data{};
|
||||
const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tries++ >= max_tries) {
|
||||
return DriverResult::NoDeviceDetected;
|
||||
}
|
||||
} while (output[16] != ring_controller_id);
|
||||
|
||||
is_connected = true;
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult RingConProtocol::ConfigureRing() {
|
||||
LOG_DEBUG(Input, "ConfigureRing");
|
||||
|
||||
static constexpr std::array<u8, 37> ring_config{
|
||||
0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
|
||||
0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
|
||||
|
||||
const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
|
||||
return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data);
|
||||
}
|
||||
|
||||
bool RingConProtocol::IsEnabled() const {
|
||||
return is_enabled;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
38
src/input_common/helpers/joycon_protocol/ringcon.h
Normal file
38
src/input_common/helpers/joycon_protocol/ringcon.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
class RingConProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
DriverResult EnableRingCon();
|
||||
|
||||
DriverResult DisableRingCon();
|
||||
|
||||
DriverResult StartRingconPolling();
|
||||
|
||||
bool IsEnabled() const;
|
||||
|
||||
private:
|
||||
DriverResult IsRingConnected(bool& is_connected);
|
||||
|
||||
DriverResult ConfigureRing();
|
||||
|
||||
bool is_enabled{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
299
src/input_common/helpers/joycon_protocol/rumble.cpp
Normal file
299
src/input_common/helpers/joycon_protocol/rumble.cpp
Normal file
|
@ -0,0 +1,299 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/rumble.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
|
||||
LOG_DEBUG(Input, "Enable Rumble");
|
||||
ScopedSetBlocking sb(this);
|
||||
const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
|
||||
return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
|
||||
}
|
||||
|
||||
DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
|
||||
std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};
|
||||
|
||||
if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
|
||||
return SendVibrationReport(DefaultVibrationBuffer);
|
||||
}
|
||||
|
||||
// Protect joycons from damage from strong vibrations
|
||||
const f32 clamp_amplitude =
|
||||
1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
|
||||
|
||||
const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
|
||||
const u8 encoded_high_amplitude =
|
||||
EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
|
||||
const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
|
||||
const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
|
||||
|
||||
buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
|
||||
buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
|
||||
buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
|
||||
buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
|
||||
|
||||
// Duplicate rumble for now
|
||||
buffer[4] = buffer[0];
|
||||
buffer[5] = buffer[1];
|
||||
buffer[6] = buffer[2];
|
||||
buffer[7] = buffer[3];
|
||||
|
||||
return SendVibrationReport(buffer);
|
||||
}
|
||||
|
||||
u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
|
||||
const u8 new_frequency =
|
||||
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
|
||||
return static_cast<u16>((new_frequency - 0x60) * 4);
|
||||
}
|
||||
|
||||
u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
|
||||
const u8 new_frequency =
|
||||
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
|
||||
return static_cast<u8>(new_frequency - 0x40);
|
||||
}
|
||||
|
||||
u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
|
||||
// More information about these values can be found here:
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
|
||||
|
||||
static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
|
||||
std::pair<f32, int>{0.0f, 0x0},
|
||||
{0.01f, 0x2},
|
||||
{0.012f, 0x4},
|
||||
{0.014f, 0x6},
|
||||
{0.017f, 0x8},
|
||||
{0.02f, 0x0a},
|
||||
{0.024f, 0x0c},
|
||||
{0.028f, 0x0e},
|
||||
{0.033f, 0x10},
|
||||
{0.04f, 0x12},
|
||||
{0.047f, 0x14},
|
||||
{0.056f, 0x16},
|
||||
{0.067f, 0x18},
|
||||
{0.08f, 0x1a},
|
||||
{0.095f, 0x1c},
|
||||
{0.112f, 0x1e},
|
||||
{0.117f, 0x20},
|
||||
{0.123f, 0x22},
|
||||
{0.128f, 0x24},
|
||||
{0.134f, 0x26},
|
||||
{0.14f, 0x28},
|
||||
{0.146f, 0x2a},
|
||||
{0.152f, 0x2c},
|
||||
{0.159f, 0x2e},
|
||||
{0.166f, 0x30},
|
||||
{0.173f, 0x32},
|
||||
{0.181f, 0x34},
|
||||
{0.189f, 0x36},
|
||||
{0.198f, 0x38},
|
||||
{0.206f, 0x3a},
|
||||
{0.215f, 0x3c},
|
||||
{0.225f, 0x3e},
|
||||
{0.23f, 0x40},
|
||||
{0.235f, 0x42},
|
||||
{0.24f, 0x44},
|
||||
{0.245f, 0x46},
|
||||
{0.251f, 0x48},
|
||||
{0.256f, 0x4a},
|
||||
{0.262f, 0x4c},
|
||||
{0.268f, 0x4e},
|
||||
{0.273f, 0x50},
|
||||
{0.279f, 0x52},
|
||||
{0.286f, 0x54},
|
||||
{0.292f, 0x56},
|
||||
{0.298f, 0x58},
|
||||
{0.305f, 0x5a},
|
||||
{0.311f, 0x5c},
|
||||
{0.318f, 0x5e},
|
||||
{0.325f, 0x60},
|
||||
{0.332f, 0x62},
|
||||
{0.34f, 0x64},
|
||||
{0.347f, 0x66},
|
||||
{0.355f, 0x68},
|
||||
{0.362f, 0x6a},
|
||||
{0.37f, 0x6c},
|
||||
{0.378f, 0x6e},
|
||||
{0.387f, 0x70},
|
||||
{0.395f, 0x72},
|
||||
{0.404f, 0x74},
|
||||
{0.413f, 0x76},
|
||||
{0.422f, 0x78},
|
||||
{0.431f, 0x7a},
|
||||
{0.44f, 0x7c},
|
||||
{0.45f, 0x7e},
|
||||
{0.46f, 0x80},
|
||||
{0.47f, 0x82},
|
||||
{0.48f, 0x84},
|
||||
{0.491f, 0x86},
|
||||
{0.501f, 0x88},
|
||||
{0.512f, 0x8a},
|
||||
{0.524f, 0x8c},
|
||||
{0.535f, 0x8e},
|
||||
{0.547f, 0x90},
|
||||
{0.559f, 0x92},
|
||||
{0.571f, 0x94},
|
||||
{0.584f, 0x96},
|
||||
{0.596f, 0x98},
|
||||
{0.609f, 0x9a},
|
||||
{0.623f, 0x9c},
|
||||
{0.636f, 0x9e},
|
||||
{0.65f, 0xa0},
|
||||
{0.665f, 0xa2},
|
||||
{0.679f, 0xa4},
|
||||
{0.694f, 0xa6},
|
||||
{0.709f, 0xa8},
|
||||
{0.725f, 0xaa},
|
||||
{0.741f, 0xac},
|
||||
{0.757f, 0xae},
|
||||
{0.773f, 0xb0},
|
||||
{0.79f, 0xb2},
|
||||
{0.808f, 0xb4},
|
||||
{0.825f, 0xb6},
|
||||
{0.843f, 0xb8},
|
||||
{0.862f, 0xba},
|
||||
{0.881f, 0xbc},
|
||||
{0.9f, 0xbe},
|
||||
{0.92f, 0xc0},
|
||||
{0.94f, 0xc2},
|
||||
{0.96f, 0xc4},
|
||||
{0.981f, 0xc6},
|
||||
{1.003f, 0xc8},
|
||||
};
|
||||
|
||||
for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
|
||||
if (amplitude <= amplitude_value) {
|
||||
return static_cast<u8>(code);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
|
||||
}
|
||||
|
||||
u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
|
||||
// More information about these values can be found here:
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
|
||||
|
||||
static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
|
||||
std::pair<f32, int>{0.0f, 0x0040},
|
||||
{0.01f, 0x8040},
|
||||
{0.012f, 0x0041},
|
||||
{0.014f, 0x8041},
|
||||
{0.017f, 0x0042},
|
||||
{0.02f, 0x8042},
|
||||
{0.024f, 0x0043},
|
||||
{0.028f, 0x8043},
|
||||
{0.033f, 0x0044},
|
||||
{0.04f, 0x8044},
|
||||
{0.047f, 0x0045},
|
||||
{0.056f, 0x8045},
|
||||
{0.067f, 0x0046},
|
||||
{0.08f, 0x8046},
|
||||
{0.095f, 0x0047},
|
||||
{0.112f, 0x8047},
|
||||
{0.117f, 0x0048},
|
||||
{0.123f, 0x8048},
|
||||
{0.128f, 0x0049},
|
||||
{0.134f, 0x8049},
|
||||
{0.14f, 0x004a},
|
||||
{0.146f, 0x804a},
|
||||
{0.152f, 0x004b},
|
||||
{0.159f, 0x804b},
|
||||
{0.166f, 0x004c},
|
||||
{0.173f, 0x804c},
|
||||
{0.181f, 0x004d},
|
||||
{0.189f, 0x804d},
|
||||
{0.198f, 0x004e},
|
||||
{0.206f, 0x804e},
|
||||
{0.215f, 0x004f},
|
||||
{0.225f, 0x804f},
|
||||
{0.23f, 0x0050},
|
||||
{0.235f, 0x8050},
|
||||
{0.24f, 0x0051},
|
||||
{0.245f, 0x8051},
|
||||
{0.251f, 0x0052},
|
||||
{0.256f, 0x8052},
|
||||
{0.262f, 0x0053},
|
||||
{0.268f, 0x8053},
|
||||
{0.273f, 0x0054},
|
||||
{0.279f, 0x8054},
|
||||
{0.286f, 0x0055},
|
||||
{0.292f, 0x8055},
|
||||
{0.298f, 0x0056},
|
||||
{0.305f, 0x8056},
|
||||
{0.311f, 0x0057},
|
||||
{0.318f, 0x8057},
|
||||
{0.325f, 0x0058},
|
||||
{0.332f, 0x8058},
|
||||
{0.34f, 0x0059},
|
||||
{0.347f, 0x8059},
|
||||
{0.355f, 0x005a},
|
||||
{0.362f, 0x805a},
|
||||
{0.37f, 0x005b},
|
||||
{0.378f, 0x805b},
|
||||
{0.387f, 0x005c},
|
||||
{0.395f, 0x805c},
|
||||
{0.404f, 0x005d},
|
||||
{0.413f, 0x805d},
|
||||
{0.422f, 0x005e},
|
||||
{0.431f, 0x805e},
|
||||
{0.44f, 0x005f},
|
||||
{0.45f, 0x805f},
|
||||
{0.46f, 0x0060},
|
||||
{0.47f, 0x8060},
|
||||
{0.48f, 0x0061},
|
||||
{0.491f, 0x8061},
|
||||
{0.501f, 0x0062},
|
||||
{0.512f, 0x8062},
|
||||
{0.524f, 0x0063},
|
||||
{0.535f, 0x8063},
|
||||
{0.547f, 0x0064},
|
||||
{0.559f, 0x8064},
|
||||
{0.571f, 0x0065},
|
||||
{0.584f, 0x8065},
|
||||
{0.596f, 0x0066},
|
||||
{0.609f, 0x8066},
|
||||
{0.623f, 0x0067},
|
||||
{0.636f, 0x8067},
|
||||
{0.65f, 0x0068},
|
||||
{0.665f, 0x8068},
|
||||
{0.679f, 0x0069},
|
||||
{0.694f, 0x8069},
|
||||
{0.709f, 0x006a},
|
||||
{0.725f, 0x806a},
|
||||
{0.741f, 0x006b},
|
||||
{0.757f, 0x806b},
|
||||
{0.773f, 0x006c},
|
||||
{0.79f, 0x806c},
|
||||
{0.808f, 0x006d},
|
||||
{0.825f, 0x806d},
|
||||
{0.843f, 0x006e},
|
||||
{0.862f, 0x806e},
|
||||
{0.881f, 0x006f},
|
||||
{0.9f, 0x806f},
|
||||
{0.92f, 0x0070},
|
||||
{0.94f, 0x8070},
|
||||
{0.96f, 0x0071},
|
||||
{0.981f, 0x8071},
|
||||
{1.003f, 0x0072},
|
||||
};
|
||||
|
||||
for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
|
||||
if (amplitude <= amplitude_value) {
|
||||
return static_cast<u16>(code);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
33
src/input_common/helpers/joycon_protocol/rumble.h
Normal file
33
src/input_common/helpers/joycon_protocol/rumble.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
class RumbleProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
DriverResult EnableRumble(bool is_enabled);
|
||||
|
||||
DriverResult SendVibration(const VibrationValue& vibration);
|
||||
|
||||
private:
|
||||
u16 EncodeHighFrequency(f32 frequency) const;
|
||||
u8 EncodeLowFrequency(f32 frequency) const;
|
||||
u8 EncodeHighAmplitude(f32 amplitude) const;
|
||||
u16 EncodeLowAmplitude(f32 amplitude) const;
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
|
@ -79,6 +79,17 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat
|
|||
TriggerOnBatteryChange(identifier, value);
|
||||
}
|
||||
|
||||
void InputEngine::SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value) {
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!configuring) {
|
||||
controller.color = value;
|
||||
}
|
||||
}
|
||||
TriggerOnColorChange(identifier, value);
|
||||
}
|
||||
|
||||
void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
|
@ -176,6 +187,18 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif
|
|||
return controller.battery;
|
||||
}
|
||||
|
||||
Common::Input::BodyColorStatus InputEngine::GetColor(const PadIdentifier& identifier) const {
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto controller_iter = controller_list.find(identifier);
|
||||
if (controller_iter == controller_list.cend()) {
|
||||
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
|
||||
identifier.pad, identifier.port);
|
||||
return {};
|
||||
}
|
||||
const ControllerData& controller = controller_iter->second;
|
||||
return controller.color;
|
||||
}
|
||||
|
||||
BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto controller_iter = controller_list.find(identifier);
|
||||
|
@ -328,6 +351,20 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
|
|||
}
|
||||
}
|
||||
|
||||
void InputEngine::TriggerOnColorChange(const PadIdentifier& identifier,
|
||||
[[maybe_unused]] Common::Input::BodyColorStatus value) {
|
||||
std::scoped_lock lock{mutex_callback};
|
||||
for (const auto& poller_pair : callback_list) {
|
||||
const InputIdentifier& poller = poller_pair.second;
|
||||
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Color, 0)) {
|
||||
continue;
|
||||
}
|
||||
if (poller.callback.on_change) {
|
||||
poller.callback.on_change();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
|
||||
const BasicMotion& value) {
|
||||
std::scoped_lock lock{mutex_callback};
|
||||
|
|
|
@ -40,6 +40,7 @@ enum class EngineInputType {
|
|||
Battery,
|
||||
Button,
|
||||
Camera,
|
||||
Color,
|
||||
HatButton,
|
||||
Motion,
|
||||
Nfc,
|
||||
|
@ -104,14 +105,17 @@ public:
|
|||
void EndConfiguration();
|
||||
|
||||
// Sets a led pattern for a controller
|
||||
virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] const Common::Input::LedStatus& led_status) {}
|
||||
virtual Common::Input::DriverResult SetLeds(
|
||||
[[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] const Common::Input::LedStatus& led_status) {
|
||||
return Common::Input::DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
// Sets rumble to a controller
|
||||
virtual Common::Input::VibrationError SetVibration(
|
||||
virtual Common::Input::DriverResult SetVibration(
|
||||
[[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
|
||||
return Common::Input::VibrationError::NotSupported;
|
||||
return Common::Input::DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
// Returns true if device supports vibrations
|
||||
|
@ -120,17 +124,17 @@ public:
|
|||
}
|
||||
|
||||
// Sets polling mode to a controller
|
||||
virtual Common::Input::PollingError SetPollingMode(
|
||||
virtual Common::Input::DriverResult SetPollingMode(
|
||||
[[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] const Common::Input::PollingMode polling_mode) {
|
||||
return Common::Input::PollingError::NotSupported;
|
||||
return Common::Input::DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
// Sets camera format to a controller
|
||||
virtual Common::Input::CameraError SetCameraFormat(
|
||||
virtual Common::Input::DriverResult SetCameraFormat(
|
||||
[[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] Common::Input::CameraFormat camera_format) {
|
||||
return Common::Input::CameraError::NotSupported;
|
||||
return Common::Input::DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
// Returns success if nfc is supported
|
||||
|
@ -199,6 +203,7 @@ public:
|
|||
bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
|
||||
f32 GetAxis(const PadIdentifier& identifier, int axis) const;
|
||||
Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
|
||||
Common::Input::BodyColorStatus GetColor(const PadIdentifier& identifier) const;
|
||||
BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
|
||||
Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
|
||||
Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
|
||||
|
@ -212,6 +217,7 @@ protected:
|
|||
void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
|
||||
void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
|
||||
void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
|
||||
void SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value);
|
||||
void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
|
||||
void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
|
||||
void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
|
||||
|
@ -227,6 +233,7 @@ private:
|
|||
std::unordered_map<int, float> axes;
|
||||
std::unordered_map<int, BasicMotion> motions;
|
||||
Common::Input::BatteryLevel battery{};
|
||||
Common::Input::BodyColorStatus color{};
|
||||
Common::Input::CameraStatus camera{};
|
||||
Common::Input::NfcStatus nfc{};
|
||||
};
|
||||
|
@ -235,6 +242,8 @@ private:
|
|||
void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
|
||||
void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
|
||||
void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
|
||||
void TriggerOnColorChange(const PadIdentifier& identifier,
|
||||
Common::Input::BodyColorStatus value);
|
||||
void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
|
||||
const BasicMotion& value);
|
||||
void TriggerOnCameraChange(const PadIdentifier& identifier,
|
||||
|
|
|
@ -498,6 +498,58 @@ private:
|
|||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromColor final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromColor(PadIdentifier identifier_, InputEngine* input_engine_)
|
||||
: identifier(identifier_), input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Color,
|
||||
.index = 0,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_color_value = {};
|
||||
callback_key = input_engine->SetCallback(input_identifier);
|
||||
}
|
||||
|
||||
~InputFromColor() override {
|
||||
input_engine->DeleteCallback(callback_key);
|
||||
}
|
||||
|
||||
Common::Input::BodyColorStatus GetStatus() const {
|
||||
return input_engine->GetColor(identifier);
|
||||
}
|
||||
|
||||
void ForceUpdate() override {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Color,
|
||||
.color_status = GetStatus(),
|
||||
};
|
||||
|
||||
last_color_value = status.color_status;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Color,
|
||||
.color_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.color_status.body != last_color_value.body) {
|
||||
last_color_value = status.color_status;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
int callback_key;
|
||||
Common::Input::BodyColorStatus last_color_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromMotion final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_,
|
||||
|
@ -754,11 +806,11 @@ public:
|
|||
explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
|
||||
: identifier(identifier_), input_engine(input_engine_) {}
|
||||
|
||||
void SetLED(const Common::Input::LedStatus& led_status) override {
|
||||
input_engine->SetLeds(identifier, led_status);
|
||||
Common::Input::DriverResult SetLED(const Common::Input::LedStatus& led_status) override {
|
||||
return input_engine->SetLeds(identifier, led_status);
|
||||
}
|
||||
|
||||
Common::Input::VibrationError SetVibration(
|
||||
Common::Input::DriverResult SetVibration(
|
||||
const Common::Input::VibrationStatus& vibration_status) override {
|
||||
return input_engine->SetVibration(identifier, vibration_status);
|
||||
}
|
||||
|
@ -767,11 +819,12 @@ public:
|
|||
return input_engine->IsVibrationEnabled(identifier);
|
||||
}
|
||||
|
||||
Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override {
|
||||
Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode) override {
|
||||
return input_engine->SetPollingMode(identifier, polling_mode);
|
||||
}
|
||||
|
||||
Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override {
|
||||
Common::Input::DriverResult SetCameraFormat(
|
||||
Common::Input::CameraFormat camera_format) override {
|
||||
return input_engine->SetCameraFormat(identifier, camera_format);
|
||||
}
|
||||
|
||||
|
@ -966,6 +1019,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
|
|||
return std::make_unique<InputFromBattery>(identifier, input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateColorDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
input_engine->PreSetController(identifier);
|
||||
return std::make_unique<InputFromColor>(identifier, input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
|
||||
Common::ParamPackage params) {
|
||||
const PadIdentifier identifier = {
|
||||
|
@ -1053,6 +1118,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
|
|||
if (params.Has("battery")) {
|
||||
return CreateBatteryDevice(params);
|
||||
}
|
||||
if (params.Has("color")) {
|
||||
return CreateColorDevice(params);
|
||||
}
|
||||
if (params.Has("camera")) {
|
||||
return CreateCameraDevice(params);
|
||||
}
|
||||
|
|
|
@ -190,6 +190,17 @@ private:
|
|||
std::unique_ptr<Common::Input::InputDevice> CreateBatteryDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates a color device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "guid": text string for identifying controllers
|
||||
* - "port": port of the connected device
|
||||
* - "pad": slot of the connected controller
|
||||
* @returns a unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateColorDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates a motion device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "input_common/drivers/gc_adapter.h"
|
||||
#endif
|
||||
#ifdef HAVE_SDL2
|
||||
#include "input_common/drivers/joycon.h"
|
||||
#include "input_common/drivers/sdl_driver.h"
|
||||
#endif
|
||||
|
||||
|
@ -81,6 +82,7 @@ struct InputSubsystem::Impl {
|
|||
RegisterEngine("virtual_gamepad", virtual_gamepad);
|
||||
#ifdef HAVE_SDL2
|
||||
RegisterEngine("sdl", sdl);
|
||||
RegisterEngine("joycon", joycon);
|
||||
#endif
|
||||
|
||||
Common::Input::RegisterInputFactory("touch_from_button",
|
||||
|
@ -111,6 +113,7 @@ struct InputSubsystem::Impl {
|
|||
UnregisterEngine(virtual_gamepad);
|
||||
#ifdef HAVE_SDL2
|
||||
UnregisterEngine(sdl);
|
||||
UnregisterEngine(joycon);
|
||||
#endif
|
||||
|
||||
Common::Input::UnregisterInputFactory("touch_from_button");
|
||||
|
@ -133,6 +136,8 @@ struct InputSubsystem::Impl {
|
|||
auto udp_devices = udp_client->GetInputDevices();
|
||||
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
||||
#ifdef HAVE_SDL2
|
||||
auto joycon_devices = joycon->GetInputDevices();
|
||||
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
|
||||
auto sdl_devices = sdl->GetInputDevices();
|
||||
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
|
||||
#endif
|
||||
|
@ -164,6 +169,9 @@ struct InputSubsystem::Impl {
|
|||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl;
|
||||
}
|
||||
if (engine == joycon->GetEngineName()) {
|
||||
return joycon;
|
||||
}
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -247,6 +255,9 @@ struct InputSubsystem::Impl {
|
|||
if (engine == sdl->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
if (engine == joycon->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
@ -260,6 +271,7 @@ struct InputSubsystem::Impl {
|
|||
udp_client->BeginConfiguration();
|
||||
#ifdef HAVE_SDL2
|
||||
sdl->BeginConfiguration();
|
||||
joycon->BeginConfiguration();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -272,6 +284,7 @@ struct InputSubsystem::Impl {
|
|||
udp_client->EndConfiguration();
|
||||
#ifdef HAVE_SDL2
|
||||
sdl->EndConfiguration();
|
||||
joycon->EndConfiguration();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -304,6 +317,7 @@ struct InputSubsystem::Impl {
|
|||
|
||||
#ifdef HAVE_SDL2
|
||||
std::shared_ptr<SDLDriver> sdl;
|
||||
std::shared_ptr<Joycons> joycon;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -440,6 +440,7 @@ void Config::ReadControlValues() {
|
|||
ReadBasicSetting(Settings::values.emulate_analog_keyboard);
|
||||
Settings::values.mouse_panning = false;
|
||||
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
|
||||
ReadBasicSetting(Settings::values.enable_joycon_driver);
|
||||
|
||||
ReadBasicSetting(Settings::values.tas_enable);
|
||||
ReadBasicSetting(Settings::values.tas_loop);
|
||||
|
@ -1139,6 +1140,7 @@ void Config::SaveControlValues() {
|
|||
WriteGlobalSetting(Settings::values.enable_accurate_vibrations);
|
||||
WriteGlobalSetting(Settings::values.motion_enabled);
|
||||
WriteBasicSetting(Settings::values.enable_raw_input);
|
||||
WriteBasicSetting(Settings::values.enable_joycon_driver);
|
||||
WriteBasicSetting(Settings::values.keyboard_enabled);
|
||||
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
|
||||
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
|
||||
|
|
|
@ -138,6 +138,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
|
|||
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
|
||||
Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
|
||||
Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked();
|
||||
Settings::values.enable_joycon_driver = ui->enable_joycon_driver->isChecked();
|
||||
}
|
||||
|
||||
void ConfigureInputAdvanced::LoadConfiguration() {
|
||||
|
@ -172,6 +173,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
|
|||
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
|
||||
ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
|
||||
ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue());
|
||||
ui->enable_joycon_driver->setChecked(Settings::values.enable_joycon_driver.GetValue());
|
||||
|
||||
UpdateUIEnabled();
|
||||
}
|
||||
|
|
|
@ -2696,6 +2696,22 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="enable_joycon_driver">
|
||||
<property name="toolTip">
|
||||
<string>Requires restarting yuzu</string>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>23</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable direct JoyCon driver</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="mouse_panning">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
|
@ -2708,7 +2724,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<item row="6" column="2">
|
||||
<widget class="QSpinBox" name="mouse_panning_sensitivity">
|
||||
<property name="toolTip">
|
||||
<string>Mouse sensitivity</string>
|
||||
|
@ -2730,14 +2746,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="motion_touch">
|
||||
<property name="text">
|
||||
<string>Motion / Touch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<item row="7" column="2">
|
||||
<widget class="QPushButton" name="buttonMotionTouch">
|
||||
<property name="text">
|
||||
<string>Configure</string>
|
||||
|
|
|
@ -66,6 +66,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
|
|||
return QObject::tr("R");
|
||||
case Common::Input::ButtonNames::TriggerL:
|
||||
return QObject::tr("L");
|
||||
case Common::Input::ButtonNames::TriggerZR:
|
||||
return QObject::tr("ZR");
|
||||
case Common::Input::ButtonNames::TriggerZL:
|
||||
return QObject::tr("ZL");
|
||||
case Common::Input::ButtonNames::TriggerSR:
|
||||
return QObject::tr("SR");
|
||||
case Common::Input::ButtonNames::TriggerSL:
|
||||
return QObject::tr("SL");
|
||||
case Common::Input::ButtonNames::ButtonStickL:
|
||||
return QObject::tr("Stick L");
|
||||
case Common::Input::ButtonNames::ButtonStickR:
|
||||
return QObject::tr("Stick R");
|
||||
case Common::Input::ButtonNames::ButtonA:
|
||||
return QObject::tr("A");
|
||||
case Common::Input::ButtonNames::ButtonB:
|
||||
|
@ -76,6 +88,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
|
|||
return QObject::tr("Y");
|
||||
case Common::Input::ButtonNames::ButtonStart:
|
||||
return QObject::tr("Start");
|
||||
case Common::Input::ButtonNames::ButtonPlus:
|
||||
return QObject::tr("Plus");
|
||||
case Common::Input::ButtonNames::ButtonMinus:
|
||||
return QObject::tr("Minus");
|
||||
case Common::Input::ButtonNames::ButtonHome:
|
||||
return QObject::tr("Home");
|
||||
case Common::Input::ButtonNames::ButtonCapture:
|
||||
return QObject::tr("Capture");
|
||||
case Common::Input::ButtonNames::L1:
|
||||
return QObject::tr("L1");
|
||||
case Common::Input::ButtonNames::L2:
|
||||
|
|
|
@ -103,9 +103,13 @@ void PlayerControlPreview::UpdateColors() {
|
|||
|
||||
colors.left = colors.primary;
|
||||
colors.right = colors.primary;
|
||||
// Possible alternative to set colors from settings
|
||||
// colors.left = QColor(controller->GetColors().left.body);
|
||||
// colors.right = QColor(controller->GetColors().right.body);
|
||||
|
||||
const auto color_left = controller->GetColorsValues()[0].body;
|
||||
const auto color_right = controller->GetColorsValues()[1].body;
|
||||
if (color_left != 0 && color_right != 0) {
|
||||
colors.left = QColor(color_left);
|
||||
colors.right = QColor(color_right);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerControlPreview::ResetInputs() {
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
#include <memory>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "core/hid/emulated_devices.h"
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "input_common/drivers/keyboard.h"
|
||||
#include "input_common/drivers/mouse.h"
|
||||
|
@ -126,9 +128,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
|
|||
ui->buttonRingAnalogPush,
|
||||
};
|
||||
|
||||
emulated_device = hid_core_.GetEmulatedDevices();
|
||||
emulated_device->SaveCurrentConfig();
|
||||
emulated_device->EnableConfiguration();
|
||||
emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
emulated_controller->SaveCurrentConfig();
|
||||
emulated_controller->EnableConfiguration();
|
||||
|
||||
Core::HID::ControllerUpdateCallback engine_callback{
|
||||
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
|
||||
.is_npad_service = false,
|
||||
};
|
||||
callback_key = emulated_controller->SetCallback(engine_callback);
|
||||
is_controller_set = true;
|
||||
|
||||
LoadConfiguration();
|
||||
|
||||
|
@ -143,9 +152,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
|
|||
HandleClick(
|
||||
analog_map_buttons[sub_button_id],
|
||||
[=, this](const Common::ParamPackage& params) {
|
||||
Common::ParamPackage param = emulated_device->GetRingParam();
|
||||
Common::ParamPackage param = emulated_controller->GetRingParam();
|
||||
SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
|
||||
emulated_device->SetRingParam(param);
|
||||
emulated_controller->SetRingParam(param);
|
||||
},
|
||||
InputCommon::Polling::InputType::Stick);
|
||||
});
|
||||
|
@ -155,16 +164,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
|
|||
connect(analog_button, &QPushButton::customContextMenuRequested,
|
||||
[=, this](const QPoint& menu_location) {
|
||||
QMenu context_menu;
|
||||
Common::ParamPackage param = emulated_device->GetRingParam();
|
||||
Common::ParamPackage param = emulated_controller->GetRingParam();
|
||||
context_menu.addAction(tr("Clear"), [&] {
|
||||
emulated_device->SetRingParam({});
|
||||
emulated_controller->SetRingParam(param);
|
||||
analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
|
||||
});
|
||||
context_menu.addAction(tr("Invert axis"), [&] {
|
||||
const bool invert_value = param.Get("invert_x", "+") == "-";
|
||||
const std::string invert_str = invert_value ? "+" : "-";
|
||||
param.Set("invert_x", invert_str);
|
||||
emulated_device->SetRingParam(param);
|
||||
emulated_controller->SetRingParam(param);
|
||||
for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM;
|
||||
++sub_button_id2) {
|
||||
analog_map_buttons[sub_button_id2]->setText(
|
||||
|
@ -177,16 +186,19 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
|
|||
}
|
||||
|
||||
connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
|
||||
Common::ParamPackage param = emulated_device->GetRingParam();
|
||||
Common::ParamPackage param = emulated_controller->GetRingParam();
|
||||
const auto slider_value = ui->sliderRingAnalogDeadzone->value();
|
||||
ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
|
||||
param.Set("deadzone", slider_value / 100.0f);
|
||||
emulated_device->SetRingParam(param);
|
||||
emulated_controller->SetRingParam(param);
|
||||
});
|
||||
|
||||
connect(ui->restore_defaults_button, &QPushButton::clicked, this,
|
||||
&ConfigureRingController::RestoreDefaults);
|
||||
|
||||
connect(ui->enable_ring_controller_button, &QPushButton::clicked, this,
|
||||
&ConfigureRingController::EnableRingController);
|
||||
|
||||
timeout_timer->setSingleShot(true);
|
||||
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
|
||||
|
||||
|
@ -202,7 +214,14 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
|
|||
}
|
||||
|
||||
ConfigureRingController::~ConfigureRingController() {
|
||||
emulated_device->DisableConfiguration();
|
||||
emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
emulated_controller->DisableConfiguration();
|
||||
|
||||
if (is_controller_set) {
|
||||
emulated_controller->DeleteCallback(callback_key);
|
||||
is_controller_set = false;
|
||||
}
|
||||
};
|
||||
|
||||
void ConfigureRingController::changeEvent(QEvent* event) {
|
||||
|
@ -219,7 +238,7 @@ void ConfigureRingController::RetranslateUI() {
|
|||
|
||||
void ConfigureRingController::UpdateUI() {
|
||||
RetranslateUI();
|
||||
const Common::ParamPackage param = emulated_device->GetRingParam();
|
||||
const Common::ParamPackage param = emulated_controller->GetRingParam();
|
||||
|
||||
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
|
||||
auto* const analog_button = analog_map_buttons[sub_button_id];
|
||||
|
@ -240,9 +259,9 @@ void ConfigureRingController::UpdateUI() {
|
|||
}
|
||||
|
||||
void ConfigureRingController::ApplyConfiguration() {
|
||||
emulated_device->DisableConfiguration();
|
||||
emulated_device->SaveCurrentConfig();
|
||||
emulated_device->EnableConfiguration();
|
||||
emulated_controller->DisableConfiguration();
|
||||
emulated_controller->SaveCurrentConfig();
|
||||
emulated_controller->EnableConfiguration();
|
||||
}
|
||||
|
||||
void ConfigureRingController::LoadConfiguration() {
|
||||
|
@ -252,10 +271,62 @@ void ConfigureRingController::LoadConfiguration() {
|
|||
void ConfigureRingController::RestoreDefaults() {
|
||||
const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
|
||||
0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
|
||||
emulated_device->SetRingParam(Common::ParamPackage(default_ring_string));
|
||||
emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
void ConfigureRingController::EnableRingController() {
|
||||
const auto dialog_title = tr("Error enabling ring input");
|
||||
|
||||
is_ring_enabled = false;
|
||||
ui->ring_controller_sensor_value->setText(tr("Not connected"));
|
||||
|
||||
if (!Settings::values.enable_joycon_driver) {
|
||||
QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
ui->enable_ring_controller_button->setEnabled(false);
|
||||
ui->enable_ring_controller_button->setText(tr("Configuring"));
|
||||
// SetPollingMode is blocking. Allow to update the button status before calling the command
|
||||
repaint();
|
||||
|
||||
const auto result = emulated_controller->SetPollingMode(
|
||||
Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring);
|
||||
switch (result) {
|
||||
case Common::Input::DriverResult::Success:
|
||||
is_ring_enabled = true;
|
||||
break;
|
||||
case Common::Input::DriverResult::NotSupported:
|
||||
QMessageBox::warning(this, dialog_title,
|
||||
tr("The current mapped device doesn't support the ring controller"));
|
||||
break;
|
||||
case Common::Input::DriverResult::NoDeviceDetected:
|
||||
QMessageBox::warning(this, dialog_title,
|
||||
tr("The current mapped device doesn't have a ring attached"));
|
||||
break;
|
||||
default:
|
||||
QMessageBox::warning(this, dialog_title,
|
||||
tr("Unexpected driver result %1").arg(static_cast<int>(result)));
|
||||
break;
|
||||
}
|
||||
ui->enable_ring_controller_button->setEnabled(true);
|
||||
ui->enable_ring_controller_button->setText(tr("Enable"));
|
||||
}
|
||||
|
||||
void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) {
|
||||
if (!is_ring_enabled) {
|
||||
return;
|
||||
}
|
||||
if (type != Core::HID::ControllerTriggerType::RingController) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto value = emulated_controller->GetRingSensorValues();
|
||||
const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value));
|
||||
ui->ring_controller_sensor_value->setText(tex_value);
|
||||
}
|
||||
|
||||
void ConfigureRingController::HandleClick(
|
||||
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
InputCommon::Polling::InputType type) {
|
||||
|
|
|
@ -13,7 +13,7 @@ class InputSubsystem;
|
|||
|
||||
namespace Core::HID {
|
||||
class HIDCore;
|
||||
class EmulatedDevices;
|
||||
class EmulatedController;
|
||||
} // namespace Core::HID
|
||||
|
||||
namespace Ui {
|
||||
|
@ -42,6 +42,12 @@ private:
|
|||
/// Restore all buttons to their default values.
|
||||
void RestoreDefaults();
|
||||
|
||||
/// Sets current polling mode to ring input
|
||||
void EnableRingController();
|
||||
|
||||
// Handles emulated controller events
|
||||
void ControllerUpdate(Core::HID::ControllerTriggerType type);
|
||||
|
||||
/// Called when the button was pressed.
|
||||
void HandleClick(QPushButton* button,
|
||||
std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
|
@ -78,7 +84,11 @@ private:
|
|||
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
|
||||
|
||||
InputCommon::InputSubsystem* input_subsystem;
|
||||
Core::HID::EmulatedDevices* emulated_device;
|
||||
Core::HID::EmulatedController* emulated_controller;
|
||||
|
||||
bool is_ring_enabled{};
|
||||
bool is_controller_set{};
|
||||
int callback_key;
|
||||
|
||||
std::unique_ptr<Ui::ConfigureRingController> ui;
|
||||
};
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>298</width>
|
||||
<height>339</height>
|
||||
<width>315</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -49,12 +49,12 @@
|
|||
<item>
|
||||
<widget class="QGroupBox" name="RingAnalog">
|
||||
<property name="title">
|
||||
<string>Ring Sensor Parameters</string>
|
||||
<string>Virtual Ring Sensor Parameters</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
@ -106,7 +106,7 @@
|
|||
<widget class="QPushButton" name="buttonRingAnalogPull">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>68</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
|
@ -155,7 +155,7 @@
|
|||
<widget class="QPushButton" name="buttonRingAnalogPush">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>68</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
|
@ -227,6 +227,102 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="RingDriver">
|
||||
<property name="title">
|
||||
<string>Direct Joycon Driver</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="enable_ring_controller_label">
|
||||
<property name="text">
|
||||
<string>Enable Ring Input</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="enable_ring_controller_button">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="ring_controller_sensor_label">
|
||||
<property name="text">
|
||||
<string>Ring Sensor Value</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="ring_controller_sensor_value">
|
||||
<property name="text">
|
||||
<string>Not connected</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
|
Loading…
Reference in a new issue