Merge pull request #9492 from german77/joycon_release

Input_common: Implement custom joycon driver v2
This commit is contained in:
liamwhite 2023-01-24 09:29:37 -05:00 committed by GitHub
commit a68af583ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 5824 additions and 420 deletions

View file

@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const {
}
}
Common::Input::CameraError Camera::SetCameraFormat(
Common::Input::DriverResult Camera::SetCameraFormat(
[[maybe_unused]] const PadIdentifier& identifier_,
const Common::Input::CameraFormat camera_format) {
status.format = camera_format;
return Common::Input::CameraError::None;
return Common::Input::DriverResult::Success;
}
} // namespace InputCommon

View file

@ -22,8 +22,8 @@ public:
std::size_t getImageWidth() const;
std::size_t getImageHeight() const;
Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
Common::Input::CameraFormat camera_format) override;
Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
Common::Input::CameraFormat camera_format) override;
private:
Common::Input::CameraStatus status{};

View file

@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
return true;
}
Common::Input::VibrationError GCAdapter::SetVibration(
Common::Input::DriverResult GCAdapter::SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
const auto processed_amplitude =
@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration(
pads[identifier.port].rumble_amplitude = processed_amplitude;
if (!rumble_enabled) {
return Common::Input::VibrationError::Disabled;
return Common::Input::DriverResult::Disabled;
}
return Common::Input::VibrationError::None;
return Common::Input::DriverResult::Success;
}
bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {

View file

@ -25,7 +25,7 @@ public:
explicit GCAdapter(std::string input_engine_);
~GCAdapter() override;
Common::Input::VibrationError SetVibration(
Common::Input::DriverResult SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
bool IsVibrationEnabled(const PadIdentifier& identifier) override;

View file

@ -0,0 +1,677 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
#include "common/param_package.h"
#include "common/settings.h"
#include "common/thread.h"
#include "input_common/drivers/joycon.h"
#include "input_common/helpers/joycon_driver.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon {
Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
// Avoid conflicting with SDL driver
if (!Settings::values.enable_joycon_driver) {
return;
}
LOG_INFO(Input, "Joycon driver Initialization started");
const int init_res = SDL_hid_init();
if (init_res == 0) {
Setup();
} else {
LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
}
}
Joycons::~Joycons() {
Reset();
}
void Joycons::Reset() {
scan_thread = {};
for (const auto& device : left_joycons) {
if (!device) {
continue;
}
device->Stop();
}
for (const auto& device : right_joycons) {
if (!device) {
continue;
}
device->Stop();
}
SDL_hid_exit();
}
void Joycons::Setup() {
u32 port = 0;
PreSetController(GetIdentifier(0, Joycon::ControllerType::None));
for (auto& device : left_joycons) {
PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
device = std::make_shared<Joycon::JoyconDriver>(port++);
}
port = 0;
for (auto& device : right_joycons) {
PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
device = std::make_shared<Joycon::JoyconDriver>(port++);
}
scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
}
void Joycons::ScanThread(std::stop_token stop_token) {
constexpr u16 nintendo_vendor_id = 0x057e;
Common::SetCurrentThreadName("JoyconScanThread");
while (!stop_token.stop_requested()) {
SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
SDL_hid_device_info* cur_dev = devs;
while (cur_dev) {
if (IsDeviceNew(cur_dev)) {
LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
cur_dev->product_id);
RegisterNewDevice(cur_dev);
}
cur_dev = cur_dev->next;
}
SDL_hid_free_enumeration(devs);
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}
bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
Joycon::ControllerType type{};
Joycon::SerialNumber serial_number{};
const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
if (result != Joycon::DriverResult::Success) {
return false;
}
const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
if (result2 != Joycon::DriverResult::Success) {
return false;
}
auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) {
if (!device) {
return false;
}
if (!device->IsConnected()) {
return false;
}
if (device->GetHandleSerialNumber() != serial_number) {
return false;
}
return true;
};
// Check if device already exist
switch (type) {
case Joycon::ControllerType::Left:
for (const auto& device : left_joycons) {
if (is_handle_identical(device)) {
return false;
}
}
break;
case Joycon::ControllerType::Right:
for (const auto& device : right_joycons) {
if (is_handle_identical(device)) {
return false;
}
}
break;
default:
return false;
}
return true;
}
void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
Joycon::ControllerType type{};
auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
auto handle = GetNextFreeHandle(type);
if (handle == nullptr) {
LOG_WARNING(Input, "No free handles available");
return;
}
if (result == Joycon::DriverResult::Success) {
result = handle->RequestDeviceAccess(device_info);
}
if (result == Joycon::DriverResult::Success) {
LOG_WARNING(Input, "Initialize device");
const std::size_t port = handle->GetDevicePort();
const Joycon::JoyconCallbacks callbacks{
.on_battery_data = {[this, port, type](Joycon::Battery value) {
OnBatteryUpdate(port, type, value);
}},
.on_color_data = {[this, port, type](Joycon::Color value) {
OnColorUpdate(port, type, value);
}},
.on_button_data = {[this, port, type](int id, bool value) {
OnButtonUpdate(port, type, id, value);
}},
.on_stick_data = {[this, port, type](int id, f32 value) {
OnStickUpdate(port, type, id, value);
}},
.on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) {
OnMotionUpdate(port, type, id, value);
}},
.on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
.on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
OnAmiiboUpdate(port, amiibo_data);
}},
.on_camera_data = {[this, port](const std::vector<u8>& camera_data,
Joycon::IrsResolution format) {
OnCameraUpdate(port, camera_data, format);
}},
};
handle->InitializeDevice();
handle->SetCallbacks(callbacks);
}
}
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
Joycon::ControllerType type) const {
if (type == Joycon::ControllerType::Left) {
const auto unconnected_device =
std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); });
if (unconnected_device != left_joycons.end()) {
return *unconnected_device;
}
}
if (type == Joycon::ControllerType::Right) {
const auto unconnected_device = std::ranges::find_if(
right_joycons, [](auto& device) { return !device->IsConnected(); });
if (unconnected_device != right_joycons.end()) {
return *unconnected_device;
}
}
return nullptr;
}
bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
const auto handle = GetHandle(identifier);
if (handle == nullptr) {
return false;
}
return handle->IsVibrationEnabled();
}
Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier,
const Common::Input::VibrationStatus& vibration) {
const Joycon::VibrationValue native_vibration{
.low_amplitude = vibration.low_amplitude,
.low_frequency = vibration.low_frequency,
.high_amplitude = vibration.high_amplitude,
.high_frequency = vibration.high_frequency,
};
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::DriverResult::InvalidHandle;
}
handle->SetVibration(native_vibration);
return Common::Input::DriverResult::Success;
}
Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
const Common::Input::LedStatus& led_status) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::DriverResult::InvalidHandle;
}
int led_config = led_status.led_1 ? 1 : 0;
led_config += led_status.led_2 ? 2 : 0;
led_config += led_status.led_3 ? 4 : 0;
led_config += led_status.led_4 ? 8 : 0;
return static_cast<Common::Input::DriverResult>(
handle->SetLedConfig(static_cast<u8>(led_config)));
}
Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier,
Common::Input::CameraFormat camera_format) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::DriverResult::InvalidHandle;
}
return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig(
Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format)));
};
Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
return Common::Input::NfcState::Success;
};
Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
const std::vector<u8>& data) {
return Common::Input::NfcState::NotSupported;
};
Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
const Common::Input::PollingMode polling_mode) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
LOG_ERROR(Input, "Invalid handle {}", identifier.port);
return Common::Input::DriverResult::InvalidHandle;
}
switch (polling_mode) {
case Common::Input::PollingMode::Active:
return static_cast<Common::Input::DriverResult>(handle->SetActiveMode());
case Common::Input::PollingMode::Pasive:
return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode());
case Common::Input::PollingMode::IR:
return static_cast<Common::Input::DriverResult>(handle->SetIrMode());
case Common::Input::PollingMode::NFC:
return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
case Common::Input::PollingMode::Ring:
return static_cast<Common::Input::DriverResult>(handle->SetRingConMode());
default:
return Common::Input::DriverResult::NotSupported;
}
}
void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
Joycon::Battery value) {
const auto identifier = GetIdentifier(port, type);
if (value.charging != 0) {
SetBattery(identifier, Common::Input::BatteryLevel::Charging);
return;
}
Common::Input::BatteryLevel battery{};
switch (value.status) {
case 0:
battery = Common::Input::BatteryLevel::Empty;
break;
case 1:
battery = Common::Input::BatteryLevel::Critical;
break;
case 2:
battery = Common::Input::BatteryLevel::Low;
break;
case 3:
battery = Common::Input::BatteryLevel::Medium;
break;
case 4:
default:
battery = Common::Input::BatteryLevel::Full;
break;
}
SetBattery(identifier, battery);
}
void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
const Joycon::Color& value) {
const auto identifier = GetIdentifier(port, type);
Common::Input::BodyColorStatus color{
.body = value.body,
.buttons = value.buttons,
.left_grip = value.left_grip,
.right_grip = value.right_grip,
};
SetColor(identifier, color);
}
void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
const auto identifier = GetIdentifier(port, type);
SetButton(identifier, id, value);
}
void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
const auto identifier = GetIdentifier(port, type);
SetAxis(identifier, id, value);
}
void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
const Joycon::MotionData& value) {
const auto identifier = GetIdentifier(port, type);
BasicMotion motion_data{
.gyro_x = value.gyro_x,
.gyro_y = value.gyro_y,
.gyro_z = value.gyro_z,
.accel_x = value.accel_x,
.accel_y = value.accel_y,
.accel_z = value.accel_z,
.delta_timestamp = 15000,
};
SetMotion(identifier, id, motion_data);
}
void Joycons::OnRingConUpdate(f32 ring_data) {
// To simplify ring detection it will always be mapped to an empty identifier for all
// controllers
constexpr PadIdentifier identifier = {
.guid = Common::UUID{},
.port = 0,
.pad = 0,
};
SetAxis(identifier, 100, ring_data);
}
void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
: Common::Input::NfcState::NewAmiibo;
SetNfc(identifier, {nfc_state, amiibo_data});
}
void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
Joycon::IrsResolution format) {
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data});
}
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
if (!device) {
return false;
}
if (!device->IsConnected()) {
return false;
}
if (device->GetDevicePort() == identifier.port) {
return true;
}
return false;
};
const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
if (type == Joycon::ControllerType::Left) {
const auto matching_device = std::ranges::find_if(
left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
if (matching_device != left_joycons.end()) {
return *matching_device;
}
}
if (type == Joycon::ControllerType::Right) {
const auto matching_device = std::ranges::find_if(
right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
if (matching_device != right_joycons.end()) {
return *matching_device;
}
}
return nullptr;
}
PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)};
return {
.guid = Common::UUID{guid},
.port = port,
.pad = static_cast<std::size_t>(type),
};
}
Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const {
const auto identifier = GetIdentifier(port, type);
return {
{"engine", GetEngineName()},
{"guid", identifier.guid.RawString()},
{"port", std::to_string(identifier.port)},
{"pad", std::to_string(identifier.pad)},
};
}
std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
std::vector<Common::ParamPackage> devices{};
auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
if (!device) {
return;
}
if (!device->IsConnected()) {
return;
}
auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType());
std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
device->GetDevicePort() + 1);
param.Set("display", std::move(name));
devices.emplace_back(param);
};
for (const auto& controller : left_joycons) {
add_entry(controller);
}
for (const auto& controller : right_joycons) {
add_entry(controller);
}
// List dual joycon pairs
for (std::size_t i = 0; i < MaxSupportedControllers; i++) {
if (!left_joycons[i] || !right_joycons[i]) {
continue;
}
if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) {
continue;
}
auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType());
const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType());
const auto type = Joycon::ControllerType::Dual;
std::string name = fmt::format("{} {}", JoyconName(type), i + 1);
main_param.Set("display", std::move(name));
main_param.Set("guid2", second_param.Get("guid", ""));
main_param.Set("pad", std::to_string(static_cast<size_t>(type)));
devices.emplace_back(main_param);
}
return devices;
}
ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>,
18>
switch_to_joycon_button = {
std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true},
{Settings::NativeButton::B, Joycon::PadButton::B, true},
{Settings::NativeButton::X, Joycon::PadButton::X, true},
{Settings::NativeButton::Y, Joycon::PadButton::Y, true},
{Settings::NativeButton::DLeft, Joycon::PadButton::Left, false},
{Settings::NativeButton::DUp, Joycon::PadButton::Up, false},
{Settings::NativeButton::DRight, Joycon::PadButton::Right, false},
{Settings::NativeButton::DDown, Joycon::PadButton::Down, false},
{Settings::NativeButton::L, Joycon::PadButton::L, false},
{Settings::NativeButton::R, Joycon::PadButton::R, true},
{Settings::NativeButton::ZL, Joycon::PadButton::ZL, false},
{Settings::NativeButton::ZR, Joycon::PadButton::ZR, true},
{Settings::NativeButton::Plus, Joycon::PadButton::Plus, true},
{Settings::NativeButton::Minus, Joycon::PadButton::Minus, false},
{Settings::NativeButton::Home, Joycon::PadButton::Home, true},
{Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false},
{Settings::NativeButton::LStick, Joycon::PadButton::StickL, false},
{Settings::NativeButton::RStick, Joycon::PadButton::StickR, true},
};
if (!params.Has("port")) {
return {};
}
ButtonMapping mapping{};
for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) {
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
if (pad == Joycon::ControllerType::Dual) {
pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left;
}
Common::ParamPackage button_params = GetParamPackage(port, pad);
button_params.Set("button", static_cast<int>(joycon_button));
mapping.insert_or_assign(switch_button, std::move(button_params));
}
// Map SL and SR buttons for left joycons
if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) {
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left);
Common::ParamPackage sl_button_params = button_params;
Common::ParamPackage sr_button_params = button_params;
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL));
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR));
mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
}
// Map SL and SR buttons for right joycons
if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) {
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right);
Common::ParamPackage sl_button_params = button_params;
Common::ParamPackage sr_button_params = button_params;
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL));
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR));
mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
}
return mapping;
}
AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("port")) {
return {};
}
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
auto pad_right = pad_left;
if (pad_left == Joycon::ControllerType::Dual) {
pad_left = Joycon::ControllerType::Left;
pad_right = Joycon::ControllerType::Right;
}
AnalogMapping mapping = {};
Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left);
left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right);
right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
return mapping;
}
MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("port")) {
return {};
}
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
auto pad_right = pad_left;
if (pad_left == Joycon::ControllerType::Dual) {
pad_left = Joycon::ControllerType::Left;
pad_right = Joycon::ControllerType::Right;
}
MotionMapping mapping = {};
Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left);
left_motion_params.Set("motion", 0);
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right);
right_Motion_params.Set("motion", 1);
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
return mapping;
}
Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
switch (button) {
case Joycon::PadButton::Left:
return Common::Input::ButtonNames::ButtonLeft;
case Joycon::PadButton::Right:
return Common::Input::ButtonNames::ButtonRight;
case Joycon::PadButton::Down:
return Common::Input::ButtonNames::ButtonDown;
case Joycon::PadButton::Up:
return Common::Input::ButtonNames::ButtonUp;
case Joycon::PadButton::LeftSL:
case Joycon::PadButton::RightSL:
return Common::Input::ButtonNames::TriggerSL;
case Joycon::PadButton::LeftSR:
case Joycon::PadButton::RightSR:
return Common::Input::ButtonNames::TriggerSR;
case Joycon::PadButton::L:
return Common::Input::ButtonNames::TriggerL;
case Joycon::PadButton::R:
return Common::Input::ButtonNames::TriggerR;
case Joycon::PadButton::ZL:
return Common::Input::ButtonNames::TriggerZL;
case Joycon::PadButton::ZR:
return Common::Input::ButtonNames::TriggerZR;
case Joycon::PadButton::A:
return Common::Input::ButtonNames::ButtonA;
case Joycon::PadButton::B:
return Common::Input::ButtonNames::ButtonB;
case Joycon::PadButton::X:
return Common::Input::ButtonNames::ButtonX;
case Joycon::PadButton::Y:
return Common::Input::ButtonNames::ButtonY;
case Joycon::PadButton::Plus:
return Common::Input::ButtonNames::ButtonPlus;
case Joycon::PadButton::Minus:
return Common::Input::ButtonNames::ButtonMinus;
case Joycon::PadButton::Home:
return Common::Input::ButtonNames::ButtonHome;
case Joycon::PadButton::Capture:
return Common::Input::ButtonNames::ButtonCapture;
case Joycon::PadButton::StickL:
return Common::Input::ButtonNames::ButtonStickL;
case Joycon::PadButton::StickR:
return Common::Input::ButtonNames::ButtonStickR;
default:
return Common::Input::ButtonNames::Undefined;
}
}
Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
if (params.Has("button")) {
return GetUIButtonName(params);
}
if (params.Has("axis")) {
return Common::Input::ButtonNames::Value;
}
if (params.Has("motion")) {
return Common::Input::ButtonNames::Engine;
}
return Common::Input::ButtonNames::Invalid;
}
std::string Joycons::JoyconName(Joycon::ControllerType type) const {
switch (type) {
case Joycon::ControllerType::Left:
return "Left Joycon";
case Joycon::ControllerType::Right:
return "Right Joycon";
case Joycon::ControllerType::Pro:
return "Pro Controller";
case Joycon::ControllerType::Grip:
return "Grip Controller";
case Joycon::ControllerType::Dual:
return "Dual Joycon";
default:
return "Unknown Joycon";
}
}
} // namespace InputCommon

