input_common: Initial skeleton for custom joycon driver
This commit is contained in:
parent
475370c8f8
commit
d80e6c399b
|
@ -51,6 +51,8 @@ enum class PollingMode {
|
||||||
NFC,
|
NFC,
|
||||||
// Enable infrared camera polling
|
// Enable infrared camera polling
|
||||||
IR,
|
IR,
|
||||||
|
// Enable ring controller polling
|
||||||
|
Ring,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class CameraFormat {
|
enum class CameraFormat {
|
||||||
|
@ -67,6 +69,7 @@ enum class VibrationError {
|
||||||
None,
|
None,
|
||||||
NotSupported,
|
NotSupported,
|
||||||
Disabled,
|
Disabled,
|
||||||
|
InvalidHandle,
|
||||||
Unknown,
|
Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,6 +77,7 @@ enum class VibrationError {
|
||||||
enum class PollingError {
|
enum class PollingError {
|
||||||
None,
|
None,
|
||||||
NotSupported,
|
NotSupported,
|
||||||
|
InvalidHandle,
|
||||||
Unknown,
|
Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,6 +194,8 @@ struct TouchStatus {
|
||||||
struct BodyColorStatus {
|
struct BodyColorStatus {
|
||||||
u32 body{};
|
u32 body{};
|
||||||
u32 buttons{};
|
u32 buttons{};
|
||||||
|
u32 left_grip{};
|
||||||
|
u32 right_grip{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// HD rumble data
|
// HD rumble data
|
||||||
|
@ -228,17 +234,31 @@ enum class ButtonNames {
|
||||||
Engine,
|
Engine,
|
||||||
// This will display the button by value instead of the button name
|
// This will display the button by value instead of the button name
|
||||||
Value,
|
Value,
|
||||||
|
|
||||||
|
// Joycon button names
|
||||||
ButtonLeft,
|
ButtonLeft,
|
||||||
ButtonRight,
|
ButtonRight,
|
||||||
ButtonDown,
|
ButtonDown,
|
||||||
ButtonUp,
|
ButtonUp,
|
||||||
TriggerZ,
|
|
||||||
TriggerR,
|
|
||||||
TriggerL,
|
|
||||||
ButtonA,
|
ButtonA,
|
||||||
ButtonB,
|
ButtonB,
|
||||||
ButtonX,
|
ButtonX,
|
||||||
ButtonY,
|
ButtonY,
|
||||||
|
ButtonPlus,
|
||||||
|
ButtonMinus,
|
||||||
|
ButtonHome,
|
||||||
|
ButtonCapture,
|
||||||
|
ButtonStickL,
|
||||||
|
ButtonStickR,
|
||||||
|
TriggerL,
|
||||||
|
TriggerZL,
|
||||||
|
TriggerSL,
|
||||||
|
TriggerR,
|
||||||
|
TriggerZR,
|
||||||
|
TriggerSR,
|
||||||
|
|
||||||
|
// GC button names
|
||||||
|
TriggerZ,
|
||||||
ButtonStart,
|
ButtonStart,
|
||||||
|
|
||||||
// DS4 button names
|
// DS4 button names
|
||||||
|
|
|
@ -51,8 +51,13 @@ endif()
|
||||||
|
|
||||||
if (ENABLE_SDL2)
|
if (ENABLE_SDL2)
|
||||||
target_sources(input_common PRIVATE
|
target_sources(input_common PRIVATE
|
||||||
|
drivers/joycon.cpp
|
||||||
|
drivers/joycon.h
|
||||||
drivers/sdl_driver.cpp
|
drivers/sdl_driver.cpp
|
||||||
drivers/sdl_driver.h
|
drivers/sdl_driver.h
|
||||||
|
helpers/joycon_driver.cpp
|
||||||
|
helpers/joycon_driver.h
|
||||||
|
helpers/joycon_protocol/joycon_types.h
|
||||||
)
|
)
|
||||||
target_link_libraries(input_common PRIVATE SDL2::SDL2)
|
target_link_libraries(input_common PRIVATE SDL2::SDL2)
|
||||||
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
||||||
|
|
|
@ -0,0 +1,615 @@
|
||||||
|
// 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_) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
for (const auto& device : pro_joycons) {
|
||||||
|
if (!device) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
device->Stop();
|
||||||
|
}
|
||||||
|
SDL_hid_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Joycons::Setup() {
|
||||||
|
u32 port = 0;
|
||||||
|
for (auto& device : left_joycons) {
|
||||||
|
PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
|
||||||
|
device = std::make_shared<Joycon::JoyconDriver>(port++);
|
||||||
|
}
|
||||||
|
for (auto& device : right_joycons) {
|
||||||
|
PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
|
||||||
|
device = std::make_shared<Joycon::JoyconDriver>(port++);
|
||||||
|
}
|
||||||
|
for (auto& device : pro_joycons) {
|
||||||
|
PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro));
|
||||||
|
device = std::make_shared<Joycon::JoyconDriver>(port++);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
constexpr u16 nintendo_vendor_id = 0x057e;
|
||||||
|
Common::SetCurrentThreadName("yuzu:input:JoyconScanThread");
|
||||||
|
scan_thread_running = true;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
|
}
|
||||||
|
scan_thread_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = [&](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;
|
||||||
|
case Joycon::ControllerType::Pro:
|
||||||
|
case Joycon::ControllerType::Grip:
|
||||||
|
for (const auto& device : pro_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");
|
||||||
|
|
||||||
|
std::function<void(Joycon::Battery)> on_battery_data;
|
||||||
|
std::function<void(Joycon::Color)> on_button_data;
|
||||||
|
std::function<void(int, f32)> on_stick_data;
|
||||||
|
std::function<void(int, std::array<u8, 6>)> on_motion_data;
|
||||||
|
std::function<void(s16)> on_ring_data;
|
||||||
|
std::function<void(const std::vector<u8>&)> on_amiibo_data;
|
||||||
|
|
||||||
|
const std::size_t port = handle->GetDevicePort();
|
||||||
|
handle->on_battery_data = {
|
||||||
|
[this, port, type](Joycon::Battery value) { OnBatteryUpdate(port, type, value); }};
|
||||||
|
handle->on_color_data = {
|
||||||
|
[this, port, type](Joycon::Color value) { OnColorUpdate(port, type, value); }};
|
||||||
|
handle->on_button_data = {
|
||||||
|
[this, port, type](int id, bool value) { OnButtonUpdate(port, type, id, value); }};
|
||||||
|
handle->on_stick_data = {
|
||||||
|
[this, port, type](int id, f32 value) { OnStickUpdate(port, type, id, value); }};
|
||||||
|
handle->on_motion_data = {[this, port, type](int id, Joycon::MotionData value) {
|
||||||
|
OnMotionUpdate(port, type, id, value);
|
||||||
|
}};
|
||||||
|
handle->on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }};
|
||||||
|
handle->on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
|
||||||
|
OnAmiiboUpdate(port, amiibo_data);
|
||||||
|
}};
|
||||||
|
handle->InitializeDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
|
||||||
|
Joycon::ControllerType type) const {
|
||||||
|
|
||||||
|
if (type == Joycon::ControllerType::Left) {
|
||||||
|
for (const auto& device : left_joycons) {
|
||||||
|
if (!device->IsConnected()) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == Joycon::ControllerType::Right) {
|
||||||
|
for (const auto& device : right_joycons) {
|
||||||
|
if (!device->IsConnected()) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
|
||||||
|
for (const auto& device : pro_joycons) {
|
||||||
|
if (!device->IsConnected()) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
|
||||||
|
const auto handle = GetHandle(identifier);
|
||||||
|
if (handle == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return handle->IsVibrationEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::VibrationError 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_amplitude,
|
||||||
|
};
|
||||||
|
auto handle = GetHandle(identifier);
|
||||||
|
if (handle == nullptr) {
|
||||||
|
return Common::Input::VibrationError::InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->SetVibration(native_vibration);
|
||||||
|
return Common::Input::VibrationError::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Joycons::SetLeds(const PadIdentifier& identifier, const Common::Input::LedStatus& led_status) {
|
||||||
|
auto handle = GetHandle(identifier);
|
||||||
|
if (handle == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
const auto result = handle->SetLedConfig(static_cast<u8>(led_config));
|
||||||
|
if (result != Joycon::DriverResult::Success) {
|
||||||
|
LOG_ERROR(Input, "Failed to set led config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::CameraError Joycons::SetCameraFormat(const PadIdentifier& identifier_,
|
||||||
|
Common::Input::CameraFormat camera_format) {
|
||||||
|
return Common::Input::CameraError::NotSupported;
|
||||||
|
};
|
||||||
|
|
||||||
|
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::PollingError 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::PollingError::InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (polling_mode) {
|
||||||
|
case Common::Input::PollingMode::NFC:
|
||||||
|
handle->SetNfcMode();
|
||||||
|
break;
|
||||||
|
case Common::Input::PollingMode::Active:
|
||||||
|
handle->SetActiveMode();
|
||||||
|
break;
|
||||||
|
case Common::Input::PollingMode::Pasive:
|
||||||
|
handle->SetPasiveMode();
|
||||||
|
break;
|
||||||
|
case Common::Input::PollingMode::Ring:
|
||||||
|
handle->SetRingConMode();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return Common::Input::PollingError::NotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Common::Input::PollingError::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
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{value.status.Value()};
|
||||||
|
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) {}
|
||||||
|
|
||||||
|
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);
|
||||||
|
SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_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) {
|
||||||
|
for (const auto& device : left_joycons) {
|
||||||
|
if (is_handle_active(device)) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == Joycon::ControllerType::Right) {
|
||||||
|
for (const auto& device : right_joycons) {
|
||||||
|
if (is_handle_active(device)) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
|
||||||
|
for (const auto& device : pro_joycons) {
|
||||||
|
if (is_handle_active(device)) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
|
||||||
|
return {
|
||||||
|
.guid = Common::UUID{Common::InvalidUUID},
|
||||||
|
.port = port,
|
||||||
|
.pad = static_cast<std::size_t>(type),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
|
||||||
|
device->GetDevicePort());
|
||||||
|
devices.emplace_back(Common::ParamPackage{
|
||||||
|
{"engine", GetEngineName()},
|
||||||
|
{"display", std::move(name)},
|
||||||
|
{"port", std::to_string(device->GetDevicePort())},
|
||||||
|
{"pad", std::to_string(static_cast<std::size_t>(device->GetHandleDeviceType()))},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& controller : left_joycons) {
|
||||||
|
add_entry(controller);
|
||||||
|
}
|
||||||
|
for (const auto& controller : right_joycons) {
|
||||||
|
add_entry(controller);
|
||||||
|
}
|
||||||
|
for (const auto& controller : pro_joycons) {
|
||||||
|
add_entry(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>(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;
|
||||||
|
right_analog_params.Set("engine", GetEngineName());
|
||||||
|
right_analog_params.Set("port", params.Get("port", 0));
|
||||||
|
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 {};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
default:
|
||||||
|
return "Unknow Joycon";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace InputCommon
|
|
@ -0,0 +1,107 @@
|
||||||
|
// 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;
|
||||||
|
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::VibrationError SetVibration(
|
||||||
|
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||||
|
|
||||||
|
void SetLeds(const PadIdentifier& identifier,
|
||||||
|
const Common::Input::LedStatus& led_status) override;
|
||||||
|
|
||||||
|
Common::Input::CameraError 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::PollingError 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);
|
||||||
|
|
||||||
|
/// Returns a JoyconHandle corresponding to a PadIdentifier
|
||||||
|
std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
|
||||||
|
|
||||||
|
/// Returns a PadIdentifier corresponding to the port number
|
||||||
|
PadIdentifier GetIdentifier(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;
|
||||||
|
bool scan_thread_running{};
|
||||||
|
|
||||||
|
// 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{};
|
||||||
|
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> pro_joycons{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
|
@ -0,0 +1,382 @@
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Set HW default configuration
|
||||||
|
vibration_enabled = true;
|
||||||
|
motion_enabled = true;
|
||||||
|
hidbus_enabled = false;
|
||||||
|
nfc_enabled = false;
|
||||||
|
passive_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
|
||||||
|
|
||||||
|
// Get fixed joycon info
|
||||||
|
supported_features = GetSupportedFeatures();
|
||||||
|
|
||||||
|
// Get Calibration data
|
||||||
|
|
||||||
|
// Set led status
|
||||||
|
|
||||||
|
// Apply HW configuration
|
||||||
|
SetPollingMode();
|
||||||
|
|
||||||
|
// 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, "JC 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, "JC Adapter input thread stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoyconDriver::OnNewData(std::span<u8> buffer) {
|
||||||
|
const auto report_mode = static_cast<InputReport>(buffer[0]);
|
||||||
|
|
||||||
|
switch (report_mode) {
|
||||||
|
case InputReport::STANDARD_FULL_60HZ:
|
||||||
|
ReadActiveMode(buffer);
|
||||||
|
break;
|
||||||
|
case InputReport::NFC_IR_MODE_60HZ:
|
||||||
|
ReadNfcIRMode(buffer);
|
||||||
|
break;
|
||||||
|
case InputReport::SIMPLE_HID_MODE:
|
||||||
|
ReadPassiveMode(buffer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoyconDriver::SetPollingMode() {
|
||||||
|
disable_input_thread = true;
|
||||||
|
disable_input_thread = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoyconDriver::ReadActiveMode(std::span<u8> buffer) {
|
||||||
|
InputReportActive data{};
|
||||||
|
memcpy(&data, buffer.data(), sizeof(InputReportActive));
|
||||||
|
|
||||||
|
// Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
|
||||||
|
// experience
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const auto new_delta_time =
|
||||||
|
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count();
|
||||||
|
delta_time = static_cast<u64>((delta_time * 0.8f) + (new_delta_time * 0.2));
|
||||||
|
last_update = now;
|
||||||
|
|
||||||
|
switch (device_type) {
|
||||||
|
case Joycon::ControllerType::Left:
|
||||||
|
break;
|
||||||
|
case Joycon::ControllerType::Right:
|
||||||
|
break;
|
||||||
|
case Joycon::ControllerType::Pro:
|
||||||
|
break;
|
||||||
|
case Joycon::ControllerType::Grip:
|
||||||
|
case Joycon::ControllerType::Dual:
|
||||||
|
case Joycon::ControllerType::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
on_battery_data(data.battery_status);
|
||||||
|
on_color_data(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoyconDriver::ReadPassiveMode(std::span<u8> buffer) {
|
||||||
|
InputReportPassive data{};
|
||||||
|
memcpy(&data, buffer.data(), sizeof(InputReportPassive));
|
||||||
|
|
||||||
|
switch (device_type) {
|
||||||
|
case Joycon::ControllerType::Left:
|
||||||
|
break;
|
||||||
|
case Joycon::ControllerType::Right:
|
||||||
|
break;
|
||||||
|
case Joycon::ControllerType::Pro:
|
||||||
|
break;
|
||||||
|
case Joycon::ControllerType::Grip:
|
||||||
|
case Joycon::ControllerType::Dual:
|
||||||
|
case Joycon::ControllerType::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoyconDriver::ReadNfcIRMode(std::span<u8> buffer) {
|
||||||
|
// This mode is compatible with the active mode
|
||||||
|
ReadActiveMode(buffer);
|
||||||
|
|
||||||
|
if (!nfc_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoyconDriver::IsInputThreadValid() const {
|
||||||
|
if (!is_connected) {
|
||||||
|
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};
|
||||||
|
return DriverResult::NotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
return DriverResult::NotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverResult JoyconDriver::SetPasiveMode() {
|
||||||
|
motion_enabled = false;
|
||||||
|
hidbus_enabled = false;
|
||||||
|
nfc_enabled = false;
|
||||||
|
passive_enabled = true;
|
||||||
|
SetPollingMode();
|
||||||
|
return DriverResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverResult JoyconDriver::SetActiveMode() {
|
||||||
|
motion_enabled = false;
|
||||||
|
hidbus_enabled = false;
|
||||||
|
nfc_enabled = false;
|
||||||
|
passive_enabled = false;
|
||||||
|
SetPollingMode();
|
||||||
|
return DriverResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverResult JoyconDriver::SetNfcMode() {
|
||||||
|
motion_enabled = false;
|
||||||
|
hidbus_enabled = false;
|
||||||
|
nfc_enabled = true;
|
||||||
|
passive_enabled = false;
|
||||||
|
SetPollingMode();
|
||||||
|
return DriverResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverResult JoyconDriver::SetRingConMode() {
|
||||||
|
motion_enabled = true;
|
||||||
|
hidbus_enabled = true;
|
||||||
|
nfc_enabled = false;
|
||||||
|
passive_enabled = false;
|
||||||
|
SetPollingMode();
|
||||||
|
return DriverResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoyconDriver::IsConnected() const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
return is_connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 handle_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
|
||||||
|
ControllerType& controller_type) {
|
||||||
|
std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
|
||||||
|
std::pair<u32, Joycon::ControllerType>{0x2006, Joycon::ControllerType::Left},
|
||||||
|
{0x2007, Joycon::ControllerType::Right},
|
||||||
|
{0x2009, Joycon::ControllerType::Pro},
|
||||||
|
{0x200E, Joycon::ControllerType::Grip},
|
||||||
|
};
|
||||||
|
constexpr u16 nintendo_vendor_id = 0x057e;
|
||||||
|
|
||||||
|
controller_type = Joycon::ControllerType::None;
|
||||||
|
if (device_info->vendor_id != nintendo_vendor_id) {
|
||||||
|
return Joycon::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Joycon::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
|
||||||
|
Joycon::SerialNumber& serial_number) {
|
||||||
|
if (device_info->serial_number == nullptr) {
|
||||||
|
return Joycon::DriverResult::Unknown;
|
||||||
|
}
|
||||||
|
std::memcpy(&serial_number, device_info->serial_number, 15);
|
||||||
|
return Joycon::DriverResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon::Joycon
|
|
@ -0,0 +1,146 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <span>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||||
|
|
||||||
|
namespace InputCommon::Joycon {
|
||||||
|
|
||||||
|
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 SetPasiveMode();
|
||||||
|
DriverResult SetActiveMode();
|
||||||
|
DriverResult SetNfcMode();
|
||||||
|
DriverResult SetRingConMode();
|
||||||
|
|
||||||
|
// Returns device type from hidapi handle
|
||||||
|
static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info,
|
||||||
|
Joycon::ControllerType& controller_type);
|
||||||
|
|
||||||
|
// Returns serial number from hidapi handle
|
||||||
|
static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
|
||||||
|
Joycon::SerialNumber& serial_number);
|
||||||
|
|
||||||
|
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, MotionData)> on_motion_data;
|
||||||
|
std::function<void(f32)> on_ring_data;
|
||||||
|
std::function<void(const std::vector<u8>&)> on_amiibo_data;
|
||||||
|
|
||||||
|
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
|
||||||
|
void 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();
|
||||||
|
|
||||||
|
/// Handles data from passive packages
|
||||||
|
void ReadPassiveMode(std::span<u8> buffer);
|
||||||
|
|
||||||
|
/// Handles data from active packages
|
||||||
|
void ReadActiveMode(std::span<u8> buffer);
|
||||||
|
|
||||||
|
/// Handles data from nfc or ir packages
|
||||||
|
void ReadNfcIRMode(std::span<u8> buffer);
|
||||||
|
|
||||||
|
// Protocol Features
|
||||||
|
|
||||||
|
// Connection status
|
||||||
|
bool is_connected{};
|
||||||
|
u64 delta_time;
|
||||||
|
std::size_t error_counter{};
|
||||||
|
std::shared_ptr<JoyconHandle> hidapi_handle = nullptr;
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||||
|
|
||||||
|
// External device status
|
||||||
|
bool starlink_connected{};
|
||||||
|
bool ring_connected{};
|
||||||
|
bool amiibo_detected{};
|
||||||
|
|
||||||
|
// 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{};
|
||||||
|
|
||||||
|
// 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
|
|
@ -0,0 +1,494 @@
|
||||||
|
// 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 = 60;
|
||||||
|
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 DriverResult {
|
||||||
|
Success,
|
||||||
|
WrongReply,
|
||||||
|
Timeout,
|
||||||
|
UnsupportedControllerType,
|
||||||
|
HandleInUse,
|
||||||
|
ErrorReadingData,
|
||||||
|
ErrorWritingData,
|
||||||
|
NoDeviceDetected,
|
||||||
|
InvalidHandle,
|
||||||
|
NotSupported,
|
||||||
|
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 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon::Joycon
|
|
@ -23,6 +23,7 @@
|
||||||
#include "input_common/drivers/gc_adapter.h"
|
#include "input_common/drivers/gc_adapter.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
|
#include "input_common/drivers/joycon.h"
|
||||||
#include "input_common/drivers/sdl_driver.h"
|
#include "input_common/drivers/sdl_driver.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ struct InputSubsystem::Impl {
|
||||||
RegisterEngine("virtual_gamepad", virtual_gamepad);
|
RegisterEngine("virtual_gamepad", virtual_gamepad);
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
RegisterEngine("sdl", sdl);
|
RegisterEngine("sdl", sdl);
|
||||||
|
RegisterEngine("joycon", joycon);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Common::Input::RegisterInputFactory("touch_from_button",
|
Common::Input::RegisterInputFactory("touch_from_button",
|
||||||
|
@ -111,6 +113,7 @@ struct InputSubsystem::Impl {
|
||||||
UnregisterEngine(virtual_gamepad);
|
UnregisterEngine(virtual_gamepad);
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
UnregisterEngine(sdl);
|
UnregisterEngine(sdl);
|
||||||
|
UnregisterEngine(joycon);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Common::Input::UnregisterInputFactory("touch_from_button");
|
Common::Input::UnregisterInputFactory("touch_from_button");
|
||||||
|
@ -133,6 +136,8 @@ struct InputSubsystem::Impl {
|
||||||
auto udp_devices = udp_client->GetInputDevices();
|
auto udp_devices = udp_client->GetInputDevices();
|
||||||
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
|
auto joycon_devices = joycon->GetInputDevices();
|
||||||
|
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
|
||||||
auto sdl_devices = sdl->GetInputDevices();
|
auto sdl_devices = sdl->GetInputDevices();
|
||||||
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
|
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
|
||||||
#endif
|
#endif
|
||||||
|
@ -164,6 +169,9 @@ struct InputSubsystem::Impl {
|
||||||
if (engine == sdl->GetEngineName()) {
|
if (engine == sdl->GetEngineName()) {
|
||||||
return sdl;
|
return sdl;
|
||||||
}
|
}
|
||||||
|
if (engine == joycon->GetEngineName()) {
|
||||||
|
return joycon;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -247,6 +255,9 @@ struct InputSubsystem::Impl {
|
||||||
if (engine == sdl->GetEngineName()) {
|
if (engine == sdl->GetEngineName()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (engine == joycon->GetEngineName()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -260,6 +271,7 @@ struct InputSubsystem::Impl {
|
||||||
udp_client->BeginConfiguration();
|
udp_client->BeginConfiguration();
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
sdl->BeginConfiguration();
|
sdl->BeginConfiguration();
|
||||||
|
joycon->BeginConfiguration();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,6 +284,7 @@ struct InputSubsystem::Impl {
|
||||||
udp_client->EndConfiguration();
|
udp_client->EndConfiguration();
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
sdl->EndConfiguration();
|
sdl->EndConfiguration();
|
||||||
|
joycon->EndConfiguration();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,6 +317,7 @@ struct InputSubsystem::Impl {
|
||||||
|
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
std::shared_ptr<SDLDriver> sdl;
|
std::shared_ptr<SDLDriver> sdl;
|
||||||
|
std::shared_ptr<Joycons> joycon;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Reference in New Issue