Compare commits

...

3 Commits

Author SHA1 Message Date
german
d4a28bbb74 Update to latest master 2021-02-07 00:12:53 -06:00
german
e2523c3828 Add hidapi lib 2021-02-06 23:16:43 -06:00
german
693a1fe1e8 First commit for native joycon support 2021-02-06 23:16:38 -06:00
12 changed files with 2091 additions and 4 deletions

3
.gitmodules vendored
View File

@@ -37,3 +37,6 @@
[submodule "opus"]
path = externals/opus/opus
url = https://github.com/xiph/opus.git
[submodule "externals/hidapi/hidapi"]
path = externals/hidapi/hidapi
url = https://github.com/libusb/hidapi.git

View File

@@ -384,6 +384,10 @@ if (NOT LIBUSB_FOUND)
set(LIBUSB_LIBRARIES usb)
endif()
find_package(HIDAPI)
add_subdirectory(externals/hidapi)
set(HIDAPI_LIBRARIES hidapi)
# Use system installed ffmpeg.
if (NOT MSVC)
find_package(FFmpeg REQUIRED)

20
externals/hidapi/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,20 @@
project(hidapi)
add_library(hidapi STATIC hidapi/hidapi/hidapi.h)
target_include_directories(hidapi PUBLIC hidapi/hidapi)
if(APPLE)
target_sources(hidapi PRIVATE hidapi/mac/hid.c)
elseif(MSVC)
target_sources(hidapi PRIVATE hidapi/windows/hid.c)
else()
find_package(Libudev)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LIBUDEV_FOUND)
target_sources(hidapi PRIVATE hidapi/linux/hid.c)
target_link_libraries(hidapi PRIVATE hidapi/udev)
else()
target_sources(hidapi PRIVATE hidapi/libusb/hid.c)
endif()
endif()
add_library(Hidapi::Hidapi ALIAS hidapi)

1
externals/hidapi/hidapi vendored Submodule

Submodule externals/hidapi/hidapi added at ad27b46170

View File

@@ -21,6 +21,10 @@ add_library(input_common STATIC
mouse/mouse_input.h
mouse/mouse_poller.cpp
mouse/mouse_poller.h
joycon/jc_adapter.cpp
joycon/jc_adapter.h
joycon/jc_poller.cpp
joycon/jc_poller.h
sdl/sdl.cpp
sdl/sdl.h
udp/client.cpp
@@ -76,5 +80,7 @@ endif()
target_include_directories(input_common SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIR})
target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES})
target_link_libraries(input_common PUBLIC ${HIDAPI_LIBRARIES})
create_target_directory_groups(input_common)
target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost)

View File