View file

@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include <thread>
#include <SDL_hidapi.h>
#include "input_common/input_engine.h"
namespace InputCommon::Joycon {
using SerialNumber = std::array<u8, 15>;
struct Battery;
struct Color;
struct MotionData;
enum class ControllerType;
enum class DriverResult;
enum class IrsResolution;
class JoyconDriver;
} // namespace InputCommon::Joycon
namespace InputCommon {
class Joycons final : public InputCommon::InputEngine {
public:
explicit Joycons(const std::string& input_engine_);
~Joycons();
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
Common::Input::DriverResult SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
Common::Input::DriverResult SetLeds(const PadIdentifier& identifier,
const Common::Input::LedStatus& led_status) override;
Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
Common::Input::CameraFormat camera_format) override;
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
const std::vector<u8>& data) override;
Common::Input::DriverResult SetPollingMode(
const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
/// Used for automapping features
std::vector<Common::ParamPackage> GetInputDevices() const override;
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
private:
static constexpr std::size_t MaxSupportedControllers = 8;
/// For shutting down, clear all data, join all threads, release usb devices
void Reset();
/// Registers controllers, clears all data and starts the scan thread
void Setup();
/// Actively searchs for new devices
void ScanThread(std::stop_token stop_token);
/// Returns true if device is valid and not registered
bool IsDeviceNew(SDL_hid_device_info* device_info) const;
/// Tries to connect to the new device
void RegisterNewDevice(SDL_hid_device_info* device_info);
/// Returns the next free handle
std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const;
void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value);
void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value);
void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value);
void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value);
void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
const Joycon::MotionData& value);
void OnRingConUpdate(f32 ring_data);
void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
Joycon::IrsResolution format);
/// Returns a JoyconHandle corresponding to a PadIdentifier
std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
/// Returns a PadIdentifier corresponding to the port number and joycon type
PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
/// Returns a ParamPackage corresponding to the port number and joycon type
Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const;
std::string JoyconName(std::size_t port) const;
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
/// Returns the name of the device in text format
std::string JoyconName(Joycon::ControllerType type) const;
std::jthread scan_thread;
// Joycon types are split by type to ease supporting dualjoycon configurations
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
};
} // namespace InputCommon

