Compare commits
4 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3589d89cc | ||
|
|
a1ed499ce8 | ||
|
|
7c6d2a3068 | ||
|
|
d27a4b3e07 |
@@ -183,17 +183,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
|
||||
|
||||
@@ -559,6 +559,7 @@ struct Values {
|
||||
|
||||
BasicSetting<bool> enable_raw_input{false, "enable_raw_input"};
|
||||
BasicSetting<bool> controller_navigation{true, "controller_navigation"};
|
||||
BasicSetting<bool> enable_sdl_joycons{false, "enable_sdl_joycons"};
|
||||
|
||||
Setting<bool> vibration_enabled{true, "vibration_enabled"};
|
||||
Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
|
||||
|
||||
@@ -5,8 +5,6 @@ add_library(input_common STATIC
|
||||
drivers/keyboard.h
|
||||
drivers/mouse.cpp
|
||||
drivers/mouse.h
|
||||
drivers/sdl_driver.cpp
|
||||
drivers/sdl_driver.h
|
||||
drivers/tas_input.cpp
|
||||
drivers/tas_input.h
|
||||
drivers/touch_screen.cpp
|
||||
@@ -53,11 +51,15 @@ 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_protocol.cpp
|
||||
helpers/joycon_protocol.h
|
||||
)
|
||||
target_link_libraries(input_common PRIVATE SDL2)
|
||||
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
||||
target_link_libraries(input_common PRIVATE SDL2)
|
||||
endif()
|
||||
|
||||
target_link_libraries(input_common PRIVATE usb)
|
||||
|
||||
592
src/input_common/drivers/joycon.cpp
Normal file
592
src/input_common/drivers/joycon.cpp
Normal file
@@ -0,0 +1,592 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "input_common/drivers/joycon.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
|
||||
if (Settings::values.enable_sdl_joycons) {
|
||||
// Avoid conflicting with SDL 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::UpdateJoyconData(JoyconData& jc, std::span<const u8> buffer) {
|
||||
switch (jc.type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
UpdateLeftPadInput(jc, buffer);
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
UpdateRightPadInput(jc, buffer);
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
UpdateProPadInput(jc, buffer);
|
||||
break;
|
||||
case Joycon::ControllerType::None:
|
||||
break;
|
||||
}
|
||||
jc.raw_battery = buffer[2] >> 4;
|
||||
}
|
||||
|
||||
std::string Joycons::JoyconName(std::size_t port) const {
|
||||
switch (joycon_data[port].type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
return "Left Joycon";
|
||||
case Joycon::ControllerType::Right:
|
||||
return "Right Joycon";
|
||||
case Joycon::ControllerType::Pro:
|
||||
return "Pro Controller";
|
||||
default:
|
||||
return "Unknow Joycon";
|
||||
}
|
||||
}
|
||||
|
||||
void Joycons::ResetDeviceType(std::size_t port) {
|
||||
if (port > joycon_data.size()) {
|
||||
return;
|
||||
}
|
||||
joycon_data[port].type = Joycon::ControllerType::None;
|
||||
}
|
||||
|
||||
void Joycons::InputThread(std::stop_token stop_token) {
|
||||
LOG_INFO(Input, "JC Adapter scan thread started");
|
||||
Common::SetCurrentThreadName("yuzu:input:JoyconInputThread");
|
||||
input_thread_running = true;
|
||||
std::vector<u8> buffer(Joycon::max_resp_size);
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
for (std::size_t port = 0; port < joycon_data.size(); ++port) {
|
||||
if (joycon_data[port].type == Joycon::ControllerType::None) {
|
||||
continue;
|
||||
}
|
||||
const int status = SDL_hid_read_timeout(joycon_data[port].joycon_handle.handle,
|
||||
buffer.data(), Joycon::max_resp_size, 15);
|
||||
|
||||
if (IsPayloadCorrect(status, buffer)) {
|
||||
UpdateJoyconData(joycon_data[port], buffer);
|
||||
}
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
input_thread_running = false;
|
||||
}
|
||||
|
||||
bool Joycons::IsPayloadCorrect(int status, std::span<const u8> buffer) {
|
||||
if (status > 0) {
|
||||
if (buffer[0] == 0x00) {
|
||||
// Invalid response
|
||||
LOG_DEBUG(Input, "Error reading payload");
|
||||
if (input_error_counter++ > 20) {
|
||||
LOG_ERROR(Input, "Timeout, Is the joycon connected?");
|
||||
input_thread.request_stop();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
input_error_counter = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Joycons::Setup() {
|
||||
// Initialize all controllers as unplugged
|
||||
for (std::size_t port = 0; port < joycon_data.size(); ++port) {
|
||||
PreSetController(GetIdentifier(port));
|
||||
joycon_data[port].type = Joycon::ControllerType::None;
|
||||
joycon_data[port].hd_rumble.high_amplitude = 0;
|
||||
joycon_data[port].hd_rumble.low_amplitude = 0;
|
||||
joycon_data[port].hd_rumble.high_frequency = 320.0f;
|
||||
joycon_data[port].hd_rumble.low_frequency = 160.0f;
|
||||
}
|
||||
if (!scan_thread_running) {
|
||||
scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
|
||||
}
|
||||
}
|
||||
|
||||
void Joycons::ScanThread(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("yuzu:input:JoyconScanThread");
|
||||
scan_thread_running = true;
|
||||
std::size_t port = 0;
|
||||
// while (!stop_token.stop_requested()) {
|
||||
LOG_INFO(Input, "Scanning for devices");
|
||||
SDL_hid_device_info* devs = SDL_hid_enumerate(0x057e, 0x0);
|
||||
SDL_hid_device_info* cur_dev = devs;
|
||||
|
||||
while (cur_dev) {
|
||||
LOG_WARNING(Input, "Device Found");
|
||||
LOG_WARNING(Input, "type : {:04X} {:04X}", cur_dev->vendor_id, cur_dev->product_id);
|
||||
bool skip_device = false;
|
||||
|
||||
for (std::size_t i = 0; i < joycon_data.size(); ++i) {
|
||||
Joycon::ControllerType type{};
|
||||
const auto result = Joycon::GetDeviceType(cur_dev, type);
|
||||
|
||||
if (result != Joycon::ErrorCode::Success || type == joycon_data[i].type) {
|
||||
LOG_WARNING(Input, "Device already exist, result = {}, type = {}", result, type);
|
||||
skip_device = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!skip_device) {
|
||||
const auto result = Joycon::CheckDeviceAccess(joycon_data[port].joycon_handle, cur_dev);
|
||||
if (result == Joycon::ErrorCode::Success) {
|
||||
LOG_WARNING(Input, "Device verified and accessible");
|
||||
++port;
|
||||
}
|
||||
}
|
||||
cur_dev = cur_dev->next;
|
||||
}
|
||||
GetJCEndpoint();
|
||||
|
||||
// std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
//}
|
||||
scan_thread_running = false;
|
||||
}
|
||||
|
||||
void Joycons::Reset() {
|
||||
scan_thread = {};
|
||||
input_thread = {};
|
||||
|
||||
for (std::size_t port = 0; port < joycon_data.size(); ++port) {
|
||||
joycon_data[port].type = Joycon::ControllerType::None;
|
||||
if (joycon_data[port].joycon_handle.handle) {
|
||||
joycon_data[port].joycon_handle.handle = nullptr;
|
||||
joycon_data[port].joycon_handle.packet_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_hid_exit();
|
||||
}
|
||||
|
||||
void Joycons::GetJCEndpoint() {
|
||||
// init joycons
|
||||
for (std::size_t port = 0; port < joycon_data.size(); ++port) {
|
||||
auto& joycon = joycon_data[port];
|
||||
auto& handle = joycon.joycon_handle;
|
||||
joycon.port = port;
|
||||
if (!joycon.joycon_handle.handle) {
|
||||
continue;
|
||||
}
|
||||
LOG_INFO(Input, "Initializing device {}", port);
|
||||
|
||||
Joycon::GetDeviceType(handle, joycon.type);
|
||||
// joycon_data[port].mac= Joycon::GetMac();
|
||||
// SetSerialNumber(joycon_data[port]);
|
||||
// SetVersionNumber(joycon_data[port]);
|
||||
// SetDeviceType(joycon_data[port]);
|
||||
Joycon::GetFactoryCalibrationData(handle, joycon.calibration, joycon.type);
|
||||
// GetColor(joycon_data[port]);
|
||||
|
||||
joycon.rumble_enabled = true;
|
||||
joycon.imu_enabled = true;
|
||||
joycon.gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
|
||||
joycon.gyro_performance = Joycon::GyroPerformance::HZ833;
|
||||
joycon.accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
|
||||
joycon.accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
|
||||
|
||||
Joycon::SetLedConfig(handle, static_cast<u8>(port + 1));
|
||||
Joycon::EnableRumble(handle, joycon.rumble_enabled);
|
||||
Joycon::EnableImu(handle, joycon.imu_enabled);
|
||||
Joycon::SetImuConfig(handle, joycon.gyro_sensitivity, joycon.gyro_performance,
|
||||
joycon.accelerometer_sensitivity, joycon.accelerometer_performance);
|
||||
Joycon::SetReportMode(handle, Joycon::ReportMode::STANDARD_FULL_60HZ);
|
||||
}
|
||||
|
||||
if (!input_thread_running) {
|
||||
input_thread =
|
||||
std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
|
||||
}
|
||||
}
|
||||
|
||||
bool Joycons::DeviceConnected(std::size_t port) const {
|
||||
if (port > joycon_data.size()) {
|
||||
return false;
|
||||
}
|
||||
return joycon_data[port].type != Joycon::ControllerType::None;
|
||||
}
|
||||
|
||||
f32 Joycons::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;
|
||||
}
|
||||
|
||||
void Joycons::UpdateLeftPadInput(JoyconData& jc, std::span<const u8> buffer) {
|
||||
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 auto identifier = GetIdentifier(jc.port);
|
||||
|
||||
const u32 raw_button = static_cast<u32>(buffer[5] | ((buffer[4] & 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]);
|
||||
SetButton(identifier, button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_left_axis_x = static_cast<u16>(buffer[6] | ((buffer[7] & 0xf) << 8));
|
||||
const u16 raw_left_axis_y = static_cast<u16>((buffer[7] >> 4) | (buffer[8] << 4));
|
||||
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, jc.calibration.left_stick.x);
|
||||
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, jc.calibration.left_stick.y);
|
||||
|
||||
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX), left_axis_x);
|
||||
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY), left_axis_y);
|
||||
|
||||
if (jc.imu_enabled) {
|
||||
auto left_motion = GetMotionInput(buffer, jc.calibration.imu, jc.accelerometer_sensitivity,
|
||||
jc.gyro_sensitivity);
|
||||
// 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;
|
||||
SetMotion(identifier, static_cast<int>(PadMotion::LeftMotion), left_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void Joycons::UpdateRightPadInput(JoyconData& jc, std::span<const u8> buffer) {
|
||||
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 auto identifier = GetIdentifier(jc.port);
|
||||
|
||||
const u32 raw_button = static_cast<u32>((buffer[3] << 8) | (buffer[4] << 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]);
|
||||
SetButton(identifier, button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_right_axis_x = static_cast<u16>(buffer[9] | ((buffer[10] & 0xf) << 8));
|
||||
const u16 raw_right_axis_y = static_cast<u16>((buffer[10] >> 4) | (buffer[11] << 4));
|
||||
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, jc.calibration.right_stick.x);
|
||||
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, jc.calibration.right_stick.y);
|
||||
SetAxis(identifier, static_cast<int>(PadAxes::RightStickX), right_axis_x);
|
||||
SetAxis(identifier, static_cast<int>(PadAxes::RightStickY), right_axis_y);
|
||||
|
||||
if (jc.imu_enabled) {
|
||||
auto right_motion = GetMotionInput(buffer, jc.calibration.imu, jc.accelerometer_sensitivity,
|
||||
jc.gyro_sensitivity);
|
||||
// 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;
|
||||
SetMotion(identifier, static_cast<int>(PadMotion::RightMotion), right_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void Joycons::UpdateProPadInput(JoyconData& jc, std::span<const u8> buffer) {
|
||||
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 auto identifier = GetIdentifier(jc.port);
|
||||
|
||||
const u32 raw_button = static_cast<u32>(buffer[5] | (buffer[3] << 8) | (buffer[4] << 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]);
|
||||
SetButton(identifier, button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_left_axis_x = static_cast<u16>(buffer[6] | ((buffer[7] & 0xf) << 8));
|
||||
const u16 raw_left_axis_y = static_cast<u16>((buffer[7] >> 4) | (buffer[8] << 4));
|
||||
const u16 raw_right_axis_x = static_cast<u16>(buffer[9] | ((buffer[10] & 0xf) << 8));
|
||||
const u16 raw_right_axis_y = static_cast<u16>((buffer[10] >> 4) | (buffer[11] << 4));
|
||||
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, jc.calibration.left_stick.x);
|
||||
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, jc.calibration.left_stick.y);
|
||||
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, jc.calibration.right_stick.x);
|
||||
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, jc.calibration.right_stick.y);
|
||||
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX), left_axis_x);
|
||||
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY), left_axis_y);
|
||||
SetAxis(identifier, static_cast<int>(PadAxes::RightStickX), right_axis_x);
|
||||
SetAxis(identifier, static_cast<int>(PadAxes::RightStickY), right_axis_y);
|
||||
|
||||
if (jc.imu_enabled) {
|
||||
auto pro_motion = GetMotionInput(buffer, jc.calibration.imu, jc.accelerometer_sensitivity,
|
||||
jc.gyro_sensitivity);
|
||||
pro_motion.accel_y = -pro_motion.accel_y;
|
||||
pro_motion.accel_z = -pro_motion.accel_z;
|
||||
SetMotion(identifier, static_cast<int>(PadMotion::ProMotion), pro_motion);
|
||||
}
|
||||
}
|
||||
|
||||
f32 Joycons::GetAccelerometerValue(s16 raw, Joycon::ImuSensorCalibration cal,
|
||||
Joycon::AccelerometerSensitivity sen) const {
|
||||
const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
|
||||
switch (sen) {
|
||||
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 Joycons::GetGyroValue(s16 raw, Joycon::ImuSensorCalibration cal,
|
||||
Joycon::GyroSensitivity sen) const {
|
||||
const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
|
||||
switch (sen) {
|
||||
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 Joycons::GetRawIMUValues(std::size_t sensor, size_t axis, std::span<const u8> buffer) const {
|
||||
const size_t offset = (sensor * 6) + (axis * 2);
|
||||
return static_cast<s16>(buffer[13 + offset] | (buffer[14 + offset] << 8));
|
||||
}
|
||||
|
||||
BasicMotion Joycons::GetMotionInput(std::span<const u8> buffer, Joycon::ImuCalibration calibration,
|
||||
Joycon::AccelerometerSensitivity accel_sensitivity,
|
||||
Joycon::GyroSensitivity gyro_sensitivity) const {
|
||||
std::array<BasicMotion, 3> motion_data{};
|
||||
for (std::size_t sample = 0; sample < motion_data.size(); ++sample) {
|
||||
auto& motion = motion_data[sample];
|
||||
const auto& accel_cal = calibration.accelerometer;
|
||||
const auto& gyro_cal = calibration.gyro;
|
||||
const std::size_t sample_index = sample * 2;
|
||||
const s16 raw_accel_x = GetRawIMUValues(sample_index, 1, buffer);
|
||||
const s16 raw_accel_y = GetRawIMUValues(sample_index, 0, buffer);
|
||||
const s16 raw_accel_z = GetRawIMUValues(sample_index, 2, buffer);
|
||||
const s16 raw_gyro_x = GetRawIMUValues(sample_index + 1, 1, buffer);
|
||||
const s16 raw_gyro_y = GetRawIMUValues(sample_index + 1, 0, buffer);
|
||||
const s16 raw_gyro_z = GetRawIMUValues(sample_index + 1, 2, buffer);
|
||||
|
||||
motion.delta_timestamp = 15000;
|
||||
motion.accel_x = GetAccelerometerValue(raw_accel_x, accel_cal[1], accel_sensitivity);
|
||||
motion.accel_y = GetAccelerometerValue(raw_accel_y, accel_cal[0], accel_sensitivity);
|
||||
motion.accel_z = GetAccelerometerValue(raw_accel_z, accel_cal[2], accel_sensitivity);
|
||||
motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], gyro_sensitivity);
|
||||
motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], gyro_sensitivity);
|
||||
motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], gyro_sensitivity);
|
||||
}
|
||||
|
||||
// TODO(German77): Return all three samples data
|
||||
return motion_data[0];
|
||||
}
|
||||
|
||||
Common::Input::VibrationError Joycons::SetRumble(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_amplitude,
|
||||
};
|
||||
Joycon::SetVibration(GetHandle(identifier), native_vibration);
|
||||
return Common::Input::VibrationError::None;
|
||||
}
|
||||
|
||||
Joycon::JoyconHandle& Joycons::GetHandle(PadIdentifier identifier) {
|
||||
return joycon_data[identifier.port].joycon_handle;
|
||||
}
|
||||
|
||||
PadIdentifier Joycons::GetIdentifier(std::size_t port) const {
|
||||
return {
|
||||
.guid = Common::UUID{Common::InvalidUUID},
|
||||
.port = port,
|
||||
.pad = 0,
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices;
|
||||
for (const auto& controller : joycon_data) {
|
||||
if (DeviceConnected(controller.port)) {
|
||||
std::string name = fmt::format("{} {}", JoyconName(controller.port), controller.port);
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
{"engine", GetEngineName()},
|
||||
{"display", std::move(name)},
|
||||
{"port", std::to_string(controller.port)},
|
||||
});
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
|
||||
static constexpr std::array<std::pair<Settings::NativeButton::Values, Joycon::PadButton>, 20>
|
||||
switch_to_joycon_button = {
|
||||
std::pair{Settings::NativeButton::A, Joycon::PadButton::A},
|
||||
{Settings::NativeButton::B, Joycon::PadButton::B},
|
||||
{Settings::NativeButton::X, Joycon::PadButton::X},
|
||||
{Settings::NativeButton::Y, Joycon::PadButton::Y},
|
||||
{Settings::NativeButton::DLeft, Joycon::PadButton::Left},
|
||||
{Settings::NativeButton::DUp, Joycon::PadButton::Up},
|
||||
{Settings::NativeButton::DRight, Joycon::PadButton::Right},
|
||||
{Settings::NativeButton::DDown, Joycon::PadButton::Down},
|
||||
{Settings::NativeButton::SL, Joycon::PadButton::LeftSL},
|
||||
{Settings::NativeButton::SR, Joycon::PadButton::LeftSR},
|
||||
{Settings::NativeButton::L, Joycon::PadButton::L},
|
||||
{Settings::NativeButton::R, Joycon::PadButton::R},
|
||||
{Settings::NativeButton::ZL, Joycon::PadButton::ZL},
|
||||
{Settings::NativeButton::ZR, Joycon::PadButton::ZR},
|
||||
{Settings::NativeButton::Plus, Joycon::PadButton::Plus},
|
||||
{Settings::NativeButton::Minus, Joycon::PadButton::Minus},
|
||||
{Settings::NativeButton::Home, Joycon::PadButton::Home},
|
||||
{Settings::NativeButton::Screenshot, Joycon::PadButton::Capture},
|
||||
{Settings::NativeButton::LStick, Joycon::PadButton::StickL},
|
||||
{Settings::NativeButton::RStick, Joycon::PadButton::StickR},
|
||||
};
|
||||
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ButtonMapping mapping{};
|
||||
for (const auto& [switch_button, joycon_button] : switch_to_joycon_button) {
|
||||
Common::ParamPackage button_params{};
|
||||
button_params.Set("engine", GetEngineName());
|
||||
button_params.Set("port", params.Get("port", 0));
|
||||
button_params.Set("button", static_cast<int>(joycon_button));
|
||||
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
AnalogMapping mapping = {};
|
||||
Common::ParamPackage left_analog_params;
|
||||
left_analog_params.Set("engine", GetEngineName());
|
||||
left_analog_params.Set("port", params.Get("port", 0));
|
||||
left_analog_params.Set("axis_x", static_cast<int>(PadAxes::LeftStickX));
|
||||
left_analog_params.Set("axis_y", static_cast<int>(PadAxes::LeftStickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
|
||||
Common::ParamPackage right_analog_params;
|
||||
right_analog_params.Set("engine", GetEngineName());
|
||||
right_analog_params.Set("port", params.Get("port", 0));
|
||||
right_analog_params.Set("axis_x", static_cast<int>(PadAxes::RightStickX));
|
||||
right_analog_params.Set("axis_y", static_cast<int>(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 {};
|
||||
}
|
||||
|
||||
MotionMapping mapping = {};
|
||||
Common::ParamPackage left_motion_params;
|
||||
left_motion_params.Set("engine", GetEngineName());
|
||||
left_motion_params.Set("port", params.Get("port", 0));
|
||||
left_motion_params.Set("motion", 0);
|
||||
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
|
||||
Common::ParamPackage right_Motion_params;
|
||||
right_Motion_params.Set("engine", GetEngineName());
|
||||
right_Motion_params.Set("port", params.Get("port", 0));
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
||||
126
src/input_common/drivers/joycon.h
Normal file
126
src/input_common/drivers/joycon.h
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <thread>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol.h"
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class Joycons final : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit Joycons(const std::string& input_engine_);
|
||||
|
||||
~Joycons();
|
||||
|
||||
/// 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;
|
||||
|
||||
Common::Input::VibrationError SetRumble(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||
|
||||
private:
|
||||
enum class PadAxes {
|
||||
LeftStickX,
|
||||
LeftStickY,
|
||||
RightStickX,
|
||||
RightStickY,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class PadMotion {
|
||||
LeftMotion,
|
||||
RightMotion,
|
||||
ProMotion,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
struct JoyconData {
|
||||
Joycon::JoyconHandle joycon_handle{nullptr, 0};
|
||||
|
||||
// Harware config
|
||||
Joycon::GyroSensitivity gyro_sensitivity;
|
||||
Joycon::GyroPerformance gyro_performance;
|
||||
Joycon::AccelerometerSensitivity accelerometer_sensitivity;
|
||||
Joycon::AccelerometerPerformance accelerometer_performance;
|
||||
Joycon::ReportMode mode;
|
||||
bool imu_enabled;
|
||||
bool rumble_enabled;
|
||||
u8 leds;
|
||||
|
||||
// Fixed value info
|
||||
f32 version;
|
||||
std::array<u8, 6> mac;
|
||||
Joycon::CalibrationData calibration;
|
||||
Joycon::ControllerType type;
|
||||
std::array<u8, 15> serial_number;
|
||||
Joycon::Color color;
|
||||
std::size_t port;
|
||||
|
||||
// Polled data
|
||||
u32 raw_temperature;
|
||||
u8 raw_battery;
|
||||
Common::Input::VibrationStatus hd_rumble;
|
||||
};
|
||||
void InputThread(std::stop_token stop_token);
|
||||
void ScanThread(std::stop_token stop_token);
|
||||
|
||||
/// Resets status of device connected to port
|
||||
void ResetDeviceType(std::size_t port);
|
||||
|
||||
/// Captures JC Adapter endpoint address,
|
||||
void GetJCEndpoint();
|
||||
|
||||
/// For shutting down, clear all data, join all threads, release usb
|
||||
void Reset();
|
||||
|
||||
bool IsPayloadCorrect(int status, std::span<const u8> buffer);
|
||||
|
||||
/// For use in initialization, querying devices to find the adapter
|
||||
void Setup();
|
||||
|
||||
bool DeviceConnected(std::size_t port) const;
|
||||
|
||||
void UpdateJoyconData(JoyconData& jc, std::span<const u8> buffer);
|
||||
void UpdateLeftPadInput(JoyconData& jc, std::span<const u8> buffer);
|
||||
void UpdateRightPadInput(JoyconData& jc, std::span<const u8> buffer);
|
||||
void UpdateProPadInput(JoyconData& jc, std::span<const u8> buffer);
|
||||
|
||||
// Reads gyro and accelerometer values
|
||||
f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
|
||||
f32 GetAccelerometerValue(s16 raw_value, Joycon::ImuSensorCalibration cal,
|
||||
Joycon::AccelerometerSensitivity sen) const;
|
||||
f32 GetGyroValue(s16 raw_value, Joycon::ImuSensorCalibration cal,
|
||||
Joycon::GyroSensitivity sen) const;
|
||||
|
||||
s16 GetRawIMUValues(size_t sensor, size_t axis, std::span<const u8> buffer) const;
|
||||
BasicMotion GetMotionInput(std::span<const u8> buffer, Joycon::ImuCalibration calibration,
|
||||
Joycon::AccelerometerSensitivity accel_sensitivity,
|
||||
Joycon::GyroSensitivity gyro_sensitivity) const;
|
||||
|
||||
Joycon::JoyconHandle& GetHandle(PadIdentifier identifier);
|
||||
PadIdentifier GetIdentifier(std::size_t port) const;
|
||||
|
||||
std::string JoyconName(std::size_t port) const;
|
||||
|
||||
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
||||
|
||||
std::jthread input_thread;
|
||||
std::jthread scan_thread;
|
||||
bool input_thread_running{};
|
||||
bool scan_thread_running{};
|
||||
u8 input_error_counter{};
|
||||
|
||||
std::array<JoyconData, 8> joycon_data{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
||||
@@ -410,9 +410,11 @@ 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
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
|
||||
if (Settings::values.enable_sdl_joycons) {
|
||||
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController
|
||||
// and not a generic one
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
|
||||
}
|
||||
|
||||
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
|
||||
// driver on Linux.
|
||||
|
||||
479
src/input_common/helpers/joycon_protocol.cpp
Normal file
479
src/input_common/helpers/joycon_protocol.cpp
Normal file
@@ -0,0 +1,479 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
u8 GetCounter(JoyconHandle& joycon_handle) {
|
||||
joycon_handle.packet_counter = (joycon_handle.packet_counter + 1) & 0x0F;
|
||||
return joycon_handle.packet_counter;
|
||||
}
|
||||
|
||||
ErrorCode CheckDeviceAccess(JoyconHandle& joycon_handle, SDL_hid_device_info* device_info) {
|
||||
ControllerType controller_type{ControllerType::None};
|
||||
const auto result = GetDeviceType(device_info, controller_type);
|
||||
if (result != ErrorCode::Success || controller_type == ControllerType::None) {
|
||||
return ErrorCode::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
joycon_handle.handle =
|
||||
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
|
||||
if (!joycon_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 ErrorCode::HandleInUse;
|
||||
}
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
f32 EncodeRumbleAmplification(f32 amplification) {
|
||||
if (amplification < 0.01182) {
|
||||
return roundf(powf(amplification, 1.7f) * 7561);
|
||||
} else if (amplification < 0.11249) {
|
||||
return roundf((logf(amplification) * 11.556f) + 55.3f);
|
||||
} else if (amplification < 0.22498) {
|
||||
return roundf((log2f(amplification) * 32) + 131);
|
||||
}
|
||||
return roundf((log2f(amplification) * 64) + 200);
|
||||
}
|
||||
|
||||
ErrorCode SetVibration(JoyconHandle& joycon_handle, VibrationValue vibration) {
|
||||
std::vector<u8> buffer(Joycon::max_resp_size);
|
||||
|
||||
buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
|
||||
buffer[1] = GetCounter(joycon_handle);
|
||||
|
||||
if (vibration.high_amplitude == 0.0f && vibration.low_amplitude == 0.0f) {
|
||||
for (std::size_t i = 0; i < Joycon::default_buffer.size(); ++i) {
|
||||
buffer[2 + i] = Joycon::default_buffer[i];
|
||||
}
|
||||
return SendData(joycon_handle, buffer, max_resp_size);
|
||||
}
|
||||
|
||||
const u16 encoded_hf =
|
||||
static_cast<u16>(roundf(128 * log2f(vibration.high_frequency * 0.1f)) - 0x180);
|
||||
const u8 encoded_lf =
|
||||
static_cast<u8>(roundf(32 * log2f(vibration.low_frequency * 0.1f)) - 0x40);
|
||||
const u8 encoded_hamp = static_cast<u8>(EncodeRumbleAmplification(vibration.high_amplitude));
|
||||
const u8 encoded_lamp = static_cast<u8>(EncodeRumbleAmplification(vibration.low_amplitude));
|
||||
|
||||
for (u8 i = 0; i < 2; ++i) {
|
||||
const u8 amplitude = i == 0 ? encoded_lamp : encoded_hamp;
|
||||
const u8 offset = i * 4;
|
||||
u32 encoded_amp = amplitude >> 1;
|
||||
const u32 parity = (encoded_amp % 2) * 0x80;
|
||||
encoded_amp >>= 1;
|
||||
encoded_amp += 0x40;
|
||||
|
||||
buffer[2 + offset] = static_cast<u8>(encoded_hf & 0xff);
|
||||
buffer[3 + offset] = static_cast<u8>((encoded_hf >> 8) & 0xff);
|
||||
buffer[4 + offset] = static_cast<u8>(encoded_lf & 0xff);
|
||||
|
||||
buffer[3 + offset] |= static_cast<u8>(amplitude);
|
||||
buffer[4 + offset] |= static_cast<u8>(parity);
|
||||
buffer[5 + offset] = static_cast<u8>(encoded_amp);
|
||||
}
|
||||
|
||||
return SendData(joycon_handle, buffer, max_resp_size);
|
||||
}
|
||||
|
||||
ErrorCode SetImuConfig(JoyconHandle& joycon_handle, GyroSensitivity gsen, GyroPerformance gfrec,
|
||||
AccelerometerSensitivity asen, AccelerometerPerformance afrec) {
|
||||
const std::vector<u8> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
|
||||
static_cast<u8>(gfrec), static_cast<u8>(afrec)};
|
||||
std::vector<u8> output;
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
const auto result = SendSubCommand(joycon_handle, SubCommand::SET_IMU_SENSITIVITY, buffer,
|
||||
buffer.size(), output);
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode SetLedConfig(JoyconHandle& joycon_handle, u8 leds) {
|
||||
const std::vector<u8> buffer{leds};
|
||||
std::vector<u8> output;
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
const auto result =
|
||||
SendSubCommand(joycon_handle, SubCommand::SET_PLAYER_LIGHTS, buffer, buffer.size(), output);
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode SetReportMode(JoyconHandle& joycon_handle, ReportMode report_mode) {
|
||||
const std::vector<u8> buffer{static_cast<u8>(report_mode)};
|
||||
std::vector<u8> output;
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
const auto result =
|
||||
SendSubCommand(joycon_handle, SubCommand::SET_REPORT_MODE, buffer, buffer.size(), output);
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode GetColor(JoyconHandle& joycon_handle, Color& color) {
|
||||
std::vector<u8> buffer;
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
const auto result = ReadSPI(joycon_handle, Joycon::CalAddr::COLOR_DATA, buffer, 12);
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
color.body = static_cast<u32>((buffer[2] << 16) | (buffer[1] << 8) | buffer[0]);
|
||||
color.buttons = static_cast<u32>((buffer[5] << 16) | (buffer[4] << 8) | buffer[3]);
|
||||
color.left_grip = static_cast<u32>((buffer[8] << 16) | (buffer[7] << 8) | buffer[6]);
|
||||
color.right_grip = static_cast<u32>((buffer[11] << 16) | (buffer[10] << 8) | buffer[9]);
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetMacAddress(JoyconHandle& joycon_handle, MacAddress& mac_address) {
|
||||
wchar_t buffer[255];
|
||||
const auto result =
|
||||
SDL_hid_get_serial_number_string(joycon_handle.handle, buffer, std::size(buffer));
|
||||
if (result == -1) {
|
||||
LOG_ERROR(Input, "Unable to get mac address {}", result);
|
||||
return ErrorCode::ErrorReadingData;
|
||||
}
|
||||
for (std::size_t i = 0; i < mac_address.size(); ++i) {
|
||||
wchar_t value[3] = {buffer[i * 2], buffer[(i * 2) + 1]};
|
||||
mac_address[i] = static_cast<u8>(std::stoi(value, 0, 16));
|
||||
}
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetSerialNumber(JoyconHandle& joycon_handle, SerialNumber& serial_number) {
|
||||
std::vector<u8> buffer;
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
const auto result = ReadSPI(joycon_handle, Joycon::CalAddr::SERIAL_NUMBER, buffer, 16);
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
for (std::size_t i = 0; i < serial_number.size(); ++i) {
|
||||
serial_number[i] = buffer[i + 1];
|
||||
}
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetDeviceType(SDL_hid_device_info* device_info, ControllerType& controller_type) {
|
||||
constexpr u16 nintendo_vendor_id = 0x057e;
|
||||
constexpr u16 left_joycon_id = 0x2006;
|
||||
constexpr u16 right_joycon_id = 0x2007;
|
||||
constexpr u16 pro_controller_id = 0x2009;
|
||||
|
||||
if (device_info->vendor_id != nintendo_vendor_id) {
|
||||
return ErrorCode::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
switch (device_info->product_id) {
|
||||
case left_joycon_id:
|
||||
controller_type = ControllerType::Left;
|
||||
break;
|
||||
case right_joycon_id:
|
||||
controller_type = ControllerType::Right;
|
||||
break;
|
||||
case pro_controller_id:
|
||||
controller_type = ControllerType::Pro;
|
||||
break;
|
||||
default:
|
||||
controller_type = ControllerType::None;
|
||||
break;
|
||||
}
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetDeviceType(JoyconHandle& joycon_handle, ControllerType& controller_type) {
|
||||
std::vector<u8> buffer;
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
const auto result = ReadSPI(joycon_handle, Joycon::CalAddr::DEVICE_TYPE, buffer, 1);
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
controller_type = static_cast<Joycon::ControllerType>(buffer[0]);
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetVersionNumber([[maybe_unused]] JoyconHandle& joycon_handle, FirmwareVersion& version) {
|
||||
// Not implemented
|
||||
version = FirmwareVersion::Rev_0;
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetLeftJoyStickCalibration(JoyconHandle& joycon_handle, JoyStickCalibration& calibration,
|
||||
bool is_factory_calibration) {
|
||||
std::vector<u8> buffer;
|
||||
ErrorCode result{ErrorCode::Unknown};
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
if (is_factory_calibration) {
|
||||
result = ReadSPI(joycon_handle, CalAddr::FACT_LEFT_DATA, buffer, 9);
|
||||
} else {
|
||||
result = ReadSPI(joycon_handle, CalAddr::USER_LEFT_DATA, buffer, 9);
|
||||
}
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
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]);
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetRightJoyStickCalibration(JoyconHandle& joycon_handle, JoyStickCalibration& calibration,
|
||||
bool is_factory_calibration) {
|
||||
std::vector<u8> buffer;
|
||||
ErrorCode result{ErrorCode::Unknown};
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
if (is_factory_calibration) {
|
||||
result = ReadSPI(joycon_handle, CalAddr::FACT_RIGHT_DATA, buffer, 9);
|
||||
} else {
|
||||
result = ReadSPI(joycon_handle, CalAddr::USER_RIGHT_DATA, buffer, 9);
|
||||
}
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
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]);
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetImuCalibration(JoyconHandle& joycon_handle, ImuCalibration& calibration,
|
||||
bool is_factory_calibration) {
|
||||
std::vector<u8> buffer;
|
||||
ErrorCode result{ErrorCode::Unknown};
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
if (is_factory_calibration) {
|
||||
result = ReadSPI(joycon_handle, CalAddr::FACT_IMU_DATA, buffer, 24);
|
||||
} else {
|
||||
result = ReadSPI(joycon_handle, CalAddr::USER_IMU_DATA, buffer, 24);
|
||||
}
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < calibration.accelerometer.size(); ++i) {
|
||||
const std::size_t index = i * 2;
|
||||
calibration.accelerometer[i].offset =
|
||||
static_cast<s16>(buffer[index] | (buffer[index + 1] << 8));
|
||||
calibration.accelerometer[i].scale =
|
||||
static_cast<s16>(buffer[index + 6] | (buffer[index + 7] << 8));
|
||||
}
|
||||
for (std::size_t i = 0; i < calibration.gyro.size(); ++i) {
|
||||
const std::size_t index = i * 2;
|
||||
calibration.gyro[i].offset =
|
||||
static_cast<s16>(buffer[index + 12] | (buffer[index + 13] << 8));
|
||||
calibration.gyro[i].scale =
|
||||
static_cast<s16>(buffer[index + 18] | (buffer[index + 19] << 8));
|
||||
}
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetUserCalibrationData(JoyconHandle& joycon_handle, CalibrationData& calibration_data,
|
||||
ControllerType controller_type) {
|
||||
ErrorCode result{ErrorCode::Unknown};
|
||||
switch (controller_type) {
|
||||
case ControllerType::Left:
|
||||
result = GetLeftJoyStickCalibration(joycon_handle, calibration_data.left_stick, false);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
case ControllerType::Right:
|
||||
result = GetRightJoyStickCalibration(joycon_handle, calibration_data.right_stick, false);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
result = GetLeftJoyStickCalibration(joycon_handle, calibration_data.left_stick, false);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
result = GetRightJoyStickCalibration(joycon_handle, calibration_data.right_stick, false);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ErrorCode::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
result = GetImuCalibration(joycon_handle, calibration_data.imu, false);
|
||||
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetFactoryCalibrationData(JoyconHandle& joycon_handle, CalibrationData& calibration_data,
|
||||
ControllerType controller_type) {
|
||||
ErrorCode result{ErrorCode::Unknown};
|
||||
switch (controller_type) {
|
||||
case ControllerType::Left:
|
||||
result = GetLeftJoyStickCalibration(joycon_handle, calibration_data.left_stick, true);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
case ControllerType::Right:
|
||||
result = GetRightJoyStickCalibration(joycon_handle, calibration_data.right_stick, true);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
result = GetLeftJoyStickCalibration(joycon_handle, calibration_data.left_stick, true);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
result = GetRightJoyStickCalibration(joycon_handle, calibration_data.right_stick, true);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ErrorCode::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
result = GetImuCalibration(joycon_handle, calibration_data.imu, true);
|
||||
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode EnableImu(JoyconHandle& joycon_handle, bool enable) {
|
||||
const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
|
||||
std::vector<u8> output;
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
const auto result =
|
||||
SendSubCommand(joycon_handle, SubCommand::ENABLE_IMU, buffer, buffer.size(), output);
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode EnableRumble(JoyconHandle& joycon_handle, bool enable) {
|
||||
const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
|
||||
std::vector<u8> output;
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
|
||||
const auto result =
|
||||
SendSubCommand(joycon_handle, SubCommand::ENABLE_VIBRATION, buffer, buffer.size(), output);
|
||||
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode SendData(JoyconHandle& joycon_handle, std::span<const u8> buffer, std::size_t size) {
|
||||
const auto result = SDL_hid_write(joycon_handle.handle, buffer.data(), size);
|
||||
if (result == -1) {
|
||||
return ErrorCode::ErrorWritingData;
|
||||
}
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode GetResponse(JoyconHandle& joycon_handle, SubCommand sc, std::vector<u8>& output) {
|
||||
constexpr int timeout_mili = 100;
|
||||
constexpr int max_tries = 10;
|
||||
int tries = 0;
|
||||
std::vector<u8> buffer(max_resp_size);
|
||||
do {
|
||||
int result =
|
||||
SDL_hid_read_timeout(joycon_handle.handle, buffer.data(), max_resp_size, timeout_mili);
|
||||
if (result < 1) {
|
||||
LOG_ERROR(Input, "No response from joycon");
|
||||
}
|
||||
tries++;
|
||||
} while (tries < max_tries && buffer[0] != 0x21 && buffer[14] != static_cast<u8>(sc));
|
||||
|
||||
if (tries >= max_tries) {
|
||||
return ErrorCode::Timeout;
|
||||
}
|
||||
if (buffer[0] != 0x21 && buffer[14] != static_cast<u8>(sc)) {
|
||||
return ErrorCode::WrongReply;
|
||||
}
|
||||
|
||||
output = buffer;
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode SendSubCommand(JoyconHandle& joycon_handle, SubCommand sc, std::span<const u8> buffer,
|
||||
std::size_t size, std::vector<u8>& output) {
|
||||
std::vector<u8> local_buffer(size + 11);
|
||||
|
||||
local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
|
||||
local_buffer[1] = GetCounter(joycon_handle);
|
||||
for (std::size_t i = 0; i < 8; ++i) {
|
||||
local_buffer[i + 2] = default_buffer[i];
|
||||
}
|
||||
local_buffer[10] = static_cast<u8>(sc);
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
local_buffer[11 + i] = buffer[i];
|
||||
}
|
||||
|
||||
auto result = SendData(joycon_handle, local_buffer, size + 11);
|
||||
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = GetResponse(joycon_handle, sc, output);
|
||||
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
ErrorCode ReadSPI(JoyconHandle& joycon_handle, CalAddr addr, std::vector<u8>& output, u8 size) {
|
||||
constexpr std::size_t max_tries = 10;
|
||||
std::size_t tries = 0;
|
||||
std::vector<u8> 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(joycon_handle, SubCommand::SPI_FLASH_READ, buffer,
|
||||
buffer.size(), local_buffer);
|
||||
if (result != ErrorCode::Success) {
|
||||
return result;
|
||||
}
|
||||
tries++;
|
||||
} while (tries < max_tries && (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]));
|
||||
|
||||
if (tries >= max_tries) {
|
||||
return ErrorCode::Timeout;
|
||||
}
|
||||
|
||||
output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.end());
|
||||
return ErrorCode::Success;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
||||
425
src/input_common/helpers/joycon_protocol.h
Normal file
425
src/input_common/helpers/joycon_protocol.h
Normal file
@@ -0,0 +1,425 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
// Based on dkms-hid-nintendo implementation and dekuNukem reverse engineering
|
||||
// https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <SDL_hidapi.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
constexpr u32 max_resp_size = 49;
|
||||
constexpr std::array<u8, 8> default_buffer{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,
|
||||
};
|
||||
|
||||
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,
|
||||
RightSL = 0x001000,
|
||||
RightSR = 0x002000,
|
||||
R = 0x004000,
|
||||
ZR = 0x008000,
|
||||
Minus = 0x010000,
|
||||
Plus = 0x020000,
|
||||
StickR = 0x040000,
|
||||
StickL = 0x080000,
|
||||
Home = 0x100000,
|
||||
Capture = 0x200000,
|
||||
};
|
||||
|
||||
enum class OutputReport {
|
||||
RUMBLE_AND_SUBCMD = 0x01,
|
||||
FW_UPDATE_PKT = 0x03,
|
||||
RUMBLE_ONLY = 0x10,
|
||||
MCU_DATA = 0x11,
|
||||
USB_CMD = 0x80,
|
||||
};
|
||||
|
||||
enum class InputReport {
|
||||
BUTTON_EVENT = 0x3F,
|
||||
SUBCMD_REPLY = 0x21,
|
||||
IMU_DATA = 0x30,
|
||||
MCU_DATA = 0x31,
|
||||
INPUT_USB_RESPONSE = 0x81,
|
||||
};
|
||||
|
||||
enum class FeatureReport {
|
||||
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 {
|
||||
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,
|
||||
};
|
||||
|
||||
enum class UsbSubCommand {
|
||||
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 {
|
||||
USR_MAGIC_0 = 0xB2,
|
||||
USR_MAGIC_1 = 0xA1,
|
||||
USRR_MAGI_SIZE = 2,
|
||||
};
|
||||
|
||||
enum class CalAddr {
|
||||
USER_LEFT_MAGIC = 0X8010,
|
||||
USER_LEFT_DATA = 0X8012,
|
||||
USER_RIGHT_MAGIC = 0X801B,
|
||||
USER_RIGHT_DATA = 0X801D,
|
||||
USER_IMU_MAGIC = 0X8026,
|
||||
USER_IMU_DATA = 0X8028,
|
||||
SERIAL_NUMBER = 0X6000,
|
||||
DEVICE_TYPE = 0X6012,
|
||||
COLOR_EXIST = 0X601B,
|
||||
FACT_LEFT_DATA = 0X603d,
|
||||
FACT_RIGHT_DATA = 0X6046,
|
||||
COLOR_DATA = 0X6050,
|
||||
FACT_IMU_DATA = 0X6020,
|
||||
};
|
||||
|
||||
enum class ReportMode {
|
||||
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 {
|
||||
DPS250,
|
||||
DPS500,
|
||||
DPS1000,
|
||||
DPS2000, // Default
|
||||
};
|
||||
|
||||
enum class AccelerometerSensitivity {
|
||||
G8, // Default
|
||||
G4,
|
||||
G2,
|
||||
G16,
|
||||
};
|
||||
|
||||
enum class GyroPerformance {
|
||||
HZ833,
|
||||
HZ208, // Default
|
||||
};
|
||||
|
||||
enum class AccelerometerPerformance {
|
||||
HZ200,
|
||||
HZ100, // Default
|
||||
};
|
||||
|
||||
enum class FirmwareVersion {
|
||||
Rev_0,
|
||||
};
|
||||
|
||||
enum class ErrorCode {
|
||||
Success,
|
||||
WrongReply,
|
||||
Timeout,
|
||||
UnsupportedControllerType,
|
||||
HandleInUse,
|
||||
ErrorReadingData,
|
||||
ErrorWritingData,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct ImuSensorCalibration {
|
||||
s16 offset;
|
||||
s16 scale;
|
||||
};
|
||||
|
||||
struct ImuCalibration {
|
||||
std::array<ImuSensorCalibration, 3> accelerometer;
|
||||
std::array<ImuSensorCalibration, 3> gyro;
|
||||
};
|
||||
|
||||
struct JoyStickAxisCalibration {
|
||||
u16 max;
|
||||
u16 min;
|
||||
u16 center;
|
||||
};
|
||||
|
||||
struct JoyStickCalibration {
|
||||
JoyStickAxisCalibration x;
|
||||
JoyStickAxisCalibration y;
|
||||
};
|
||||
|
||||
struct CalibrationData {
|
||||
JoyStickCalibration left_stick;
|
||||
JoyStickCalibration right_stick;
|
||||
ImuCalibration imu;
|
||||
};
|
||||
|
||||
struct Color {
|
||||
u32 body;
|
||||
u32 buttons;
|
||||
u32 left_grip;
|
||||
u32 right_grip;
|
||||
};
|
||||
|
||||
struct VibrationValue {
|
||||
f32 low_amplitude;
|
||||
f32 low_frequency;
|
||||
f32 high_amplitude;
|
||||
f32 high_frequency;
|
||||
};
|
||||
|
||||
struct JoyconHandle {
|
||||
SDL_hid_device* handle;
|
||||
u8 packet_counter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Increments and returns the packet counter of the handle
|
||||
* @param joycon_handle device to send the data
|
||||
* @returns packet counter value
|
||||
*/
|
||||
u8 GetCounter(JoyconHandle& joycon_handle);
|
||||
|
||||
/**
|
||||
* Verifies and sets the joycon_handle if device is valid
|
||||
* @param joycon_handle device to send the data
|
||||
* @param device info from the driver
|
||||
* @returns true if the device is valid
|
||||
*/
|
||||
ErrorCode CheckDeviceAccess(JoyconHandle& joycon_handle, SDL_hid_device_info* device);
|
||||
|
||||
/**
|
||||
* Encondes the amplitude to be sended on a packet
|
||||
* @param amplification original amplification value
|
||||
* @returns encoded amplification value
|
||||
*/
|
||||
f32 EncodeRumbleAmplification(f32 amplification);
|
||||
|
||||
/**
|
||||
* Sends a request to set the vibration of the joycon
|
||||
* @param joycon_handle device to send the data
|
||||
* @param vibration amplitude and frequency of the vibration
|
||||
*/
|
||||
ErrorCode SetVibration(JoyconHandle& joycon_handle, VibrationValue vibration);
|
||||
|
||||
/**
|
||||
* Sends a request to set the configuration of the motion sensor
|
||||
* @param joycon_handle device to send the data
|
||||
* @param gsen gyro sensitivity
|
||||
* @param gfrec gyro update frequency
|
||||
* @param asen accelerometer sensitivity
|
||||
* @param afrec accelerometer update frequency
|
||||
*/
|
||||
ErrorCode SetImuConfig(JoyconHandle& joycon_handle, GyroSensitivity gsen, GyroPerformance gfrec,
|
||||
AccelerometerSensitivity asen, AccelerometerPerformance afrec);
|
||||
|
||||
/**
|
||||
* Sends a request to set the polling mode of the joycon
|
||||
* @param joycon_handle device to send the data
|
||||
* @param report_mode polling mode to be set
|
||||
*/
|
||||
ErrorCode SetReportMode(JoyconHandle& joycon_handle, Joycon::ReportMode report_mode);
|
||||
|
||||
/**
|
||||
* Sends a request to set a specific led pattern
|
||||
* @param joycon_handle device to send the data
|
||||
* @param leds led pattern to be set
|
||||
*/
|
||||
ErrorCode SetLedConfig(JoyconHandle& joycon_handle, u8 leds);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon colors from memory
|
||||
* @param joycon_handle device to read the data
|
||||
* @returns color object with the colors of the joycon
|
||||
*/
|
||||
ErrorCode GetColor(JoyconHandle& joycon_handle, Color& color);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon mac address from handle
|
||||
* @param joycon_handle device to read the data
|
||||
* @returns array containing the mac address
|
||||
*/
|
||||
ErrorCode GetMacAddress(JoyconHandle& joycon_handle, MacAddress& mac_address);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon serial number from memory
|
||||
* @param joycon_handle device to read the data
|
||||
* @returns array containing the serial number
|
||||
*/
|
||||
ErrorCode GetSerialNumber(JoyconHandle& joycon_handle, SerialNumber& serial_number);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon type from handle
|
||||
* @param joycon_handle device to read the data
|
||||
* @returns controller type of the joycon
|
||||
*/
|
||||
ErrorCode GetDeviceType(JoyconHandle& joycon_handle, ControllerType& controller_type);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon tyoe from memory
|
||||
* @param joycon_handle device to read the data
|
||||
* @returns controller type of the joycon
|
||||
*/
|
||||
ErrorCode GetDeviceType(SDL_hid_device_info* device_info, ControllerType& controller_type);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon firmware version
|
||||
* @param joycon_handle device to read the data
|
||||
* @returns u16 with the version number
|
||||
*/
|
||||
ErrorCode GetVersionNumber(JoyconHandle& joycon_handle, FirmwareVersion& version);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the left stick calibration from memory
|
||||
* @param joycon_handle device to read the data
|
||||
* @param is_factory_calibration if true factory values will be returned
|
||||
* @returns JoyStickCalibration of the left joystick
|
||||
*/
|
||||
ErrorCode GetLeftJoyStickCalibration(JoyconHandle& joycon_handle, JoyStickCalibration& calibration,
|
||||
bool is_factory_calibration);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the right stick calibration from memory
|
||||
* @param joycon_handle device to read the data
|
||||
* @param is_factory_calibration if true factory values will be returned
|
||||
* @returns JoyStickCalibration of the left joystick
|
||||
*/
|
||||
ErrorCode GetRightJoyStickCalibration(JoyconHandle& joycon_handle, JoyStickCalibration& calibration,
|
||||
bool is_factory_calibration);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the motion calibration from memory
|
||||
* @param joycon_handle device to read the data
|
||||
* @param is_factory_calibration if true factory values will be returned
|
||||
* @returns ImuCalibration of the joystick motion
|
||||
*/
|
||||
ErrorCode GetImuCalibration(JoyconHandle& joycon_handle, ImuCalibration& calibration,
|
||||
bool is_factory_calibration);
|
||||
|
||||
/**
|
||||
* Requests user calibration from the joystick
|
||||
* @param joycon_handle device to read the data
|
||||
* @param controller_type type of calibration to be requested
|
||||
* @returns User CalibrationData of the joystick
|
||||
*/
|
||||
ErrorCode GetUserCalibrationData(JoyconHandle& joycon_handle, CalibrationData& calibration_data,
|
||||
ControllerType controller_type);
|
||||
|
||||
/**
|
||||
* Requests factory calibration from the joystick
|
||||
* @param joycon_handle device to read the data
|
||||
* @param controller_type type of calibration to be requested
|
||||
* @returns Factory CalibrationData of the joystick
|
||||
*/
|
||||
ErrorCode GetFactoryCalibrationData(JoyconHandle& joycon_handle, CalibrationData& calibration_data,
|
||||
ControllerType controller_type);
|
||||
|
||||
/**
|
||||
* Sends a request to enable motion
|
||||
* @param joycon_handle device to read the data
|
||||
* @param is_factory_calibration if true motion data will be enabled
|
||||
*/
|
||||
ErrorCode EnableImu(JoyconHandle& joycon_handle, bool enable);
|
||||
|
||||
/**
|
||||
* Sends a request to enable vibrations
|
||||
* @param joycon_handle device to read the data
|
||||
* @param is_factory_calibration if true rumble will be enabled
|
||||
*/
|
||||
ErrorCode EnableRumble(JoyconHandle& joycon_handle, bool enable);
|
||||
|
||||
/**
|
||||
* Sends data to the joycon device
|
||||
* @param joycon_handle device to send the data
|
||||
* @param buffer data to be send
|
||||
* @param size size in bytes of the buffer
|
||||
*/
|
||||
ErrorCode SendData(JoyconHandle& joycon_handle, std::span<const u8> buffer, std::size_t size);
|
||||
|
||||
/**
|
||||
* Waits for incoming data of the joycon device that matchs the subcommand
|
||||
* @param joycon_handle device to read the data
|
||||
* @param sub_command type of data to be returned
|
||||
* @returns a buffer containing the responce
|
||||
*/
|
||||
ErrorCode GetResponse(JoyconHandle& joycon_handle, SubCommand sub_command, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Sends data to the joycon device
|
||||
* @param joycon_handle device to send the data
|
||||
* @param buffer data to be send
|
||||
* @param size size in bytes of the buffer
|
||||
*/
|
||||
ErrorCode SendSubCommand(JoyconHandle& joycon_handle, SubCommand sc, std::span<const u8> buffer,
|
||||
std::size_t size, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Sends data to the joycon device
|
||||
* @param joycon_handle device to send the data
|
||||
* @param buffer data to be send
|
||||
* @param size size in bytes of the buffer
|
||||
*/
|
||||
ErrorCode ReadSPI(JoyconHandle& joycon_handle, CalAddr addr, std::vector<u8>& output, u8 size);
|
||||
|
||||
} // namespace InputCommon::Joycon
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "input_common/input_poller.h"
|
||||
#include "input_common/main.h"
|
||||
#ifdef HAVE_SDL2
|
||||
#include "input_common/drivers/joycon.h"
|
||||
#include "input_common/drivers/sdl_driver.h"
|
||||
#endif
|
||||
|
||||
@@ -87,6 +88,15 @@ struct InputSubsystem::Impl {
|
||||
sdl_input_factory);
|
||||
Common::Input::RegisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName(),
|
||||
sdl_output_factory);
|
||||
|
||||
joycon = std::make_shared<Joycons>("joycon");
|
||||
joycon->SetMappingCallback(mapping_callback);
|
||||
joycon_input_factory = std::make_shared<InputFactory>(joycon);
|
||||
joycon_output_factory = std::make_shared<OutputFactory>(joycon);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(joycon->GetEngineName(),
|
||||
joycon_input_factory);
|
||||
Common::Input::RegisterFactory<Common::Input::OutputDevice>(joycon->GetEngineName(),
|
||||
joycon_output_factory);
|
||||
#endif
|
||||
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(
|
||||
@@ -123,6 +133,10 @@ struct InputSubsystem::Impl {
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(sdl->GetEngineName());
|
||||
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName());
|
||||
sdl.reset();
|
||||
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(joycon->GetEngineName());
|
||||
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(joycon->GetEngineName());
|
||||
joycon.reset();
|
||||
#endif
|
||||
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>("touch_from_button");
|
||||
@@ -135,14 +149,17 @@ struct InputSubsystem::Impl {
|
||||
};
|
||||
|
||||
auto keyboard_devices = keyboard->GetInputDevices();
|
||||
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
|
||||
auto mouse_devices = mouse->GetInputDevices();
|
||||
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
|
||||
auto gcadapter_devices = gcadapter->GetInputDevices();
|
||||
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
|
||||
auto udp_devices = udp_client->GetInputDevices();
|
||||
|
||||
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
|
||||
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
|
||||
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
|
||||
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
|
||||
@@ -172,6 +189,9 @@ struct InputSubsystem::Impl {
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl->GetAnalogMappingForDevice(params);
|
||||
}
|
||||
if (engine == joycon->GetEngineName()) {
|
||||
return joycon->GetAnalogMappingForDevice(params);
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
@@ -195,6 +215,9 @@ struct InputSubsystem::Impl {
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl->GetButtonMappingForDevice(params);
|
||||
}
|
||||
if (engine == joycon->GetEngineName()) {
|
||||
return joycon->GetButtonMappingForDevice(params);
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
@@ -212,6 +235,9 @@ struct InputSubsystem::Impl {
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl->GetMotionMappingForDevice(params);
|
||||
}
|
||||
if (engine == joycon->GetEngineName()) {
|
||||
return joycon->GetMotionMappingForDevice(params);
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
@@ -237,6 +263,9 @@ struct InputSubsystem::Impl {
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl->GetUIName(params);
|
||||
}
|
||||
if (engine == joycon->GetEngineName()) {
|
||||
return joycon->GetUIName(params);
|
||||
}
|
||||
#endif
|
||||
return Common::Input::ButtonNames::Invalid;
|
||||
}
|
||||
@@ -281,6 +310,9 @@ struct InputSubsystem::Impl {
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
if (engine == joycon->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -292,6 +324,7 @@ struct InputSubsystem::Impl {
|
||||
udp_client->BeginConfiguration();
|
||||
#ifdef HAVE_SDL2
|
||||
sdl->BeginConfiguration();
|
||||
joycon->BeginConfiguration();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -302,6 +335,7 @@ struct InputSubsystem::Impl {
|
||||
udp_client->EndConfiguration();
|
||||
#ifdef HAVE_SDL2
|
||||
sdl->EndConfiguration();
|
||||
joycon->EndConfiguration();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -335,6 +369,9 @@ struct InputSubsystem::Impl {
|
||||
std::shared_ptr<SDLDriver> sdl;
|
||||
std::shared_ptr<InputFactory> sdl_input_factory;
|
||||
std::shared_ptr<OutputFactory> sdl_output_factory;
|
||||
std::shared_ptr<Joycons> joycon;
|
||||
std::shared_ptr<InputFactory> joycon_input_factory;
|
||||
std::shared_ptr<OutputFactory> joycon_output_factory;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -398,6 +398,7 @@ void Config::ReadControlValues() {
|
||||
#else
|
||||
Settings::values.enable_raw_input = false;
|
||||
#endif
|
||||
ReadBasicSetting(Settings::values.enable_sdl_joycons);
|
||||
ReadBasicSetting(Settings::values.emulate_analog_keyboard);
|
||||
Settings::values.mouse_panning = false;
|
||||
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
|
||||
@@ -1042,6 +1043,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_sdl_joycons);
|
||||
WriteBasicSetting(Settings::values.keyboard_enabled);
|
||||
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
|
||||
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
|
||||
|
||||
@@ -136,6 +136,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
|
||||
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
|
||||
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
|
||||
Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
|
||||
Settings::values.enable_sdl_joycons = ui->enable_sdl_joycons->isChecked();
|
||||
}
|
||||
|
||||
void ConfigureInputAdvanced::LoadConfiguration() {
|
||||
@@ -169,6 +170,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
|
||||
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
|
||||
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
|
||||
ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
|
||||
ui->enable_sdl_joycons->setChecked(Settings::values.enable_sdl_joycons.GetValue());
|
||||
|
||||
UpdateUIEnabled();
|
||||
}
|
||||
|
||||
@@ -2682,6 +2682,22 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="enable_sdl_joycons">
|
||||
<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 SDL Joycons</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="mouse_panning">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
@@ -2694,7 +2710,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<item row="7" column="2">
|
||||
<widget class="QSpinBox" name="mouse_panning_sensitivity">
|
||||
<property name="toolTip">
|
||||
<string>Mouse sensitivity</string>
|
||||
@@ -2716,14 +2732,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>
|
||||
|
||||
@@ -67,6 +67,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:
|
||||
@@ -77,6 +89,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:
|
||||
|
||||
Reference in New Issue
Block a user