@@ -0,0 +1,986 @@
// Copyright 2020 Yuzu Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <span>
#include <thread>
#include "common/logging/log.h"
#include "input_common/joycon/jc_adapter.h"
namespace JCAdapter {
/// Used to loop through and assign button in poller
constexpr std::array<PadButton, 22> PadButtonArray{
PadButton::BUTTON_DOWN, PadButton::BUTTON_UP, PadButton::BUTTON_RIGHT,
PadButton::BUTTON_LEFT, PadButton::BUTTON_L_SR, PadButton::BUTTON_L_SL,
PadButton::TRIGGER_L, PadButton::TRIGGER_ZL, PadButton::BUTTON_Y,
PadButton::BUTTON_X, PadButton::BUTTON_B, PadButton::BUTTON_A,
PadButton::BUTTON_R_SL, PadButton::BUTTON_R_SR, PadButton::TRIGGER_R,
PadButton::TRIGGER_ZR, PadButton::BUTTON_MINUS, PadButton::BUTTON_PLUS,
PadButton::STICK_R, PadButton::STICK_L, PadButton::BUTTON_HOME,
PadButton::BUTTON_CAPTURE,
};
Joycons::Joycons() {
LOG_INFO(Input, "JC Adapter Initialization started");
global_counter = 0;
adapter_thread_running = false;
const int init_res = hid_init();
if (init_res == 0) {
Setup();
} else {
LOG_ERROR(Input, "HIDapi could not be initialized. failed with error = {}", init_res);
}
}
void Joycons::SendWrite(hid_device* handle, std::vector<u8> buffer, int size) {
hid_write(handle, buffer.data(), size);
}
u8 Joycons::GetCounter() {
const u8 counter = global_counter;
global_counter = (global_counter + 1) & 0x0F;
return counter;
}
std::vector<u8> Joycons::SubCommand(hid_device* handle, SubComamnd sc, std::vector<u8> buffer,
int size) {
std::vector<u8> local_buffer(size + 11);
local_buffer[0] = static_cast<u8>(Output::RUMBLE_AND_SUBCMD);
local_buffer[1] = GetCounter();
for (int i = 0; i < 8; ++i) {
local_buffer[i + 2] = default_buffer[i];
}
local_buffer[10] = static_cast<u8>(sc);
for (int i = 0; i < size; ++i) {
local_buffer[11 + i] = buffer[i];
}
SendWrite(handle, local_buffer, size + 11);
return GetResponse(handle, sc);
}
std::vector<u8> Joycons::GetResponse(hid_device* handle, SubComamnd sc) {
int tries = 0;
std::vector<u8> buffer(max_resp_size);
do {
int result = hid_read_timeout(handle, buffer.data(), max_resp_size, 100);
if (result < 1) {
LOG_ERROR(Input, "No response from joystick");
}
tries++;
} while (tries < 10 && buffer[0] != 0x21 && buffer[14] != static_cast<u8>(sc));
return buffer;
}
std::vector<u8> Joycons::ReadSPI(hid_device* handle, CalAddr addr, u8 size) {
std::vector<u8> buffer = {0x00, 0x00, 0x00, 0x00, size};
std::vector<u8> local_buffer(size + 20);
buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
for (int i = 0; i < 100; ++i) {
local_buffer = SubCommand(handle, SubComamnd::SPI_FLASH_READ, buffer, 5);
if (local_buffer[15] == buffer[0] && local_buffer[16] == buffer[1]) {
break;
}
}
return std::vector<u8>(local_buffer.begin() + 20, local_buffer.end());
}
void Joycons::SetJoyStickCal(std::vector<u8> buffer, JoyStick& axis1, JoyStick& axis2, bool left) {
if (left) {
axis1.max = (u16)(((buffer[1] & 0x0F) << 8) | buffer[0]);
axis2.max = (u16)((buffer[2] << 4) | (buffer[1] >> 4));
axis1.center = (u16)(((buffer[4] & 0x0F) << 8) | buffer[3]);
axis2.center = (u16)((buffer[5] << 4) | (buffer[4] >> 4));
axis1.min = (u16)(((buffer[7] & 0x0F) << 8) | buffer[6]);
axis2.min = (u16)((buffer[8] << 4) | (buffer[7] >> 4));
} else {
axis1.center = (u16)(((buffer[1] & 0x0F) << 8) | buffer[0]);
axis2.center = (u16)((buffer[2] << 4) | (buffer[1] >> 4));
axis1.min = (u16)(((buffer[4] & 0x0F) << 8) | buffer[3]);
axis2.min = (u16)((buffer[5] << 4) | (buffer[4] >> 4));
axis1.max = (u16)(((buffer[7] & 0x0F) << 8) | buffer[6]);
axis2.max = (u16)((buffer[8] << 4) | (buffer[7] >> 4));
}
/* Nintendo fix for drifting stick
buffer = ReadSPI(0x60, (isLeft ? 0x86 : 0x98), 16);
joycon[0].joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);*/
}
void Joycons::SetImuCal(Joycon& jc, std::vector<u8> buffer) {
for (std::size_t i = 0; i < jc.imu.size(); ++i) {
jc.imu[i].acc.offset = (u16)(buffer[0 + (i * 2)] | (buffer[1 + (i * 2)] << 8));
jc.imu[i].acc.scale = (u16)(buffer[0 + 6 + (i * 2)] | (buffer[1 + 6 + (i * 2)] << 8));
jc.imu[i].gyr.offset = (u16)(buffer[0 + 12 + (i * 2)] | (buffer[1 + 12 + (i * 2)] << 8));
jc.imu[i].gyr.scale = (u16)(buffer[0 + 18 + (i * 2)] | (buffer[1 + 18 + (i * 2)] << 8));
}
}
void Joycons::GetUserCalibrationData(Joycon& jc) {
std::vector<u8> buffer;
hid_set_nonblocking(jc.handle, 0);
switch (jc.type) {
case JoyControllerTypes::Left:
buffer = ReadSPI(jc.handle, CalAddr::USER_LEFT_DATA, 9);
SetJoyStickCal(buffer, jc.axis[0], jc.axis[1], true);
break;
case JoyControllerTypes::Right:
buffer = ReadSPI(jc.handle, CalAddr::USER_RIGHT_DATA, 9);
SetJoyStickCal(buffer, jc.axis[2], jc.axis[3], false);
break;
case JoyControllerTypes::Pro:
buffer = ReadSPI(jc.handle, CalAddr::USER_LEFT_DATA, 9);
SetJoyStickCal(buffer, jc.axis[0], jc.axis[1], true);
buffer = ReadSPI(jc.handle, CalAddr::USER_RIGHT_DATA, 9);
SetJoyStickCal(buffer, jc.axis[2], jc.axis[3], false);
break;
case JoyControllerTypes::None:
break;
}
buffer = ReadSPI(jc.handle, CalAddr::USER_IMU_DATA, 24);
SetImuCal(jc, buffer);
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::GetFactoryCalibrationData(Joycon& jc) {
std::vector<u8> buffer;
hid_set_nonblocking(jc.handle, 0);
switch (jc.type) {
case JoyControllerTypes::Left:
buffer = ReadSPI(jc.handle, CalAddr::FACT_LEFT_DATA, 9);
SetJoyStickCal(buffer, jc.axis[0], jc.axis[1], true);
break;
case JoyControllerTypes::Right:
buffer = ReadSPI(jc.handle, CalAddr::FACT_RIGHT_DATA, 9);
SetJoyStickCal(buffer, jc.axis[2], jc.axis[3], false);
break;
case JoyControllerTypes::Pro:
buffer = ReadSPI(jc.handle, CalAddr::FACT_LEFT_DATA, 9);
SetJoyStickCal(buffer, jc.axis[0], jc.axis[1], true);
buffer = ReadSPI(jc.handle, CalAddr::FACT_RIGHT_DATA, 9);
SetJoyStickCal(buffer, jc.axis[2], jc.axis[3], false);
break;
case JoyControllerTypes::None:
break;
}
buffer = ReadSPI(jc.handle, CalAddr::FACT_IMU_DATA, 24);
SetImuCal(jc, buffer);
hid_set_nonblocking(jc.handle, 1);
}
s16 Joycons::GetRawIMUValues(std::size_t sensor, size_t axis, std::vector<u8> buffer) {
const size_t offset = (sensor * 6) + (axis * 2);
return static_cast<s16>(buffer[13 + offset] | (buffer[14 + offset] << 8));
}
f32 Joycons::TransformAccValue(s16 raw, ImuData cal, AccSensitivity sen) {
// const f32 value = (raw - cal.offset) * cal.scale / 65535.0f / 1000.0f;
const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
switch (sen) {
case AccSensitivity::G2:
return value / 4.0f;
case AccSensitivity::G4:
return value / 2.0f;
case AccSensitivity::G8:
return value;
case AccSensitivity::G16:
return value * 2.0f;
}
return value;
}
f32 Joycons::TransformGyrValue(s16 raw, ImuData cal, GyrSensitivity sen) {
// const f32 value = (raw - cal.offset) * cal.scale / 65535.0f / 360.0f / 3.8f;
const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
switch (sen) {
case GyrSensitivity::DPS250:
return value / 8.0f;
case GyrSensitivity::DPS500:
return value / 4.0f;
case GyrSensitivity::DPS1000:
return value / 2.0f;
case GyrSensitivity::DPS2000:
return value;
}
return value;
}
void Joycons::GetIMUValues(Joycon& jc, std::vector<u8> buffer) {
for (std::size_t i = 0; i < jc.imu.size(); ++i) {
jc.imu[i].gyr.value = 0;
jc.imu[i].acc.value = 0;
}
for (std::size_t i = 0; i < jc.imu.size(); ++i) {
for (std::size_t sample = 0; sample < 3; ++sample) {
const s16 raw_gyr = GetRawIMUValues((sample * 2) + 1, i, buffer);
const s16 raw_acc = GetRawIMUValues(sample * 2, i, buffer);
switch (i) {
case 0:
jc.gyro[sample].y = TransformGyrValue(raw_gyr, jc.imu[i].gyr, jc.gsen);
jc.imu[1].gyr.value += TransformGyrValue(raw_gyr, jc.imu[i].gyr, jc.gsen) * 0.3333f;
jc.imu[1].acc.value += TransformAccValue(raw_acc, jc.imu[i].acc, jc.asen) * 0.3333f;
break;
case 1:
jc.gyro[sample].x = TransformGyrValue(raw_gyr, jc.imu[i].gyr, jc.gsen);
jc.imu[0].gyr.value += TransformGyrValue(raw_gyr, jc.imu[i].gyr, jc.gsen) * 0.3333f;
jc.imu[0].acc.value += TransformAccValue(raw_acc, jc.imu[i].acc, jc.asen) * 0.3333f;
break;
case 2:
jc.gyro[sample].z = TransformGyrValue(raw_gyr, jc.imu[i].gyr, jc.gsen);
jc.imu[2].gyr.value += TransformGyrValue(raw_gyr, jc.imu[i].gyr, jc.gsen) * 0.3333f;
jc.imu[2].acc.value += TransformAccValue(raw_acc, jc.imu[i].acc, jc.asen) * 0.3333f;
break;
}
}
}
}
void Joycons::SetImuConfig(Joycon& jc, GyrSensitivity gsen, GyrPerformance gfrec,
AccSensitivity asen, AccPerformance afrec) {
hid_set_nonblocking(jc.handle, 0);
jc.gsen = gsen;
jc.gfrec = gfrec;
jc.asen = asen;
jc.afrec = afrec;
const std::vector<u8> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
static_cast<u8>(gfrec), static_cast<u8>(afrec)};
SubCommand(jc.handle, SubComamnd::SET_IMU_SENSITIVITY, buffer, 4);
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::GetLeftPadInput(Joycon& jc, std::vector<u8> buffer) {
jc.button = static_cast<u32>(buffer[5]);
jc.button |= static_cast<u32>((buffer[4] & 0b00101001) << 16);
jc.axis[0].value = static_cast<u16>(buffer[6] | ((buffer[7] & 0xf) << 8));
jc.axis[1].value = static_cast<u16>((buffer[7] >> 4) | (buffer[8] << 4));
}
void Joycons::GetRightPadInput(Joycon& jc, std::vector<u8> buffer) {
jc.button = static_cast<u32>(buffer[3] << 8);
jc.button |= static_cast<u32>((buffer[4] & 0b00010110) << 16);
jc.axis[2].value = static_cast<u16>(buffer[9] | ((buffer[10] & 0xf) << 8));
jc.axis[3].value = static_cast<u16>((buffer[10] >> 4) | (buffer[11] << 4));
}
void Joycons::GetProPadInput(Joycon& jc, std::vector<u8> buffer) {
jc.button = static_cast<u32>(buffer[5] & 0b11001111);
jc.button |= static_cast<u32>((buffer[3] & 0b11001111) << 8);
jc.button |= static_cast<u32>((buffer[4] & 0b00111111) << 16);
jc.axis[0].value = static_cast<u16>(buffer[6] | ((buffer[7] & 0xf) << 8));
jc.axis[1].value = static_cast<u16>((buffer[7] >> 4) | (buffer[8] << 4));
jc.axis[2].value = static_cast<u16>(buffer[9] | ((buffer[10] & 0xf) << 8));
jc.axis[3].value = static_cast<u16>((buffer[10] >> 4) | (buffer[11] << 4));
}
void Joycons::SetRumble(std::size_t port, f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) {
if (!DeviceConnected(port) || !joycon[port].rumble_enabled) {
return;
}
freq_high = std::clamp(freq_high, 81.75177f, 1252.572266f);
freq_low = std::clamp(freq_low, 40.875885f, 626.286133f);
amp_high = std::clamp(amp_high, 0.0f, 1.0f);
amp_low = std::clamp(amp_low, 0.0f, 1.0f);
if (freq_high != joycon[port].hd_rumble.freq_high ||
freq_low != joycon[port].hd_rumble.freq_low ||
amp_high != joycon[port].hd_rumble.amp_high || amp_low != joycon[port].hd_rumble.amp_low) {
joycon[port].hd_rumble.freq_high = freq_high;
joycon[port].hd_rumble.freq_low = freq_low;
joycon[port].hd_rumble.amp_high = amp_high;
joycon[port].hd_rumble.amp_low = amp_low;
SendRumble(port);
}
}
void Joycons::SendRumble(std::size_t port) {
std::vector<u8> buffer(max_resp_size);
buffer[port] = static_cast<u8>(Output::RUMBLE_ONLY);
buffer[1] = GetCounter();
for (int i = 0; i < 8; ++i) {
buffer[2 + i] = default_buffer[i];
}
if (joycon[port].hd_rumble.amp_low > 0 || joycon[port].hd_rumble.amp_high > 0) {
u16 encoded_hf =
static_cast<u16>(roundf(128 * log2f(joycon[port].hd_rumble.freq_high * 0.1f)) - 0x180);
u8 encoded_lf =
static_cast<u8>(roundf(32 * log2f(joycon[port].hd_rumble.freq_low * 0.1f)) - 0x40);
u8 encoded_hamp =
static_cast<u8>(EncodeRumbleAmplification(joycon[port].hd_rumble.amp_high));
u8 encoded_lamp =
static_cast<u8>(EncodeRumbleAmplification(joycon[port].hd_rumble.amp_low));
for (u8 i = 0; i < 2; ++i) {
const u8 amplitude = i == 0 ? encoded_lamp : encoded_hamp;
const u8 offset = i * 4;
u32 encoded_amp = amplitude >> 1;
const u32 parity = (encoded_amp % 2) * 0x80;
encoded_amp >>= 1;
encoded_amp += 0x40;
buffer[2 + offset] = static_cast<u8>(encoded_hf & 0xff);
buffer[3 + offset] = static_cast<u8>((encoded_hf >> 8) & 0xff);
buffer[4 + offset] = static_cast<u8>(encoded_lf & 0xff);
buffer[3 + offset] |= static_cast<u8>(amplitude);
buffer[4 + offset] |= static_cast<u8>(parity);
buffer[5 + offset] = static_cast<u8>(encoded_amp);
}
}
SendWrite(joycon[port].handle, buffer, max_resp_size);
}
const f32 Joycons::EncodeRumbleAmplification(f32 amplification) {
if (amplification < 0.01182) {
return roundf(pow(amplification, 1.7f) * 7561);
} else if (amplification < 0.11249) {
return roundf((log(amplification) * 11.556f) + 55.3f);
} else if (amplification < 0.22498) {
return roundf((log2f(amplification) * 32) + 131);
}
return roundf((log2f(amplification) * 64) + 200);
}
const f32 Joycons::GetTemperatureCelcius(std::size_t port) {
if (!DeviceConnected(port)) {
return 0.0f;
}
return 25.0f + joycon[port].temperature * 0.0625f;
}
const f32 Joycons::GetTemperatureFahrenheit(std::size_t port) {
if (!DeviceConnected(port)) {
return 0.0f;
}
return GetTemperatureCelcius(port) * 1.8f + 32;
}
const u8 Joycons::GetBatteryLevel(std::size_t port) {
if (!DeviceConnected(port)) {
return 0x0;
}
return joycon[port].battery;
}
const std::array<u8, 15> Joycons::GetSerialNumber(std::size_t port) {
if (!DeviceConnected(port)) {
return {};
}
return joycon[port].serial_number;
}
const f32 Joycons::GetVersion(std::size_t port) {
if (!DeviceConnected(port)) {
return 0x0;
}
return joycon[port].version;
}
const JoyControllerTypes Joycons::GetDeviceType(std::size_t port) {
if (!DeviceConnected(port)) {
return JoyControllerTypes::None;
}
return joycon[port].type;
}
const std::array<u8, 6> Joycons::GetMac(std::size_t port) {
if (!DeviceConnected(port)) {
return {};
}
return joycon[port].mac;
}
const u32 Joycons::GetBodyColor(std::size_t port) {
if (!DeviceConnected(port)) {
return 0x0;
}
return joycon[port].color.body;
}
const u32 Joycons::GetButtonColor(std::size_t port) {
if (!DeviceConnected(port)) {
return 0x0;
}
return joycon[port].color.buttons;
}
const u32 Joycons::GetLeftGripColor(std::size_t port) {
if (!DeviceConnected(port)) {
return 0x0;
}
return joycon[port].color.left_grip;
}
const u32 Joycons::GetRightGripColor(std::size_t port) {
if (!DeviceConnected(port)) {
return 0x0;
}
return joycon[port].color.right_grip;
}
void Joycons::SetSerialNumber(Joycon& jc) {
std::vector<u8> buffer;
hid_set_nonblocking(jc.handle, 0);
buffer = ReadSPI(jc.handle, CalAddr::SERIAL_NUMBER, 16);
for (int i = 0; i < 15; ++i) {
jc.serial_number[i] = buffer[i + 1];
}
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::SetDeviceType(Joycon& jc) {
std::vector<u8> buffer;
hid_set_nonblocking(jc.handle, 0);
buffer = ReadSPI(jc.handle, CalAddr::DEVICE_TYPE, 1);
jc.type = static_cast<JoyControllerTypes>(buffer[0]);
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::GetColor(Joycon& jc) {
std::vector<u8> buffer;
hid_set_nonblocking(jc.handle, 0);
buffer = ReadSPI(jc.handle, CalAddr::COLOR_DATA, 12);
jc.color.body = (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
jc.color.buttons = (buffer[5] << 16) | (buffer[4] << 8) | buffer[3];
jc.color.left_grip = (buffer[8] << 16) | (buffer[7] << 8) | buffer[6];
jc.color.right_grip = (buffer[11] << 16) | (buffer[10] << 8) | buffer[9];
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::SetMac(Joycon& jc) {
wchar_t mac[255];
hid_get_serial_number_string(jc.handle, mac, std::size(mac));
for (int i = 0; i < 6; ++i) {
wchar_t value[3] = {mac[i * 2], mac[(i * 2) + 1]};
jc.mac[i] = static_cast<u8>(std::stoi(value, 0, 16));
}
}
void Joycons::SetVersionNumber(Joycon& jc) {
jc.version = 0.0f;
}
void Joycons::SetLedConfig(Joycon& jc, u8 leds) {
hid_set_nonblocking(jc.handle, 0);
jc.leds = leds;
const std::vector<u8> buffer{leds};
SubCommand(jc.handle, SubComamnd::SET_PLAYER_LIGHTS, buffer, 1);
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::EnableImu(Joycon& jc, bool enable) {
hid_set_nonblocking(jc.handle, 0);
jc.imu_enabled = enable;
const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
SubCommand(jc.handle, SubComamnd::ENABLE_IMU, buffer, 1);
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::EnableRumble(Joycon& jc, bool enable) {
hid_set_nonblocking(jc.handle, 0);
jc.rumble_enabled = enable;
const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
SubCommand(jc.handle, SubComamnd::ENABLE_VIBRATION, buffer, 1);
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::SetReportMode(Joycon& jc, ReportMode mode) {
hid_set_nonblocking(jc.handle, 0);
jc.mode = mode;
const std::vector<u8> buffer{static_cast<u8>(mode)};
SubCommand(jc.handle, SubComamnd::SET_REPORT_MODE, buffer, 1);
hid_set_nonblocking(jc.handle, 1);
}
void Joycons::UpdateJoyconData(Joycon& jc, std::vector<u8> buffer) {
switch (jc.type) {
case JoyControllerTypes::Left:
GetLeftPadInput(jc, buffer);
if (jc.imu_enabled) {
GetIMUValues(jc, buffer);
jc.imu[1].acc.value = -jc.imu[1].acc.value;
jc.imu[0].gyr.value = -jc.imu[0].gyr.value;
jc.gyro[0].x = -jc.gyro[0].x;
jc.gyro[1].x = -jc.gyro[1].x;
jc.gyro[2].x = -jc.gyro[2].x;
jc.imu[2].acc.value = -jc.imu[2].acc.value;
}
break;
case JoyControllerTypes::Right:
GetRightPadInput(jc, buffer);
if (jc.imu_enabled) {
GetIMUValues(jc, buffer);
jc.imu[0].acc.value = -jc.imu[0].acc.value;
jc.imu[1].acc.value = -jc.imu[1].acc.value;
jc.imu[2].gyr.value = -jc.imu[2].gyr.value;
jc.gyro[0].z = -jc.gyro[0].z;
jc.gyro[1].z = -jc.gyro[1].z;
jc.gyro[2].z = -jc.gyro[2].z;
}
break;
case JoyControllerTypes::Pro:
GetProPadInput(jc, buffer);
if (jc.imu_enabled) {
GetIMUValues(jc, buffer);
jc.imu[1].acc.value = -jc.imu[1].acc.value;
jc.imu[2].acc.value = -jc.imu[2].acc.value;
}
break;
case JoyControllerTypes::None:
break;
}
const auto now = std::chrono::system_clock::now();
u64 difference =
std::chrono::duration_cast<std::chrono::microseconds>(now - jc.last_motion_update).count();
jc.last_motion_update = now;
Common::Vec3f acceleration =
Common::Vec3f(jc.imu[0].acc.value, jc.imu[1].acc.value, jc.imu[2].acc.value);
jc.motion->SetAcceleration(acceleration);
for (int i = 0; i < 3; ++i) {
jc.motion->SetGyroscope(jc.gyro[i]);
jc.motion->UpdateRotation(difference / 3);
jc.motion->UpdateOrientation(difference / 3);
}
jc.battery = buffer[2] >> 4;
}
void Joycons::UpdateYuzuSettings(Joycon& jc, std::size_t port) {
if (DeviceConnected(port) && configuring) {
JCPadStatus pad;
if (jc.button != 0) {
pad.button = jc.button;
pad_queue[port].Push(pad);
}
for (std::size_t i = 0; i < jc.axis.size(); ++i) {
const u16 value = jc.axis[i].value;
const u16 origin = jc.axis[i].center;
if (value != 0) {
if (value > origin + 500 || value < origin - 500) {
pad.axis = static_cast<PadAxes>(i);
pad.axis_value = value - origin;
pad_queue[port].Push(pad);
}
}
}
for (std::size_t i = 0; i < jc.imu.size(); ++i) {
const f32 value = jc.imu[i].gyr.value;
const f32 value2 = jc.imu[i].acc.value;
if (value > 6.0f || value < -6.0f) {
pad.motion = static_cast<PadMotion>(i);
pad.motion_value = value;
pad_queue[port].Push(pad);
}
if (value2 > 2.0f || value2 < -2.0f) {
pad.motion = static_cast<PadMotion>(i + 3);
pad.motion_value = value;
pad_queue[port].Push(pad);
}
}
}
}
void Joycons::JoyconToState(Joycon& jc, JCState& state) {
for (const auto& button : PadButtonArray) {
const u32 button_value = static_cast<u32>(button);
state.buttons.insert_or_assign(button_value, jc.button & button_value);
}
for (std::size_t i = 0; i < jc.axis.size(); ++i) {
f32 axis_value = static_cast<f32>(jc.axis[i].value - jc.axis[i].center);
if (axis_value > 0) {
axis_value = axis_value / jc.axis[i].max;
} else {
axis_value = axis_value / jc.axis[i].min;
}
axis_value = std::clamp(axis_value, -1.0f, 1.0f);
state.axes.insert_or_assign(static_cast<u16>(i), axis_value);
}
Common::Vec3f gyroscope = jc.motion->GetGyroscope();
Common::Vec3f accelerometer = jc.motion->GetAcceleration();
Common::Vec3f rotation = jc.motion->GetRotations();
std::array<Common::Vec3f, 3> orientation = jc.motion->GetOrientation();
state.motion.insert_or_assign(static_cast<u16>(0), gyroscope.x);
state.motion.insert_or_assign(static_cast<u16>(1), gyroscope.y);
state.motion.insert_or_assign(static_cast<u16>(2), gyroscope.z);
state.motion.insert_or_assign(static_cast<u16>(3), accelerometer.x);
state.motion.insert_or_assign(static_cast<u16>(4), accelerometer.y);
state.motion.insert_or_assign(static_cast<u16>(5), accelerometer.z);
state.motion.insert_or_assign(static_cast<u16>(6), rotation.x);
state.motion.insert_or_assign(static_cast<u16>(7), rotation.y);
state.motion.insert_or_assign(static_cast<u16>(8), rotation.z);
state.motion.insert_or_assign(static_cast<u16>(9), orientation[0].x);
state.motion.insert_or_assign(static_cast<u16>(10), orientation[0].y);
state.motion.insert_or_assign(static_cast<u16>(11), orientation[0].z);
state.motion.insert_or_assign(static_cast<u16>(12), orientation[1].x);
state.motion.insert_or_assign(static_cast<u16>(13), orientation[1].y);
state.motion.insert_or_assign(static_cast<u16>(14), orientation[1].z);
state.motion.insert_or_assign(static_cast<u16>(15), orientation[2].x);
state.motion.insert_or_assign(static_cast<u16>(16), orientation[2].y);
state.motion.insert_or_assign(static_cast<u16>(17), orientation[2].z);
if (jc.button & 0x1 || jc.button & 0x100) {
jc.motion->ResetRotations();
jc.motion->SetQuaternion({{0, 0, -1}, 0});
}
}
void Joycons::ReadLoop() {
LOG_DEBUG(Input, "JC Adapter Read() thread started");
std::vector<u8> buffer(max_resp_size);
while (adapter_thread_running) {
for (std::size_t port = 0; port < joycon.size(); ++port) {
if (joycon[port].type != JoyControllerTypes::None) {
const int status =
hid_read_timeout(joycon[port].handle, buffer.data(), max_resp_size, 15);
if (status > 0) {
if (buffer[0] == 0x00) {
// Invalid response
LOG_ERROR(Input, "error reading buffer");
adapter_thread_running = false;
joycon[port].type = JoyControllerTypes::None;
break;
}
UpdateJoyconData(joycon[port], buffer);
UpdateYuzuSettings(joycon[port], port);
JoyconToState(joycon[port], joycon[port].state);
/*
printf("ctrl:%d\tbtn:%d\tx1:%d\ty1:%d\tx2:%d\ty2:%d\tax:%.3f\tay:%.3f\taz:%.3f\tgx:%.3f\tgy:%.3f,
" "gz:%.3f\tbuffer:",port, joycon[port].button, joycon[port].axis[0].value -
joycon[port].axis[0].center, joycon[port].axis[1].value -
joycon[port].axis[1].center, joycon[port].axis[2].value -
joycon[port].axis[2].center, joycon[port].axis[3].value -
joycon[port].axis[3].center, joycon[port].imu[0].acc.value,
joycon[port].imu[1].acc.value, joycon[port].imu[2].acc.value,
joycon[port].imu[0].gyr.value, joycon[port].imu[1].gyr.value,
joycon[port].imu[2].gyr.value);
for (int i = 0; i < 7; ++i) {
printf("%02hx ", buffer[i]);
}
printf("\n");*/
}
}
}
std::this_thread::yield();
}
}
void Joycons::Setup() {
// Initialize all controllers as unplugged
for (std::size_t port = 0; port < joycon.size(); ++port) {
joycon[port].type = JoyControllerTypes::None;
for (std::size_t i = 0; i < joycon[port].axis.size(); ++i) {
joycon[port].axis[i].value = 0;
}
for (std::size_t i = 0; i < joycon[port].imu.size(); ++i) {
joycon[port].imu[i].acc.value = 0;
joycon[port].imu[i].gyr.value = 0;
}
joycon[port].hd_rumble.amp_high = 0;
joycon[port].hd_rumble.amp_low = 0;
joycon[port].hd_rumble.freq_high = 160.0f;
joycon[port].hd_rumble.freq_low = 80.0f;
}
std::size_t port = 0;
hid_device_info* devs = hid_enumerate(0x057e, 0x2006);
hid_device_info* cur_dev = devs;
while (cur_dev) {
printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls",
cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
printf("\n");
printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string);
printf(" Product: %ls\n", cur_dev->product_string);
printf(" Release: %hx\n", cur_dev->release_number);
printf(" Interface: %d\n", cur_dev->interface_number);
printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page);
printf("\n");
if (CheckDeviceAccess(port, cur_dev)) {
// JC Adapter found and accessible, registering it
++port;
}
cur_dev = cur_dev->next;
}
devs = hid_enumerate(0x057e, 0x2007);
cur_dev = devs;
while (cur_dev) {
printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls",
cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
printf("\n");
printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string);
printf(" Product: %ls\n", cur_dev->product_string);
printf(" Release: %hx\n", cur_dev->release_number);
printf(" Interface: %d\n", cur_dev->interface_number);
printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page);
printf("\n");
if (CheckDeviceAccess(port, cur_dev)) {
// JC Adapter found and accessible, registering it
++port;
}
cur_dev = cur_dev->next;
} //*/
/*
devs = hid_enumerate(0x057e, 0x2009);
cur_dev = devs;
while (cur_dev) {
printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls",
cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
printf("\n");
printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string);
printf(" Product: %ls\n", cur_dev->product_string);
printf(" Release: %hx\n", cur_dev->release_number);
printf(" Interface: %d\n", cur_dev->interface_number);
printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page);
printf("\n");
if (CheckDeviceAccess(port, cur_dev)) {
// JC Adapter found and accessible, registering it
++port;
}
cur_dev = cur_dev->next;
}
*/
GetJCEndpoint();
}
bool Joycons::CheckDeviceAccess(std::size_t port, hid_device_info* device) {
if (device->vendor_id != 0x057e ||
!(device->product_id == 0x2006 || device->product_id == 0x2007 ||
device->product_id == 0x2009)) {
// This isn't the device we are looking for.
return false;
}
joycon[port].handle = hid_open(device->vendor_id, device->product_id, device->serial_number);
if (!joycon[port].handle) {
LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.",
device->vendor_id, device->product_id);
return false;
}
hid_set_nonblocking(joycon[port].handle, 1);
switch (device->product_id) {
case 0x2006:
joycon[port].type = JoyControllerTypes::Left;
break;
case 0x2007:
joycon[port].type = JoyControllerTypes::Right;
break;
case 0x2009:
joycon[port].type = JoyControllerTypes::Pro;
break;
}
return true;
}
void Joycons::GetJCEndpoint() {
// init joycons
for (u8 port = 0; port < joycon.size(); ++port) {
joycon[port].port = port;
if (joycon[port].type != JoyControllerTypes::None) {
SetMac(joycon[port]);
SetSerialNumber(joycon[port]);
SetVersionNumber(joycon[port]);
SetDeviceType(joycon[port]);
GetFactoryCalibrationData(joycon[port]);
GetColor(joycon[port]);
SetLedConfig(joycon[port], port + 1);
EnableImu(joycon[port], true);
SetImuConfig(joycon[port], GyrSensitivity::DPS2000, GyrPerformance::HZ833,
AccSensitivity::G8, AccPerformance::HZ100);
EnableRumble(joycon[port], true);
SetReportMode(joycon[port], ReportMode::STANDARD_FULL_60HZ);
joycon[port].motion = new InputCommon::MotionInput(0.3f, 0.005f, 0.0f);
joycon[port].motion->SetGyroThreshold(0.001f);
// joycon[port].motion->EnableReset(false);
}
}
adapter_thread_running = true;
adapter_input_thread = std::thread(&Joycons::ReadLoop, this); // Read input
}
Joycons::~Joycons() {
Reset();
}
void Joycons::Reset() {
if (adapter_thread_running) {
adapter_thread_running = false;
}
if (adapter_input_thread.joinable()) {
adapter_input_thread.join();
}
for (u8 port = 0; port < joycon.size(); ++port) {
joycon[port].type = JoyControllerTypes::None;
if (joycon[port].handle) {
joycon[port].handle = nullptr;
}
}
hid_exit();
}
std::string Joycons::JoyconName(std::size_t port) const {
switch (joycon[port].type) {
case JoyControllerTypes::Left:
return "Left Joycon";
break;
case JoyControllerTypes::Right:
return "Right Joycon";
break;
case JoyControllerTypes::Pro:
return "Pro Controller";
break;
case JoyControllerTypes::None:
return "Unknow Joycon";
break;
}
return "Unknow Joycon";
}
std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
// std::scoped_lock lock(joystick_map_mutex);
std::vector<Common::ParamPackage> devices;
for (const auto& controller : joycon) {
if (DeviceConnected(controller.port)) {
std::string name = fmt::format("{} {}", JoyconName(controller.port), controller.port);
devices.emplace_back(Common::ParamPackage{
{"class", "jcpad"},
{"display", std::move(name)},
{"port", std::to_string(controller.port)},
});
}
}
return devices;
}
InputCommon::ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
// This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
// We will add those afterwards
// This list also excludes Screenshot since theres not really a mapping for that
static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 20>
switch_to_jcadapter_button = {
std::pair{Settings::NativeButton::A, PadButton::BUTTON_B},
{Settings::NativeButton::B, PadButton::BUTTON_A},
{Settings::NativeButton::X, PadButton::BUTTON_Y},
{Settings::NativeButton::Y, PadButton::BUTTON_X},
{Settings::NativeButton::LStick, PadButton::STICK_L},
{Settings::NativeButton::RStick, PadButton::STICK_R},
{Settings::NativeButton::L, PadButton::TRIGGER_L},
{Settings::NativeButton::R, PadButton::TRIGGER_R},
{Settings::NativeButton::Plus, PadButton::BUTTON_PLUS},
{Settings::NativeButton::Minus, PadButton::BUTTON_MINUS},
{Settings::NativeButton::DLeft, PadButton::BUTTON_LEFT},
{Settings::NativeButton::DUp, PadButton::BUTTON_UP},
{Settings::NativeButton::DRight, PadButton::BUTTON_RIGHT},
{Settings::NativeButton::DDown, PadButton::BUTTON_DOWN},
{Settings::NativeButton::SL, PadButton::BUTTON_L_SL},
{Settings::NativeButton::SR, PadButton::BUTTON_L_SR},
{Settings::NativeButton::Screenshot, PadButton::BUTTON_CAPTURE},
{Settings::NativeButton::Home, PadButton::BUTTON_HOME},
{Settings::NativeButton::ZL, PadButton::TRIGGER_ZL},
{Settings::NativeButton::ZR, PadButton::TRIGGER_ZR},
};
if (!params.Has("port")) {
return {};
}
InputCommon::ButtonMapping mapping{};
for (const auto& [switch_button, jcadapter_button] : switch_to_jcadapter_button) {
Common::ParamPackage button_params({{"engine", "jcpad"}});
button_params.Set("port", params.Get("port", 0));
button_params.Set("button", static_cast<int>(jcadapter_button));
mapping[switch_button] = button_params;
}
return mapping;
}
InputCommon::AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("port")) {
return {};
}
InputCommon::AnalogMapping mapping = {};
Common::ParamPackage left_analog_params;
left_analog_params.Set("engine", "jcpad");
left_analog_params.Set("port", params.Get("port", 0));
left_analog_params.Set("axis_x", 0);
left_analog_params.Set("axis_y", 1);
mapping[Settings::NativeAnalog::LStick] = left_analog_params;
Common::ParamPackage right_analog_params;
right_analog_params.Set("engine", "jcpad");
right_analog_params.Set("port", params.Get("port", 0));
right_analog_params.Set("axis_x", 2);
right_analog_params.Set("axis_y", 3);
mapping[Settings::NativeAnalog::RStick] = right_analog_params;
return mapping;
}
bool Joycons::DeviceConnected(std::size_t port) const {
if (port > 4) {
return false;
}
return joycon[port].type != JoyControllerTypes::None;
}
void Joycons::ResetDeviceType(std::size_t port) {
if (port > 4) {
return;
}
joycon[port].type = JoyControllerTypes::None;
}
void Joycons::BeginConfiguration() {
for (auto& pq : pad_queue) {
pq.Clear();
}
configuring = true;
}
void Joycons::EndConfiguration() {
for (auto& pq : pad_queue) {
pq.Clear();
}
configuring = false;
}
JCState& Joycons::GetPadState(std::size_t port) {
return joycon[port].state;
}
const JCState& Joycons::GetPadState(std::size_t port) const {
return joycon[port].state;
}
std::array<Common::SPSCQueue<JCPadStatus>, 4>& Joycons::GetPadQueue() {
return pad_queue;
}
const std::array<Common::SPSCQueue<JCPadStatus>, 4>& Joycons::GetPadQueue() const {
return pad_queue;
}
} // namespace JCAdapter