View file

@ -334,6 +334,15 @@ void SDLDriver::InitJoystick(int joystick_index) {
const auto guid = GetGUID(sdl_joystick);
if (Settings::values.enable_joycon_driver) {
if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
(guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
SDL_JoystickClose(sdl_joystick);
return;
}
}
std::scoped_lock lock{joystick_map_mutex};
if (joystick_map.find(guid) == joystick_map.end()) {
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
@ -456,9 +465,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
// not a generic one
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
// Disable hidapi drivers for switch controllers when the custom joycon driver is enabled
if (Settings::values.enable_joycon_driver) {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
} else {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
}
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
// driver on Linux.
@ -548,7 +561,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
return devices;
}
Common::Input::VibrationError SDLDriver::SetVibration(
Common::Input::DriverResult SDLDriver::SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
const auto joystick =
GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
@ -582,7 +595,7 @@ Common::Input::VibrationError SDLDriver::SetVibration(
.vibration = new_vibration,
});
return Common::Input::VibrationError::None;
return Common::Input::DriverResult::Success;
}
bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {

View file

@ -63,7 +63,7 @@ public:
bool IsStickInverted(const Common::ParamPackage& params) override;
Common::Input::VibrationError SetVibration(
Common::Input::DriverResult SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
bool IsVibrationEnabled(const PadIdentifier& identifier) override;

View file

@ -22,22 +22,23 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(
VirtualAmiibo::~VirtualAmiibo() = default;
Common::Input::PollingError VirtualAmiibo::SetPollingMode(
Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
[[maybe_unused]] const PadIdentifier& identifier_,
const Common::Input::PollingMode polling_mode_) {
polling_mode = polling_mode_;
if (polling_mode == Common::Input::PollingMode::NFC) {
switch (polling_mode) {
case Common::Input::PollingMode::NFC:
if (state == State::Initialized) {
state = State::WaitingForAmiibo;
}
} else {
return Common::Input::DriverResult::Success;
default:
if (state == State::AmiiboIsOpen) {
CloseAmiibo();
}
return Common::Input::DriverResult::NotSupported;
}
return Common::Input::PollingError::None;
}
Common::Input::NfcState VirtualAmiibo::SupportsNfc(

View file

@ -36,7 +36,7 @@ public:
~VirtualAmiibo() override;
// Sets polling mode to a controller
Common::Input::PollingError SetPollingMode(
Common::Input::DriverResult SetPollingMode(
const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;