View File

@@ -0,0 +1,371 @@
// Copyright 2020 Yuzu Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <functional>
#include <mutex>
#include <span>
#include <thread>
#include <unordered_map>
#include <hidapi.h>
#include "common/param_package.h"
#include "common/threadsafe_queue.h"
#include "common/vector_math.h"
#include "input_common/main.h"
#include "input_common/motion_input.h"
#include "input_common/settings.h"
namespace JCAdapter {
enum class PadButton {
BUTTON_DOWN = 0x000001,
BUTTON_UP = 0x000002,
BUTTON_RIGHT = 0x000004,
BUTTON_LEFT = 0x000008,
BUTTON_L_SR = 0x000010,
BUTTON_L_SL = 0x000020,
TRIGGER_L = 0x000040,
TRIGGER_ZL = 0x000080,
BUTTON_Y = 0x000100,
BUTTON_X = 0x000200,
BUTTON_B = 0x000400,
BUTTON_A = 0x000800,
BUTTON_R_SL = 0x001000,
BUTTON_R_SR = 0x002000,
TRIGGER_R = 0x004000,
TRIGGER_ZR = 0x008000,
BUTTON_MINUS = 0x010000,
BUTTON_PLUS = 0x020000,
STICK_R = 0x040000,
STICK_L = 0x080000,
BUTTON_HOME = 0x100000,
BUTTON_CAPTURE = 0x200000,
// Below is for compatibility with "AxisButton" and "MotionButton" type
STICK = 0x400000,
MOTION = 0x800000,
};
enum class Output {
RUMBLE_AND_SUBCMD = 0x01,
FW_UPDATE_PKT = 0x03,
RUMBLE_ONLY = 0x10,
MCU_DATA = 0x11,
USB_CMD = 0x80,
};
enum class SubComamnd {
STATE = 0x00,
MANUAL_BT_PAIRING = 0x01,
REQ_DEV_INFO = 0x02,
SET_REPORT_MODE = 0x03,
TRIGGERS_ELAPSED = 0x04,
GET_PAGE_LIST_STATE = 0x05,
SET_HCI_STATE = 0x06,
RESET_PAIRING_INFO = 0x07,
LOW_POWER_MODE = 0x08,
SPI_FLASH_READ = 0x10,
SPI_FLASH_WRITE = 0x11,
RESET_MCU = 0x20,
SET_MCU_CONFIG = 0x21,
SET_MCU_STATE = 0x22,
SET_PLAYER_LIGHTS = 0x30,
GET_PLAYER_LIGHTS = 0x31,
SET_HOME_LIGHT = 0x38,
ENABLE_IMU = 0x40,
SET_IMU_SENSITIVITY = 0x41,
WRITE_IMU_REG = 0x42,
READ_IMU_REG = 0x43,
ENABLE_VIBRATION = 0x48,
GET_REGULATED_VOLTAGE = 0x50,
};
enum class CalAddr {
USER_LEFT_MAGIC = 0X8010,
USER_LEFT_DATA = 0X8012,
USER_RIGHT_MAGIC = 0X801B,
USER_RIGHT_DATA = 0X801D,
USER_IMU_MAGIC = 0X8026,
USER_IMU_DATA = 0X8028,
SERIAL_NUMBER = 0X6000,
DEVICE_TYPE = 0X6012,
COLOR_EXIST = 0X601B,
FACT_LEFT_DATA = 0X603d,
FACT_RIGHT_DATA = 0X6046,
COLOR_DATA = 0X6050,
FACT_IMU_DATA = 0X6020,
};
enum class ReportMode {
ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
MCU_UPDATE_STATE = 0x23,
STANDARD_FULL_60HZ = 0x30,
NFC_IR_MODE_60HZ = 0x31,
SIMPLE_HID_MODE = 0x3F,
};
enum class GyrSensitivity {
DPS250,
DPS500,
DPS1000,
DPS2000,
};
enum class AccSensitivity {
G8,
G4,
G2,
G16,
};
enum class GyrPerformance {
HZ833,
HZ208,
};
enum class AccPerformance {
HZ200,
HZ100,
};
enum class PadAxes {
StickX,
StickY,
SubstickX,
SubstickY,
Undefined,
};
enum class PadMotion {
GyrX,
GyrY,
GyrZ,
AccX,
AccY,
AccZ,
Undefined,
};
enum class JoyControllerTypes {
None,
Left,
Right,
Pro,
};
extern const std::array<PadButton, 22> PadButtonArray;
struct JCPadStatus {
u32 button{};
std::array<u16, 4> axis_values{};
std::array<f32, 6> imu_values{};
std::array<f32, 9> orientation{};
u8 port{};
PadAxes axis{PadAxes::Undefined};
s16 axis_value{0};
PadMotion motion{PadMotion::Undefined};
f32 motion_value{0.0f};
};
struct JCState {
std::unordered_map<int, bool> buttons;
std::unordered_map<int, float> axes;
std::unordered_map<int, float> motion;
};
class Joycons {
public:
/// Initialize the JC Adapter capture and read sequence
Joycons();
/// Close the adapter read thread and release the adapter
~Joycons();
/// Used for polling
void BeginConfiguration();
void EndConfiguration();
std::vector<Common::ParamPackage> GetInputDevices() const;
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params);
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params);
bool DeviceConnected(std::size_t port) const;
void SetRumble(std::size_t port, f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low);
const f32 GetTemperatureCelcius(std::size_t port);
const f32 GetTemperatureFahrenheit(std::size_t port);
const u8 GetBatteryLevel(std::size_t port);
const std::array<u8, 15> GetSerialNumber(std::size_t port);
const f32 GetVersion(std::size_t port);
const JoyControllerTypes GetDeviceType(std::size_t port);
const std::array<u8, 6> GetMac(std::size_t port);
const u32 GetBodyColor(std::size_t port);
const u32 GetButtonColor(std::size_t port);
const u32 GetLeftGripColor(std::size_t port);
const u32 GetRightGripColor(std::size_t port);
std::array<Common::SPSCQueue<JCPadStatus>, 4>& GetPadQueue();
const std::array<Common::SPSCQueue<JCPadStatus>, 4>& GetPadQueue() const;
JCState& GetPadState(std::size_t port);
const JCState& GetPadState(std::size_t port) const;
private:
struct HDRumble {
f32 freq_high;
f32 freq_low;
f32 amp_high;
f32 amp_low;
};
struct ImuData {
s16 offset;
s16 scale;
f32 value;
};
struct Imu {
ImuData acc;
ImuData gyr;
};
struct JoyStick {
u16 max;
u16 min;
u16 center;
u16 deadzone;
u16 value;
};
struct Color {
u32 body;
u32 buttons;
u32 left_grip;
u32 right_grip;
};
struct Joycon {
hid_device* handle = nullptr;
JCState state;
// Harware config
GyrSensitivity gsen;
GyrPerformance gfrec;
AccSensitivity asen;
AccPerformance afrec;
ReportMode mode;
bool imu_enabled;
bool rumble_enabled;
u8 leds;
// Fixed value info
f32 version;
std::array<u8, 6> mac;
JoyControllerTypes type;
std::array<u8, 15> serial_number;
Color color;
u8 port;
// Realtime values
InputCommon::MotionInput* motion = nullptr;
std::array<Common::Vec3f, 3> gyro;
std::array<JoyStick, 4> axis;
std::array<Imu, 3> imu;
HDRumble hd_rumble;
u32 button;
u32 temperature;
u8 battery;
std::chrono::time_point<std::chrono::system_clock> last_motion_update;
};
// Low level commands for communication
void SendWrite(hid_device* handle, std::vector<u8> buffer, int size);
u8 GetCounter();
std::vector<u8> SubCommand(hid_device* handle, SubComamnd sc, std::vector<u8> buffer, int size);
std::vector<u8> GetResponse(hid_device* handle, SubComamnd sc);
std::vector<u8> ReadSPI(hid_device* handle, CalAddr addr, u8 size);
// Reads calibration values
void SetJoyStickCal(std::vector<u8> buffer, JoyStick& axis1, JoyStick& axis2, bool left);
void SetImuCal(Joycon& jc, std::vector<u8> buffer);
void GetUserCalibrationData(Joycon& jc);
void GetFactoryCalibrationData(Joycon& jc);
// Reads buttons and stick values
void GetLeftPadInput(Joycon& jc, std::vector<u8> buffer);
void GetRightPadInput(Joycon& jc, std::vector<u8> buffer);
void GetProPadInput(Joycon& jc, std::vector<u8> buffer);
// Reads gyro and accelerometer values
f32 TransformAccValue(s16 raw, ImuData cal, AccSensitivity sen);
f32 TransformGyrValue(s16 raw, ImuData cal, GyrSensitivity sen);
s16 GetRawIMUValues(size_t sensor, size_t axis, std::vector<u8> buffer);
void GetIMUValues(Joycon& jc, std::vector<u8> buffer);
void SetImuConfig(Joycon& jc, GyrSensitivity gsen, GyrPerformance gfrec, AccSensitivity asen,
AccPerformance afrec);
// Sends rumble state
void SendRumble(std::size_t port);
const f32 EncodeRumbleAmplification(f32 amplification);
// Reads color values
void GetColor(Joycon& jc);
// Reads device hardware values
void SetMac(Joycon& jc);
void SetSerialNumber(Joycon& jc);
void SetDeviceType(Joycon& jc);
void SetVersionNumber(Joycon& jc);
// Write device hardware configuration
void SetLedConfig(Joycon& jc, u8 leds);
void EnableImu(Joycon& jc, bool enable);
void EnableRumble(Joycon& jc, bool enable);
void SetReportMode(Joycon& jc, ReportMode mode);
void UpdateJoyconData(Joycon& jc, std::vector<u8> buffer);
void UpdateYuzuSettings(Joycon& jc, std::size_t port);
void JoyconToState(Joycon& jc, JCState& state);
std::string JoyconName(std::size_t port) const;
void ReadLoop();
/// Resets status of device connected to port
void ResetDeviceType(std::size_t port);
/// Returns true if we successfully gain access to JC Adapter
bool CheckDeviceAccess(std::size_t port, hid_device_info* device);
/// Captures JC Adapter endpoint address,
void GetJCEndpoint();
/// For shutting down, clear all data, join all threads, release usb
void Reset();
/// For use in initialization, querying devices to find the adapter
void Setup();
// void UpdateOrientation(Joycon& jc, u64 time, std::size_t iteration);
std::thread adapter_input_thread;
bool adapter_thread_running;
bool configuring = false;
std::array<Joycon, 4> joycon;
std::array<Common::SPSCQueue<JCPadStatus>, 4> pad_queue;
u8 global_counter;
static constexpr u32 max_resp_size = 49;
static constexpr std::array<u8, 8> default_buffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
};
} // namespace JCAdapter

View File

@@ -0,0 +1,459 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#include <list>
#include <mutex>
#include <utility>
#include "common/assert.h"
#include "common/threadsafe_queue.h"
#include "input_common/joycon/jc_adapter.h"
#include "input_common/joycon/jc_poller.h"
namespace InputCommon {
class JCButton final : public Input::ButtonDevice {
public:
explicit JCButton(int port_, int button_, JCAdapter::Joycons* adapter)
: port(port_), button(button_), jcadapter(adapter) {}
~JCButton() override;
bool GetStatus() const override {
if (jcadapter->DeviceConnected(port)) {
return jcadapter->GetPadState(port).buttons.at(button);
}
return false;
}
bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
if (jcadapter->DeviceConnected(port)) {
jcadapter->SetRumble(port, amp_high, amp_low, freq_high, freq_low);
return true;
}
return false;
}
private:
const int port;
const int button;
JCAdapter::Joycons* jcadapter;
};
class JCAxisButton final : public Input::ButtonDevice {
public:
explicit JCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
JCAdapter::Joycons* adapter)
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
jcadapter(adapter) {}
bool GetStatus() const override {
if (jcadapter->DeviceConnected(port)) {
const float axis_value = jcadapter->GetPadState(port).axes.at(axis);
if (trigger_if_greater) {
// TODO: Might be worthwile to set a slider for the trigger threshold. It is
// currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
return axis_value > threshold;
}
return axis_value < -threshold;
}
return false;
}
bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
if (jcadapter->DeviceConnected(port)) {
jcadapter->SetRumble(port, amp_high, amp_low, freq_high, freq_low);
return true;
}
return false;
}
private:
const int port;
const int axis;
float threshold;
bool trigger_if_greater;
JCAdapter::Joycons* jcadapter;
};
// Motion buttons are a temporary way to test motion
class JCMotionButton final : public Input::ButtonDevice {
public:
explicit JCMotionButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
JCAdapter::Joycons* adapter)
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
jcadapter(adapter) {}
bool GetStatus() const override {
if (jcadapter->DeviceConnected(port)) {
const float axis_value = jcadapter->GetPadState(port).motion.at(axis);
if (trigger_if_greater) {
// TODO: Might be worthwile to set a slider for the trigger threshold. It is
// currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
return axis_value > threshold;
}
return axis_value < -threshold;
}
return false;
}
bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
if (jcadapter->DeviceConnected(port)) {
jcadapter->SetRumble(port, amp_high, amp_low, freq_high, freq_low);
return true;
}
return false;
}
private:
const int port;
const int axis;
float threshold;
bool trigger_if_greater;
JCAdapter::Joycons* jcadapter;
};
JCButtonFactory::JCButtonFactory(std::shared_ptr<JCAdapter::Joycons> adapter_)
: adapter(std::move(adapter_)) {}
JCButton::~JCButton() = default;
std::unique_ptr<Input::ButtonDevice> JCButtonFactory::Create(const Common::ParamPackage& params) {
const int button_id = params.Get("button", 0);
const int port = params.Get("port", 0);
constexpr int PAD_STICK_ID = static_cast<u32>(JCAdapter::PadButton::STICK);
constexpr int PAD_MOTION_ID = static_cast<u32>(JCAdapter::PadButton::MOTION);
// button is not an axis/stick button
if (button_id != PAD_STICK_ID && button_id != PAD_MOTION_ID) {
auto button = std::make_unique<JCButton>(port, button_id, adapter.get());
return std::move(button);
}
// For Axis buttons, used by the binary sticks.
if (button_id == PAD_STICK_ID && button_id != PAD_MOTION_ID) {
const int axis = params.Get("axis", 0);
const float threshold = params.Get("threshold", 0.25f);
const std::string direction_name = params.Get("direction", "");
bool trigger_if_greater;
if (direction_name == "+") {
trigger_if_greater = true;
} else if (direction_name == "-") {
trigger_if_greater = false;
} else {
trigger_if_greater = true;
LOG_ERROR(Input, "Unknown direction {}", direction_name);
}
return std::make_unique<JCAxisButton>(port, axis, threshold, trigger_if_greater,
adapter.get());
}
// For Motion buttons, used by the binary motion.
if (button_id != PAD_STICK_ID && button_id == PAD_MOTION_ID) {
const int axis = params.Get("motion", 0);
const float threshold = params.Get("threshold", 0.5f);
const std::string direction_name = params.Get("direction", "");
bool trigger_if_greater;
if (direction_name == "+") {
trigger_if_greater = true;
} else if (direction_name == "-") {
trigger_if_greater = false;
} else {
trigger_if_greater = true;
LOG_ERROR(Input, "Unknown direction {}", direction_name);
}
return std::make_unique<JCMotionButton>(port, axis, threshold, trigger_if_greater,
adapter.get());
}
UNREACHABLE();
return nullptr;
}
Common::ParamPackage JCButtonFactory::GetNextInput() {
Common::ParamPackage params;
JCAdapter::JCPadStatus pad;
auto& queue = adapter->GetPadQueue();
for (std::size_t port = 0; port < queue.size(); ++port) {
while (queue[port].Pop(pad)) {
// This while loop will break on the earliest detected button
params.Set("engine", "jcpad");
params.Set("port", static_cast<int>(port));
for (const auto& button : JCAdapter::PadButtonArray) {
const int button_value = static_cast<u32>(button);
if (pad.button & button_value) {
params.Set("button", button_value);
break;
}
}
// For Axis button implementation
if (pad.axis != JCAdapter::PadAxes::Undefined) {
params.Set("axis", static_cast<u16>(pad.axis));
params.Set("button", static_cast<int>(JCAdapter::PadButton::STICK));
if (pad.axis_value > 0) {
params.Set("direction", "+");
params.Set("threshold", "0.25");
} else {
params.Set("direction", "-");
params.Set("threshold", "0.25");
}
break;
}
// For Motion button implementation
if (pad.motion != JCAdapter::PadMotion::Undefined) {
params.Set("motion", static_cast<u16>(pad.motion));
params.Set("button", static_cast<int>(JCAdapter::PadButton::MOTION));
if (pad.motion_value > 0) {
params.Set("direction", "+");
params.Set("threshold", "0.5");
} else {
params.Set("direction", "-");
params.Set("threshold", "0.5");
}
break;
}
}
}
return params;
}
void JCButtonFactory::BeginConfiguration() {
polling = true;
adapter->BeginConfiguration();
}
void JCButtonFactory::EndConfiguration() {
polling = false;
adapter->EndConfiguration();
}
class JCAnalog final : public Input::AnalogDevice {
public:
JCAnalog(int port_, int axis_x_, int axis_y_, bool invert_x_, bool invert_y_, float deadzone_,
float range_, JCAdapter::Joycons* adapter)
: port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
deadzone(deadzone_), range(range_), jcadapter(adapter) {}
float GetAxis(int axis) const {
if (jcadapter->DeviceConnected(port)) {
std::lock_guard lock{mutex};
if (axis < 10) {
return jcadapter->GetPadState(port).axes.at(axis);
} else {
return jcadapter->GetPadState(port).motion.at(axis - 10);
}
}
return 0.0f;
}
std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
float x = GetAxis(analog_axis_x);
float y = GetAxis(analog_axis_y);
if (invert_x) {
x = -x;
}
if (invert_y) {
y = -y;
}
// Make sure the coordinates are in the unit circle,
// otherwise normalize it.
float r = x * x + y * y;
if (r > 1.0f) {
r = std::sqrt(r);
x /= r;
y /= r;
}
return {x, y};
}
std::tuple<float, float> GetStatus() const override {
const auto [x, y] = GetAnalog(axis_x, axis_y);
const float r = std::sqrt((x * x) + (y * y));
if (r > deadzone) {
return {x / r * (r - deadzone) / (1 - deadzone),
y / r * (r - deadzone) / (1 - deadzone)};
}
return {0.0f, 0.0f};
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
const auto [x, y] = GetStatus();
const float directional_deadzone = 0.4f;
switch (direction) {
case Input::AnalogDirection::RIGHT:
return x > directional_deadzone;
case Input::AnalogDirection::LEFT:
return x < -directional_deadzone;
case Input::AnalogDirection::UP:
return y > directional_deadzone;
case Input::AnalogDirection::DOWN:
return y < -directional_deadzone;
}
return false;
}
private:
const int port;
const int axis_x;
const int axis_y;
const bool invert_x;
const bool invert_y;
const float deadzone;
const float range;
JCAdapter::Joycons* jcadapter;
mutable std::mutex mutex;
};
/// An analog device factory that creates analog devices from JC Adapter
JCAnalogFactory::JCAnalogFactory(std::shared_ptr<JCAdapter::Joycons> adapter_)
: adapter(std::move(adapter_)) {}
/**
* Creates analog device from joystick axes
* @param params contains parameters for creating the device:
* - "port": the nth jcpad on the adapter
* - "axis_x": the index of the axis to be bind as x-axis
* - "axis_y": the index of the axis to be bind as y-axis
*/
std::unique_ptr<Input::AnalogDevice> JCAnalogFactory::Create(const Common::ParamPackage& params) {
const auto port = static_cast<u32>(params.Get("port", 0));
const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
const std::string invert_x_value = params.Get("invert_x", "+");
const std::string invert_y_value = params.Get("invert_y", "+");
const bool invert_x = invert_x_value == "-";
const bool invert_y = invert_y_value == "-";
return std::make_unique<JCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
adapter.get());
}
void JCAnalogFactory::BeginConfiguration() {
polling = true;
adapter->BeginConfiguration();
}
void JCAnalogFactory::EndConfiguration() {
polling = false;
adapter->EndConfiguration();
}
Common::ParamPackage JCAnalogFactory::GetNextInput() {
JCAdapter::JCPadStatus pad;
auto& queue = adapter->GetPadQueue();
for (std::size_t port = 0; port < queue.size(); ++port) {
while (queue[port].Pop(pad)) {
if (pad.axis != JCAdapter::PadAxes::Undefined && std::abs(pad.axis_value) > 0.1) {
// An analog device needs two axes, so we need to store the axis for later and wait
// for a second input event. The axes also must be from the same joystick.
const u16 axis = static_cast<u16>(pad.axis);
if (analog_x_axis == -1) {
analog_x_axis = axis;
controller_number = static_cast<int>(port);
} else if (analog_y_axis == -1 && analog_x_axis != axis &&
controller_number == static_cast<int>(port)) {
analog_y_axis = axis;
}
} else if (pad.motion != JCAdapter::PadMotion::Undefined &&
std::abs(pad.motion_value) > 1) {
const u16 axis = 10 + static_cast<u16>(pad.motion);
if (analog_x_axis == -1) {
analog_x_axis = axis;
controller_number = static_cast<int>(port);
} else if (analog_y_axis == -1 && analog_x_axis != axis &&
controller_number == static_cast<int>(port)) {
analog_y_axis = axis;
}
}
}
}
Common::ParamPackage params;
if (analog_x_axis != -1 && analog_y_axis != -1) {
params.Set("engine", "jcpad");
params.Set("port", controller_number);
params.Set("axis_x", analog_x_axis);
params.Set("axis_y", analog_y_axis);
analog_x_axis = -1;
analog_y_axis = -1;
controller_number = -1;
return params;
}
return params;
}
class JCMotion final : public Input::MotionDevice {
public:
JCMotion(int port_, JCAdapter::Joycons* adapter) : port(port_), jcadapter(adapter) {}
std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
std::array<Common::Vec3f, 3>>
GetStatus() const override {
auto motion = jcadapter->GetPadState(port).motion;
if (motion.size() == 18) {
auto gyroscope = Common::MakeVec(motion.at(0), motion.at(1), motion.at(2));
auto accelerometer = Common::MakeVec(motion.at(3), motion.at(4), motion.at(5));
auto rotation = Common::MakeVec(motion.at(6), motion.at(7), motion.at(8));
std::array<Common::Vec3f, 3> orientation{};
orientation[0] = Common::MakeVec(motion.at(9), motion.at(10), motion.at(11));
orientation[1] = Common::MakeVec(motion.at(12), motion.at(13), motion.at(14));
orientation[2] = Common::MakeVec(motion.at(15), motion.at(16), motion.at(17));
return std::make_tuple(accelerometer, gyroscope, rotation, orientation);
}
return {};
}
private:
const int port;
JCAdapter::Joycons* jcadapter;
mutable std::mutex mutex;
};
/// A motion device factory that creates motion devices from JC Adapter
JCMotionFactory::JCMotionFactory(std::shared_ptr<JCAdapter::Joycons> adapter_)
: adapter(std::move(adapter_)) {}
/**
* Creates motion device
* @param params contains parameters for creating the device:
* - "port": the nth jcpad on the adapter
*/
std::unique_ptr<Input::MotionDevice> JCMotionFactory::Create(const Common::ParamPackage& params) {
const int port = params.Get("port", 0);
return std::make_unique<JCMotion>(port, adapter.get());
}
void JCMotionFactory::BeginConfiguration() {
polling = true;
adapter->BeginConfiguration();
}
void JCMotionFactory::EndConfiguration() {
polling = false;
adapter->EndConfiguration();
}
Common::ParamPackage JCMotionFactory::GetNextInput() {
Common::ParamPackage params;
JCAdapter::JCPadStatus pad;
auto& queue = adapter->GetPadQueue();
for (std::size_t port = 0; port < queue.size(); ++port) {
while (queue[port].Pop(pad)) {
if (pad.motion == JCAdapter::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
continue;
}
params.Set("engine", "jcpad");
params.Set("motion", static_cast<u16>(pad.motion));
params.Set("port", static_cast<int>(port));
return params;
}
}
return params;
}
} // namespace InputCommon

View File

@@ -0,0 +1,89 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/frontend/input.h"
#include "input_common/joycon/jc_adapter.h"
namespace InputCommon {
/**
* A button device factory representing a jcpad. It receives jcpad events and forward them
* to all button devices it created.
*/
class JCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
public:
explicit JCButtonFactory(std::shared_ptr<JCAdapter::Joycons> adapter_);
/**
* Creates a button device from a button press
* @param params contains parameters for creating the device:
* - "code": the code of the key to bind with the button
*/
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
Common::ParamPackage GetNextInput();
/// For device input configuration/polling
void BeginConfiguration();
void EndConfiguration();
bool IsPolling() const {
return polling;
}
private:
std::shared_ptr<JCAdapter::Joycons> adapter;
bool polling = false;
};
/// An analog device factory that creates analog devices from JC Adapter
class JCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
public:
explicit JCAnalogFactory(std::shared_ptr<JCAdapter::Joycons> adapter_);
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
Common::ParamPackage GetNextInput();
/// For device input configuration/polling
void BeginConfiguration();
void EndConfiguration();
bool IsPolling() const {
return polling;
}
private:
std::shared_ptr<JCAdapter::Joycons> adapter;
int analog_x_axis = -1;
int analog_y_axis = -1;
int controller_number = -1;
bool polling = false;
};
/// A motion device factory that creates motion devices from JC Adapter
class JCMotionFactory final : public Input::Factory<Input::MotionDevice> {
public:
explicit JCMotionFactory(std::shared_ptr<JCAdapter::Joycons> adapter_);
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
Common::ParamPackage GetNextInput();
/// For device input configuration/polling
void BeginConfiguration();
void EndConfiguration();
bool IsPolling() const {
return polling;
}
private:
std::shared_ptr<JCAdapter::Joycons> adapter;
bool polling = false;
};
} // namespace InputCommon

View File

@@ -8,6 +8,8 @@
#include "input_common/analog_from_button.h"
#include "input_common/gcadapter/gc_adapter.h"
#include "input_common/gcadapter/gc_poller.h"
#include "input_common/joycon/jc_adapter.h"
#include "input_common/joycon/jc_poller.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_from_button.h"
@@ -32,6 +34,14 @@ struct InputSubsystem::Impl {
gcvibration = std::make_shared<GCVibrationFactory>(gcadapter);
Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration);
jcadapter = std::make_shared<JCAdapter::Joycons>();
jcbuttons = std::make_shared<JCButtonFactory>(jcadapter);
Input::RegisterFactory<Input::ButtonDevice>("jcpad", jcbuttons);
jcanalog = std::make_shared<JCAnalogFactory>(jcadapter);
Input::RegisterFactory<Input::AnalogDevice>("jcpad", jcanalog);
jcmotion = std::make_shared<JCMotionFactory>(jcadapter);
Input::RegisterFactory<Input::MotionDevice>("jcpad", jcmotion);
keyboard = std::make_shared<Keyboard>();
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
@@ -79,6 +89,14 @@ struct InputSubsystem::Impl {
gcanalog.reset();
gcvibration.reset();
Input::UnregisterFactory<Input::ButtonDevice>("jcpad");
Input::UnregisterFactory<Input::AnalogDevice>("jcpad");
Input::UnregisterFactory<Input::MotionDevice>("jcpad");
jcbuttons.reset();
jcanalog.reset();
jcmotion.reset();
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
@@ -105,6 +123,8 @@ struct InputSubsystem::Impl {
auto sdl_devices = sdl->GetInputDevices();
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
#endif
auto jcpad_devices = jcadapter->GetInputDevices();
devices.insert(devices.end(), jcpad_devices.begin(), jcpad_devices.end());
auto udp_devices = udp->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
auto gcpad_devices = gcadapter->GetInputDevices();
@@ -120,6 +140,9 @@ struct InputSubsystem::Impl {
if (params.Get("class", "") == "gcpad") {
return gcadapter->GetAnalogMappingForDevice(params);
}
if (params.Get("class", "") == "jcpad") {
return jcadapter->GetAnalogMappingForDevice(params);
}
#ifdef HAVE_SDL2
if (params.Get("class", "") == "sdl") {
return sdl->GetAnalogMappingForDevice(params);
@@ -136,6 +159,9 @@ struct InputSubsystem::Impl {
if (params.Get("class", "") == "gcpad") {
return gcadapter->GetButtonMappingForDevice(params);
}
if (params.Get("class", "") == "jcpad") {
return jcadapter->GetButtonMappingForDevice(params);
}
#ifdef HAVE_SDL2
if (params.Get("class", "") == "sdl") {
return sdl->GetButtonMappingForDevice(params);
@@ -169,6 +195,10 @@ struct InputSubsystem::Impl {
std::shared_ptr<MouseAnalogFactory> mouseanalog;
std::shared_ptr<MouseMotionFactory> mousemotion;
std::shared_ptr<MouseTouchFactory> mousetouch;
std::shared_ptr<JCButtonFactory> jcbuttons;
std::shared_ptr<JCAnalogFactory> jcanalog;
std::shared_ptr<JCMotionFactory> jcmotion;
std::shared_ptr<JCAdapter::Joycons> jcadapter;
std::shared_ptr<CemuhookUDP::Client> udp;
std::shared_ptr<GCAdapter::Adapter> gcadapter;
std::shared_ptr<MouseInput::Mouse> mouse;
@@ -234,6 +264,30 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
return impl->gcbuttons.get();
}
JCAnalogFactory* InputSubsystem::GetJCAnalogs() {
return impl->jcanalog.get();
}
const JCAnalogFactory* InputSubsystem::GetJCAnalogs() const {
return impl->jcanalog.get();
}
JCButtonFactory* InputSubsystem::GetJCButtons() {
return impl->jcbuttons.get();
}
const JCButtonFactory* InputSubsystem::GetJCButtons() const {
return impl->jcbuttons.get();
}
JCMotionFactory* InputSubsystem::GetJCMotions() {
return impl->jcmotion.get();
}
const JCMotionFactory* InputSubsystem::GetJCMotions() const {
return impl->jcmotion.get();
}
UDPMotionFactory* InputSubsystem::GetUDPMotions() {
return impl->udpmotion.get();
}
@@ -319,4 +373,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
};
return circle_pad_param.Serialize();
}
} // namespace InputCommon
} // namespace InputCommon

View File

@@ -58,6 +58,9 @@ public:
class GCAnalogFactory;
class GCButtonFactory;
class JCAnalogFactory;
class JCButtonFactory;
class JCMotionFactory;
class UDPMotionFactory;
class UDPTouchFactory;
class MouseButtonFactory;
@@ -132,6 +135,24 @@ public:
/// Retrieves the underlying GameCube button handler.
[[nodiscard]] const GCButtonFactory* GetGCButtons() const;
/// Retrieves the underlying joycon analog handler.
[[nodiscard]] JCAnalogFactory* GetJCAnalogs();
/// Retrieves the underlying joycon analog handler.
[[nodiscard]] const JCAnalogFactory* GetJCAnalogs() const;
/// Retrieves the underlying joycon button handler.
[[nodiscard]] JCButtonFactory* GetJCButtons();
/// Retrieves the underlying joycon button handler.
[[nodiscard]] const JCButtonFactory* GetJCButtons() const;
/// Retrieves the underlying joycon motion handler.
[[nodiscard]] JCMotionFactory* GetJCMotions();
/// Retrieves the underlying joycon motion handler.
[[nodiscard]] const JCMotionFactory* GetJCMotions() const;
/// Retrieves the underlying udp motion handler.
[[nodiscard]] UDPMotionFactory* GetUDPMotions();

View File

@@ -17,6 +17,7 @@
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/sm/sm.h"
#include "input_common/gcadapter/gc_poller.h"
#include "input_common/joycon/jc_poller.h"
#include "input_common/main.h"
#include "input_common/mouse/mouse_poller.h"
#include "input_common/udp/udp.h"
@@ -128,6 +129,25 @@ QString ButtonToText(const Common::ParamPackage& param) {
return GetKeyName(param.Get("code", 0));
}
if (param.Get("engine", "") == "jcpad") {
if (param.Has("motion")) {
const QString motion_str = QString::fromStdString(param.Get("motion", ""));
const QString direction_str = QString::fromStdString(param.Get("direction", ""));
return QObject::tr("JC Motion %1%2").arg(motion_str, direction_str);
}
if (param.Has("axis")) {
const QString axis_str = QString::fromStdString(param.Get("axis", ""));
const QString direction_str = QString::fromStdString(param.Get("direction", ""));
return QObject::tr("JC Axis %1%2").arg(axis_str, direction_str);
}
if (param.Has("button")) {
const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
return QObject::tr("JC Button %1").arg(button_str);
}
return GetKeyName(param.Get("code", 0));
}
if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
const QString hat_str = QString::fromStdString(param.Get("hat", ""));
@@ -201,6 +221,26 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
return {};
}
if (param.Get("engine", "") == "jcpad") {
if (dir == "modifier") {
return QObject::tr("[unused]");
}
if (dir == "left" || dir == "right") {
const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
return QObject::tr("JC Axis %1").arg(axis_x_str);
}
if (dir == "up" || dir == "down") {
const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
return QObject::tr("JC Axis %1").arg(axis_y_str);
}
return {};
}
return QObject::tr("[unknown]");
}
} // namespace
@@ -529,6 +569,27 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
return;
}
}
if (input_subsystem->GetJCAnalogs()->IsPolling()) {
params = input_subsystem->GetJCAnalogs()->GetNextInput();
if (params.Has("engine")) {
SetPollingResult(params, false);
return;
}
}
if (input_subsystem->GetJCButtons()->IsPolling()) {
params = input_subsystem->GetJCButtons()->GetNextInput();
if (params.Has("engine")) {
SetPollingResult(params, false);
return;
}
}
if (input_subsystem->GetJCMotions()->IsPolling()) {
params = input_subsystem->GetJCMotions()->GetNextInput();
if (params.Has("engine")) {
SetPollingResult(params, false);
return;
}
}
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
if (params.Has("engine") && IsInputAcceptable(params)) {
@@ -845,9 +906,9 @@ void ConfigureInputPlayer::UpdateUI() {
int slider_value;
auto& param = analogs_param[analog_id];
const bool is_controller = param.Get("engine", "") == "sdl" ||
param.Get("engine", "") == "gcpad" ||
param.Get("engine", "") == "mouse";
const bool is_controller =
param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad" ||
param.Get("engine", "") == "mouse" || param.Get("engine", "") == "jcpad";
if (is_controller) {
if (!param.Has("deadzone")) {
@@ -1173,6 +1234,14 @@ void ConfigureInputPlayer::HandleClick(
input_subsystem->GetMouseTouch()->BeginConfiguration();
}
if (type == InputCommon::Polling::DeviceType::Button) {
input_subsystem->GetJCButtons()->BeginConfiguration();
} else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) {
input_subsystem->GetJCAnalogs()->BeginConfiguration();
} else {
input_subsystem->GetJCMotions()->BeginConfiguration();
}
timeout_timer->start(2500); // Cancel after 2.5 seconds
poll_timer->start(50); // Check for new inputs every 50ms
}
@@ -1197,6 +1266,10 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
input_subsystem->GetMouseMotions()->EndConfiguration();
input_subsystem->GetMouseTouch()->EndConfiguration();
input_subsystem->GetJCButtons()->EndConfiguration();
input_subsystem->GetJCAnalogs()->EndConfiguration();
input_subsystem->GetJCMotions()->EndConfiguration();
if (!abort) {
(*input_setter)(params);
}