Merge pull request #6558 from german77/ringcon2
hidbus: Implement hidbus and ringcon
This commit is contained in:
commit
fd49b186fa
|
@ -590,6 +590,9 @@ struct Values {
|
||||||
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
|
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
|
||||||
std::vector<TouchFromButtonMap> touch_from_button_maps;
|
std::vector<TouchFromButtonMap> touch_from_button_maps;
|
||||||
|
|
||||||
|
BasicSetting<bool> enable_ring_controller{true, "enable_ring_controller"};
|
||||||
|
RingconRaw ringcon_analogs;
|
||||||
|
|
||||||
// Data Storage
|
// Data Storage
|
||||||
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
|
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
|
||||||
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
|
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
|
||||||
|
|
|
@ -357,6 +357,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
|
||||||
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
|
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
|
||||||
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
|
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
|
||||||
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
|
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
|
||||||
|
using RingconRaw = std::string;
|
||||||
|
|
||||||
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
|
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
|
||||||
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
|
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
|
||||||
|
|
|
@ -434,6 +434,8 @@ add_library(core STATIC
|
||||||
hle/service/grc/grc.h
|
hle/service/grc/grc.h
|
||||||
hle/service/hid/hid.cpp
|
hle/service/hid/hid.cpp
|
||||||
hle/service/hid/hid.h
|
hle/service/hid/hid.h
|
||||||
|
hle/service/hid/hidbus.cpp
|
||||||
|
hle/service/hid/hidbus.h
|
||||||
hle/service/hid/irs.cpp
|
hle/service/hid/irs.cpp
|
||||||
hle/service/hid/irs.h
|
hle/service/hid/irs.h
|
||||||
hle/service/hid/ring_lifo.h
|
hle/service/hid/ring_lifo.h
|
||||||
|
@ -460,6 +462,14 @@ add_library(core STATIC
|
||||||
hle/service/hid/controllers/touchscreen.h
|
hle/service/hid/controllers/touchscreen.h
|
||||||
hle/service/hid/controllers/xpad.cpp
|
hle/service/hid/controllers/xpad.cpp
|
||||||
hle/service/hid/controllers/xpad.h
|
hle/service/hid/controllers/xpad.h
|
||||||
|
hle/service/hid/hidbus/hidbus_base.cpp
|
||||||
|
hle/service/hid/hidbus/hidbus_base.h
|
||||||
|
hle/service/hid/hidbus/ringcon.cpp
|
||||||
|
hle/service/hid/hidbus/ringcon.h
|
||||||
|
hle/service/hid/hidbus/starlink.cpp
|
||||||
|
hle/service/hid/hidbus/starlink.h
|
||||||
|
hle/service/hid/hidbus/stubbed.cpp
|
||||||
|
hle/service/hid/hidbus/stubbed.h
|
||||||
hle/service/jit/jit_context.cpp
|
hle/service/jit/jit_context.cpp
|
||||||
hle/service/jit/jit_context.h
|
hle/service/jit/jit_context.h
|
||||||
hle/service/jit/jit.cpp
|
hle/service/jit/jit.cpp
|
||||||
|
|
|
@ -15,6 +15,7 @@ EmulatedDevices::EmulatedDevices() = default;
|
||||||
EmulatedDevices::~EmulatedDevices() = default;
|
EmulatedDevices::~EmulatedDevices() = default;
|
||||||
|
|
||||||
void EmulatedDevices::ReloadFromSettings() {
|
void EmulatedDevices::ReloadFromSettings() {
|
||||||
|
ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
|
||||||
ReloadInput();
|
ReloadInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +67,8 @@ void EmulatedDevices::ReloadInput() {
|
||||||
key_index++;
|
key_index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ring_analog_device = Common::Input::CreateDevice<Common::Input::InputDevice>(ring_params);
|
||||||
|
|
||||||
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
|
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
|
||||||
if (!mouse_button_devices[index]) {
|
if (!mouse_button_devices[index]) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -120,6 +123,13 @@ void EmulatedDevices::ReloadInput() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ring_analog_device) {
|
||||||
|
ring_analog_device->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatedDevices::UnloadInput() {
|
void EmulatedDevices::UnloadInput() {
|
||||||
|
@ -155,6 +165,7 @@ void EmulatedDevices::SaveCurrentConfig() {
|
||||||
if (!is_configuring) {
|
if (!is_configuring) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Settings::values.ringcon_analogs = ring_params.Serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatedDevices::RestoreConfig() {
|
void EmulatedDevices::RestoreConfig() {
|
||||||
|
@ -164,6 +175,15 @@ void EmulatedDevices::RestoreConfig() {
|
||||||
ReloadFromSettings();
|
ReloadFromSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage EmulatedDevices::GetRingParam() const {
|
||||||
|
return ring_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::SetRingParam(Common::ParamPackage param) {
|
||||||
|
ring_params = std::move(param);
|
||||||
|
ReloadInput();
|
||||||
|
}
|
||||||
|
|
||||||
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
|
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
|
||||||
std::size_t index) {
|
std::size_t index) {
|
||||||
if (index >= device_status.keyboard_values.size()) {
|
if (index >= device_status.keyboard_values.size()) {
|
||||||
|
@ -410,6 +430,23 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
|
||||||
TriggerOnChange(DeviceTriggerType::Mouse);
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
const auto force_value = TransformToStick(callback);
|
||||||
|
|
||||||
|
device_status.ring_analog_value = force_value.x;
|
||||||
|
|
||||||
|
if (is_configuring) {
|
||||||
|
device_status.ring_analog_value = {};
|
||||||
|
TriggerOnChange(DeviceTriggerType::RingController);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_status.ring_analog_state.force = force_value.x.value;
|
||||||
|
|
||||||
|
TriggerOnChange(DeviceTriggerType::RingController);
|
||||||
|
}
|
||||||
|
|
||||||
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
|
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
|
||||||
std::scoped_lock lock{mutex};
|
std::scoped_lock lock{mutex};
|
||||||
return device_status.keyboard_values;
|
return device_status.keyboard_values;
|
||||||
|
@ -425,6 +462,10 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
|
||||||
return device_status.mouse_button_values;
|
return device_status.mouse_button_values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
|
||||||
|
return device_status.ring_analog_value;
|
||||||
|
}
|
||||||
|
|
||||||
KeyboardKey EmulatedDevices::GetKeyboard() const {
|
KeyboardKey EmulatedDevices::GetKeyboard() const {
|
||||||
std::scoped_lock lock{mutex};
|
std::scoped_lock lock{mutex};
|
||||||
return device_status.keyboard_state;
|
return device_status.keyboard_state;
|
||||||
|
@ -450,6 +491,10 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
|
||||||
return device_status.mouse_wheel_state;
|
return device_status.mouse_wheel_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RingSensorForce EmulatedDevices::GetRingSensorForce() const {
|
||||||
|
return device_status.ring_analog_state;
|
||||||
|
}
|
||||||
|
|
||||||
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
|
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
|
||||||
std::scoped_lock lock{callback_mutex};
|
std::scoped_lock lock{callback_mutex};
|
||||||
for (const auto& poller_pair : callback_list) {
|
for (const auto& poller_pair : callback_list) {
|
||||||
|
|
|
@ -26,9 +26,11 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice
|
||||||
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
|
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
|
||||||
Settings::NativeMouseWheel::NumMouseWheels>;
|
Settings::NativeMouseWheel::NumMouseWheels>;
|
||||||
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
|
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
|
||||||
|
using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
|
||||||
|
|
||||||
using MouseButtonParams =
|
using MouseButtonParams =
|
||||||
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
|
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
|
||||||
|
using RingAnalogParams = Common::ParamPackage;
|
||||||
|
|
||||||
using KeyboardValues =
|
using KeyboardValues =
|
||||||
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
|
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
|
||||||
|
@ -39,12 +41,17 @@ using MouseButtonValues =
|
||||||
using MouseAnalogValues =
|
using MouseAnalogValues =
|
||||||
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
|
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
|
||||||
using MouseStickValue = Common::Input::TouchStatus;
|
using MouseStickValue = Common::Input::TouchStatus;
|
||||||
|
using RingAnalogValue = Common::Input::AnalogStatus;
|
||||||
|
|
||||||
struct MousePosition {
|
struct MousePosition {
|
||||||
f32 x;
|
f32 x;
|
||||||
f32 y;
|
f32 y;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RingSensorForce {
|
||||||
|
f32 force;
|
||||||
|
};
|
||||||
|
|
||||||
struct DeviceStatus {
|
struct DeviceStatus {
|
||||||
// Data from input_common
|
// Data from input_common
|
||||||
KeyboardValues keyboard_values{};
|
KeyboardValues keyboard_values{};
|
||||||
|
@ -52,6 +59,7 @@ struct DeviceStatus {
|
||||||
MouseButtonValues mouse_button_values{};
|
MouseButtonValues mouse_button_values{};
|
||||||
MouseAnalogValues mouse_analog_values{};
|
MouseAnalogValues mouse_analog_values{};
|
||||||
MouseStickValue mouse_stick_value{};
|
MouseStickValue mouse_stick_value{};
|
||||||
|
RingAnalogValue ring_analog_value{};
|
||||||
|
|
||||||
// Data for HID serices
|
// Data for HID serices
|
||||||
KeyboardKey keyboard_state{};
|
KeyboardKey keyboard_state{};
|
||||||
|
@ -59,12 +67,14 @@ struct DeviceStatus {
|
||||||
MouseButton mouse_button_state{};
|
MouseButton mouse_button_state{};
|
||||||
MousePosition mouse_position_state{};
|
MousePosition mouse_position_state{};
|
||||||
AnalogStickState mouse_wheel_state{};
|
AnalogStickState mouse_wheel_state{};
|
||||||
|
RingSensorForce ring_analog_state{};
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DeviceTriggerType {
|
enum class DeviceTriggerType {
|
||||||
Keyboard,
|
Keyboard,
|
||||||
KeyboardModdifier,
|
KeyboardModdifier,
|
||||||
Mouse,
|
Mouse,
|
||||||
|
RingController,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InterfaceUpdateCallback {
|
struct InterfaceUpdateCallback {
|
||||||
|
@ -110,6 +120,15 @@ public:
|
||||||
/// Reverts any mapped changes made that weren't saved
|
/// Reverts any mapped changes made that weren't saved
|
||||||
void RestoreConfig();
|
void RestoreConfig();
|
||||||
|
|
||||||
|
// Returns the current mapped ring device
|
||||||
|
Common::ParamPackage GetRingParam() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current mapped ring device
|
||||||
|
* @param param ParamPackage with ring sensor data to be mapped
|
||||||
|
*/
|
||||||
|
void SetRingParam(Common::ParamPackage param);
|
||||||
|
|
||||||
/// Returns the latest status of button input from the keyboard with parameters
|
/// Returns the latest status of button input from the keyboard with parameters
|
||||||
KeyboardValues GetKeyboardValues() const;
|
KeyboardValues GetKeyboardValues() const;
|
||||||
|
|
||||||
|
@ -119,6 +138,9 @@ public:
|
||||||
/// Returns the latest status of button input from the mouse with parameters
|
/// Returns the latest status of button input from the mouse with parameters
|
||||||
MouseButtonValues GetMouseButtonsValues() const;
|
MouseButtonValues GetMouseButtonsValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of analog input from the ring sensor with parameters
|
||||||
|
RingAnalogValue GetRingSensorValues() const;
|
||||||
|
|
||||||
/// Returns the latest status of button input from the keyboard
|
/// Returns the latest status of button input from the keyboard
|
||||||
KeyboardKey GetKeyboard() const;
|
KeyboardKey GetKeyboard() const;
|
||||||
|
|
||||||
|
@ -134,6 +156,9 @@ public:
|
||||||
/// Returns the latest mouse wheel change
|
/// Returns the latest mouse wheel change
|
||||||
AnalogStickState GetMouseWheel() const;
|
AnalogStickState GetMouseWheel() const;
|
||||||
|
|
||||||
|
/// Returns the latest ringcon force sensor value
|
||||||
|
RingSensorForce GetRingSensorForce() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a callback to the list of events
|
* Adds a callback to the list of events
|
||||||
* @param update_callback InterfaceUpdateCallback that will be triggered
|
* @param update_callback InterfaceUpdateCallback that will be triggered
|
||||||
|
@ -185,6 +210,12 @@ private:
|
||||||
*/
|
*/
|
||||||
void SetMouseStick(const Common::Input::CallbackStatus& callback);
|
void SetMouseStick(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the ring analog sensor status of the ring controller
|
||||||
|
* @param callback A CallbackStatus containing the force status
|
||||||
|
*/
|
||||||
|
void SetRingAnalog(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a callback that something has changed on the device status
|
* Triggers a callback that something has changed on the device status
|
||||||
* @param type Input type of the event to trigger
|
* @param type Input type of the event to trigger
|
||||||
|
@ -193,11 +224,14 @@ private:
|
||||||
|
|
||||||
bool is_configuring{false};
|
bool is_configuring{false};
|
||||||
|
|
||||||
|
RingAnalogParams ring_params;
|
||||||
|
|
||||||
KeyboardDevices keyboard_devices;
|
KeyboardDevices keyboard_devices;
|
||||||
KeyboardModifierDevices keyboard_modifier_devices;
|
KeyboardModifierDevices keyboard_modifier_devices;
|
||||||
MouseButtonDevices mouse_button_devices;
|
MouseButtonDevices mouse_button_devices;
|
||||||
MouseAnalogDevices mouse_analog_devices;
|
MouseAnalogDevices mouse_analog_devices;
|
||||||
MouseStickDevice mouse_stick_device;
|
MouseStickDevice mouse_stick_device;
|
||||||
|
RingAnalogDevice ring_analog_device;
|
||||||
|
|
||||||
mutable std::mutex mutex;
|
mutable std::mutex mutex;
|
||||||
mutable std::mutex callback_mutex;
|
mutable std::mutex callback_mutex;
|
||||||
|
|
|
@ -140,6 +140,7 @@ struct KernelCore::Impl {
|
||||||
CleanupObject(font_shared_mem);
|
CleanupObject(font_shared_mem);
|
||||||
CleanupObject(irs_shared_mem);
|
CleanupObject(irs_shared_mem);
|
||||||
CleanupObject(time_shared_mem);
|
CleanupObject(time_shared_mem);
|
||||||
|
CleanupObject(hidbus_shared_mem);
|
||||||
CleanupObject(system_resource_limit);
|
CleanupObject(system_resource_limit);
|
||||||
|
|
||||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||||
|
@ -622,16 +623,20 @@ struct KernelCore::Impl {
|
||||||
constexpr std::size_t font_size{0x1100000};
|
constexpr std::size_t font_size{0x1100000};
|
||||||
constexpr std::size_t irs_size{0x8000};
|
constexpr std::size_t irs_size{0x8000};
|
||||||
constexpr std::size_t time_size{0x1000};
|
constexpr std::size_t time_size{0x1000};
|
||||||
|
constexpr std::size_t hidbus_size{0x1000};
|
||||||
|
|
||||||
const PAddr hid_phys_addr{system_pool.GetAddress()};
|
const PAddr hid_phys_addr{system_pool.GetAddress()};
|
||||||
const PAddr font_phys_addr{system_pool.GetAddress() + hid_size};
|
const PAddr font_phys_addr{system_pool.GetAddress() + hid_size};
|
||||||
const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size};
|
const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size};
|
||||||
const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size};
|
const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size};
|
||||||
|
const PAddr hidbus_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size +
|
||||||
|
time_size};
|
||||||
|
|
||||||
hid_shared_mem = KSharedMemory::Create(system.Kernel());
|
hid_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
font_shared_mem = KSharedMemory::Create(system.Kernel());
|
font_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
irs_shared_mem = KSharedMemory::Create(system.Kernel());
|
irs_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
time_shared_mem = KSharedMemory::Create(system.Kernel());
|
time_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
|
hidbus_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
|
|
||||||
hid_shared_mem->Initialize(system.DeviceMemory(), nullptr,
|
hid_shared_mem->Initialize(system.DeviceMemory(), nullptr,
|
||||||
{hid_phys_addr, hid_size / PageSize},
|
{hid_phys_addr, hid_size / PageSize},
|
||||||
|
@ -649,6 +654,10 @@ struct KernelCore::Impl {
|
||||||
{time_phys_addr, time_size / PageSize},
|
{time_phys_addr, time_size / PageSize},
|
||||||
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
|
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
|
||||||
time_phys_addr, time_size, "Time:SharedMemory");
|
time_phys_addr, time_size, "Time:SharedMemory");
|
||||||
|
hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr,
|
||||||
|
{hidbus_phys_addr, hidbus_size / PageSize},
|
||||||
|
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
|
||||||
|
hidbus_phys_addr, hidbus_size, "HidBus:SharedMemory");
|
||||||
}
|
}
|
||||||
|
|
||||||
KClientPort* CreateNamedServicePort(std::string name) {
|
KClientPort* CreateNamedServicePort(std::string name) {
|
||||||
|
@ -748,6 +757,7 @@ struct KernelCore::Impl {
|
||||||
Kernel::KSharedMemory* font_shared_mem{};
|
Kernel::KSharedMemory* font_shared_mem{};
|
||||||
Kernel::KSharedMemory* irs_shared_mem{};
|
Kernel::KSharedMemory* irs_shared_mem{};
|
||||||
Kernel::KSharedMemory* time_shared_mem{};
|
Kernel::KSharedMemory* time_shared_mem{};
|
||||||
|
Kernel::KSharedMemory* hidbus_shared_mem{};
|
||||||
|
|
||||||
// Memory layout
|
// Memory layout
|
||||||
std::unique_ptr<KMemoryLayout> memory_layout;
|
std::unique_ptr<KMemoryLayout> memory_layout;
|
||||||
|
@ -1047,6 +1057,14 @@ const Kernel::KSharedMemory& KernelCore::GetTimeSharedMem() const {
|
||||||
return *impl->time_shared_mem;
|
return *impl->time_shared_mem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() {
|
||||||
|
return *impl->hidbus_shared_mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
|
||||||
|
return *impl->hidbus_shared_mem;
|
||||||
|
}
|
||||||
|
|
||||||
void KernelCore::Suspend(bool in_suspention) {
|
void KernelCore::Suspend(bool in_suspention) {
|
||||||
const bool should_suspend = exception_exited || in_suspention;
|
const bool should_suspend = exception_exited || in_suspention;
|
||||||
{
|
{
|
||||||
|
|
|
@ -264,6 +264,12 @@ public:
|
||||||
/// Gets the shared memory object for Time services.
|
/// Gets the shared memory object for Time services.
|
||||||
const Kernel::KSharedMemory& GetTimeSharedMem() const;
|
const Kernel::KSharedMemory& GetTimeSharedMem() const;
|
||||||
|
|
||||||
|
/// Gets the shared memory object for HIDBus services.
|
||||||
|
Kernel::KSharedMemory& GetHidBusSharedMem();
|
||||||
|
|
||||||
|
/// Gets the shared memory object for HIDBus services.
|
||||||
|
const Kernel::KSharedMemory& GetHidBusSharedMem() const;
|
||||||
|
|
||||||
/// Suspend/unsuspend the OS.
|
/// Suspend/unsuspend the OS.
|
||||||
void Suspend(bool in_suspention);
|
void Suspend(bool in_suspention);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/service/hid/errors.h"
|
#include "core/hle/service/hid/errors.h"
|
||||||
#include "core/hle/service/hid/hid.h"
|
#include "core/hle/service/hid/hid.h"
|
||||||
|
#include "core/hle/service/hid/hidbus.h"
|
||||||
#include "core/hle/service/hid/irs.h"
|
#include "core/hle/service/hid/irs.h"
|
||||||
#include "core/hle/service/hid/xcd.h"
|
#include "core/hle/service/hid/xcd.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
@ -2128,32 +2129,6 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class HidBus final : public ServiceFramework<HidBus> {
|
|
||||||
public:
|
|
||||||
explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} {
|
|
||||||
// clang-format off
|
|
||||||
static const FunctionInfo functions[] = {
|
|
||||||
{1, nullptr, "GetBusHandle"},
|
|
||||||
{2, nullptr, "IsExternalDeviceConnected"},
|
|
||||||
{3, nullptr, "Initialize"},
|
|
||||||
{4, nullptr, "Finalize"},
|
|
||||||
{5, nullptr, "EnableExternalDevice"},
|
|
||||||
{6, nullptr, "GetExternalDeviceId"},
|
|
||||||
{7, nullptr, "SendCommandAsync"},
|
|
||||||
{8, nullptr, "GetSendCommandAsynceResult"},
|
|
||||||
{9, nullptr, "SetEventForSendCommandAsycResult"},
|
|
||||||
{10, nullptr, "GetSharedMemoryHandle"},
|
|
||||||
{11, nullptr, "EnableJoyPollingReceiveMode"},
|
|
||||||
{12, nullptr, "DisableJoyPollingReceiveMode"},
|
|
||||||
{13, nullptr, "GetPollingData"},
|
|
||||||
{14, nullptr, "SetStatusManagerType"},
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
RegisterHandlers(functions);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
|
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
|
||||||
std::make_shared<Hid>(system)->InstallAsService(service_manager);
|
std::make_shared<Hid>(system)->InstallAsService(service_manager);
|
||||||
std::make_shared<HidBus>(system)->InstallAsService(service_manager);
|
std::make_shared<HidBus>(system)->InstallAsService(service_manager);
|
||||||
|
|
|
@ -0,0 +1,531 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/core_timing.h"
|
||||||
|
#include "core/core_timing_util.h"
|
||||||
|
#include "core/hid/hid_types.h"
|
||||||
|
#include "core/hle/ipc_helpers.h"
|
||||||
|
#include "core/hle/kernel/k_event.h"
|
||||||
|
#include "core/hle/kernel/k_readable_event.h"
|
||||||
|
#include "core/hle/kernel/k_shared_memory.h"
|
||||||
|
#include "core/hle/kernel/k_transfer_memory.h"
|
||||||
|
#include "core/hle/service/hid/hidbus.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/ringcon.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/starlink.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/stubbed.h"
|
||||||
|
#include "core/hle/service/service.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
// (15ms, 66Hz)
|
||||||
|
constexpr auto hidbus_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000};
|
||||||
|
|
||||||
|
HidBus::HidBus(Core::System& system_)
|
||||||
|
: ServiceFramework{system_, "hidbus"}, service_context{system_, service_name} {
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static const FunctionInfo functions[] = {
|
||||||
|
{1, &HidBus::GetBusHandle, "GetBusHandle"},
|
||||||
|
{2, &HidBus::IsExternalDeviceConnected, "IsExternalDeviceConnected"},
|
||||||
|
{3, &HidBus::Initialize, "Initialize"},
|
||||||
|
{4, &HidBus::Finalize, "Finalize"},
|
||||||
|
{5, &HidBus::EnableExternalDevice, "EnableExternalDevice"},
|
||||||
|
{6, &HidBus::GetExternalDeviceId, "GetExternalDeviceId"},
|
||||||
|
{7, &HidBus::SendCommandAsync, "SendCommandAsync"},
|
||||||
|
{8, &HidBus::GetSendCommandAsynceResult, "GetSendCommandAsynceResult"},
|
||||||
|
{9, &HidBus::SetEventForSendCommandAsycResult, "SetEventForSendCommandAsycResult"},
|
||||||
|
{10, &HidBus::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
|
||||||
|
{11, &HidBus::EnableJoyPollingReceiveMode, "EnableJoyPollingReceiveMode"},
|
||||||
|
{12, &HidBus::DisableJoyPollingReceiveMode, "DisableJoyPollingReceiveMode"},
|
||||||
|
{13, nullptr, "GetPollingData"},
|
||||||
|
{14, &HidBus::SetStatusManagerType, "SetStatusManagerType"},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
RegisterHandlers(functions);
|
||||||
|
|
||||||
|
// Register update callbacks
|
||||||
|
hidbus_update_event = Core::Timing::CreateEvent(
|
||||||
|
"Hidbus::UpdateCallback",
|
||||||
|
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||||
|
const auto guard = LockService();
|
||||||
|
UpdateHidbus(user_data, ns_late);
|
||||||
|
});
|
||||||
|
|
||||||
|
system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
HidBus::~HidBus() {
|
||||||
|
system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||||
|
auto& core_timing = system.CoreTiming();
|
||||||
|
|
||||||
|
if (is_hidbus_enabled) {
|
||||||
|
for (std::size_t i = 0; i < devices.size(); ++i) {
|
||||||
|
if (!devices[i].is_device_initializated) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto& device = devices[i].device;
|
||||||
|
device->OnUpdate();
|
||||||
|
auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index];
|
||||||
|
cur_entry.is_polling_mode = device->IsPollingMode();
|
||||||
|
cur_entry.polling_mode = device->GetPollingMode();
|
||||||
|
cur_entry.is_enabled = device->IsEnabled();
|
||||||
|
|
||||||
|
u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer();
|
||||||
|
std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status,
|
||||||
|
sizeof(HidbusStatusManagerEntry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ns_late is higher than the update rate ignore the delay
|
||||||
|
if (ns_late > hidbus_update_ns) {
|
||||||
|
ns_late = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const {
|
||||||
|
for (std::size_t i = 0; i < devices.size(); ++i) {
|
||||||
|
const auto& device_handle = devices[i].handle;
|
||||||
|
if (handle.abstracted_pad_id == device_handle.abstracted_pad_id &&
|
||||||
|
handle.internal_index == device_handle.internal_index &&
|
||||||
|
handle.player_number == device_handle.player_number &&
|
||||||
|
handle.bus_type == device_handle.bus_type &&
|
||||||
|
handle.is_valid == device_handle.is_valid) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::GetBusHandle(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
struct Parameters {
|
||||||
|
Core::HID::NpadIdType npad_id;
|
||||||
|
INSERT_PADDING_WORDS_NOINIT(1);
|
||||||
|
BusType bus_type;
|
||||||
|
u64 applet_resource_user_id;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
|
||||||
|
|
||||||
|
const auto parameters{rp.PopRaw<Parameters>()};
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}",
|
||||||
|
parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id);
|
||||||
|
|
||||||
|
bool is_handle_found = 0;
|
||||||
|
std::size_t handle_index = 0;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < devices.size(); i++) {
|
||||||
|
const auto& handle = devices[i].handle;
|
||||||
|
if (!handle.is_valid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (static_cast<Core::HID::NpadIdType>(handle.player_number) == parameters.npad_id &&
|
||||||
|
handle.bus_type == parameters.bus_type) {
|
||||||
|
is_handle_found = true;
|
||||||
|
handle_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle not found. Create a new one
|
||||||
|
if (!is_handle_found) {
|
||||||
|
for (std::size_t i = 0; i < devices.size(); i++) {
|
||||||
|
if (devices[i].handle.is_valid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
devices[i].handle = {
|
||||||
|
.abstracted_pad_id = static_cast<u8>(i),
|
||||||
|
.internal_index = static_cast<u8>(i),
|
||||||
|
.player_number = static_cast<u8>(parameters.npad_id),
|
||||||
|
.bus_type = parameters.bus_type,
|
||||||
|
.is_valid = true,
|
||||||
|
};
|
||||||
|
handle_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OutData {
|
||||||
|
bool is_valid;
|
||||||
|
INSERT_PADDING_BYTES(7);
|
||||||
|
BusHandle handle;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(OutData) == 0x10, "OutData has incorrect size.");
|
||||||
|
|
||||||
|
const OutData out_data{
|
||||||
|
.is_valid = true,
|
||||||
|
.handle = devices[handle_index].handle,
|
||||||
|
};
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 6};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.PushRaw(out_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID,
|
||||||
|
"Called, abstracted_pad_id={}, bus_type={}, internal_index={}, "
|
||||||
|
"player_number={}, is_valid={}",
|
||||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
|
||||||
|
bus_handle_.player_number, bus_handle_.is_valid);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
const auto& device = devices[device_index.value()].device;
|
||||||
|
const bool is_attached = device->IsDeviceActivated();
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push(is_attached);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::Initialize(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID,
|
||||||
|
"called, abstracted_pad_id={} bus_type={} internal_index={} "
|
||||||
|
"player_number={} is_valid={}, applet_resource_user_id={}",
|
||||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
|
||||||
|
bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id);
|
||||||
|
|
||||||
|
is_hidbus_enabled = true;
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
const auto entry_index = devices[device_index.value()].handle.internal_index;
|
||||||
|
auto& cur_entry = hidbus_status.entries[entry_index];
|
||||||
|
|
||||||
|
if (bus_handle_.internal_index == 0 && Settings::values.enable_ring_controller) {
|
||||||
|
MakeDevice<RingController>(bus_handle_);
|
||||||
|
devices[device_index.value()].is_device_initializated = true;
|
||||||
|
devices[device_index.value()].device->ActivateDevice();
|
||||||
|
cur_entry.is_in_focus = true;
|
||||||
|
cur_entry.is_connected = true;
|
||||||
|
cur_entry.is_connected_result = ResultSuccess;
|
||||||
|
cur_entry.is_enabled = false;
|
||||||
|
cur_entry.is_polling_mode = false;
|
||||||
|
} else {
|
||||||
|
MakeDevice<HidbusStubbed>(bus_handle_);
|
||||||
|
devices[device_index.value()].is_device_initializated = true;
|
||||||
|
cur_entry.is_in_focus = true;
|
||||||
|
cur_entry.is_connected = false;
|
||||||
|
cur_entry.is_connected_result = ResultSuccess;
|
||||||
|
cur_entry.is_enabled = false;
|
||||||
|
cur_entry.is_polling_mode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status,
|
||||||
|
sizeof(hidbus_status));
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::Finalize(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID,
|
||||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, "
|
||||||
|
"player_number={}, is_valid={}, applet_resource_user_id={}",
|
||||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
|
||||||
|
bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
const auto entry_index = devices[device_index.value()].handle.internal_index;
|
||||||
|
auto& cur_entry = hidbus_status.entries[entry_index];
|
||||||
|
auto& device = devices[device_index.value()].device;
|
||||||
|
devices[device_index.value()].is_device_initializated = false;
|
||||||
|
device->DeactivateDevice();
|
||||||
|
|
||||||
|
cur_entry.is_in_focus = true;
|
||||||
|
cur_entry.is_connected = false;
|
||||||
|
cur_entry.is_connected_result = ResultSuccess;
|
||||||
|
cur_entry.is_enabled = false;
|
||||||
|
cur_entry.is_polling_mode = false;
|
||||||
|
std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status,
|
||||||
|
sizeof(hidbus_status));
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
struct Parameters {
|
||||||
|
bool enable;
|
||||||
|
INSERT_PADDING_BYTES_NOINIT(7);
|
||||||
|
BusHandle bus_handle;
|
||||||
|
u64 inval;
|
||||||
|
u64 applet_resource_user_id;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
|
||||||
|
|
||||||
|
const auto parameters{rp.PopRaw<Parameters>()};
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID,
|
||||||
|
"called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
|
||||||
|
"player_number={}, is_valid={}, inval={}, applet_resource_user_id{}",
|
||||||
|
parameters.enable, parameters.bus_handle.abstracted_pad_id,
|
||||||
|
parameters.bus_handle.bus_type, parameters.bus_handle.internal_index,
|
||||||
|
parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval,
|
||||||
|
parameters.applet_resource_user_id);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
auto& device = devices[device_index.value()].device;
|
||||||
|
device->Enable(parameters.enable);
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID,
|
||||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
|
||||||
|
"is_valid={}",
|
||||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
|
||||||
|
bus_handle_.player_number, bus_handle_.is_valid);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
const auto& device = devices[device_index.value()].device;
|
||||||
|
u32 device_id = device->GetDeviceId();
|
||||||
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(device_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::SendCommandAsync(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto data = ctx.ReadBuffer();
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_HID,
|
||||||
|
"called, data_size={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
|
||||||
|
"player_number={}, is_valid={}",
|
||||||
|
data.size(), bus_handle_.abstracted_pad_id, bus_handle_.bus_type,
|
||||||
|
bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
auto& device = devices[device_index.value()].device;
|
||||||
|
device->SetCommand(data);
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
void HidBus::GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_HID,
|
||||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
|
||||||
|
"is_valid={}",
|
||||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
|
||||||
|
bus_handle_.player_number, bus_handle_.is_valid);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
const auto& device = devices[device_index.value()].device;
|
||||||
|
const std::vector<u8> data = device->GetReply();
|
||||||
|
const u64 data_size = ctx.WriteBuffer(data);
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 4};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u64>(data_size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
void HidBus::SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID,
|
||||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
|
||||||
|
"is_valid={}",
|
||||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
|
||||||
|
bus_handle_.player_number, bus_handle_.is_valid);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
const auto& device = devices[device_index.value()].device;
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.PushCopyObjects(device->GetSendCommandAsycEvent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
void HidBus::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
|
||||||
|
LOG_DEBUG(Service_HID, "called");
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.PushCopyObjects(&system.Kernel().GetHidBusSharedMem());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto t_mem_size{rp.Pop<u32>()};
|
||||||
|
const auto t_mem_handle{ctx.GetCopyHandle(0)};
|
||||||
|
const auto polling_mode_{rp.PopEnum<JoyPollingMode>()};
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
|
||||||
|
ASSERT_MSG(t_mem_size == 0x1000, "t_mem_size is not 0x1000 bytes");
|
||||||
|
|
||||||
|
auto t_mem =
|
||||||
|
system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
|
||||||
|
|
||||||
|
if (t_mem.IsNull()) {
|
||||||
|
LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_MSG(t_mem->GetSize() == 0x1000, "t_mem has incorrect size");
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID,
|
||||||
|
"called, t_mem_handle=0x{:08X}, polling_mode={}, abstracted_pad_id={}, bus_type={}, "
|
||||||
|
"internal_index={}, player_number={}, is_valid={}",
|
||||||
|
t_mem_handle, polling_mode_, bus_handle_.abstracted_pad_id, bus_handle_.bus_type,
|
||||||
|
bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
auto& device = devices[device_index.value()].device;
|
||||||
|
device->SetPollingMode(polling_mode_);
|
||||||
|
device->SetTransferMemoryPointer(system.Memory().GetPointer(t_mem->GetSourceAddress()));
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID,
|
||||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
|
||||||
|
"is_valid={}",
|
||||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
|
||||||
|
bus_handle_.player_number, bus_handle_.is_valid);
|
||||||
|
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
|
||||||
|
|
||||||
|
if (device_index) {
|
||||||
|
auto& device = devices[device_index.value()].device;
|
||||||
|
device->DisablePollingMode();
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Invalid handle");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultUnknown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidBus::SetStatusManagerType(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto manager_type{rp.PopEnum<StatusManagerType>()};
|
||||||
|
|
||||||
|
LOG_WARNING(Service_HID, "(STUBBED) called, manager_type={}", manager_type);
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
};
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||||
|
#include "core/hle/service/kernel_helpers.h"
|
||||||
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
|
namespace Core::Timing {
|
||||||
|
struct EventType;
|
||||||
|
} // namespace Core::Timing
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
} // namespace Core
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
|
||||||
|
class HidBus final : public ServiceFramework<HidBus> {
|
||||||
|
public:
|
||||||
|
explicit HidBus(Core::System& system_);
|
||||||
|
~HidBus() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const std::size_t max_number_of_handles = 0x13;
|
||||||
|
|
||||||
|
enum class HidBusDeviceId : std::size_t {
|
||||||
|
RingController = 0x20,
|
||||||
|
FamicomRight = 0x21,
|
||||||
|
Starlink = 0x28,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hidbus::detail::StatusManagerType
|
||||||
|
enum class StatusManagerType : u32 {
|
||||||
|
None,
|
||||||
|
Type16,
|
||||||
|
Type32,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hidbus::BusType
|
||||||
|
enum class BusType : u8 {
|
||||||
|
LeftJoyRail,
|
||||||
|
RightJoyRail,
|
||||||
|
InternalBus, // Lark microphone
|
||||||
|
|
||||||
|
MaxBusType,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hidbus::BusHandle
|
||||||
|
struct BusHandle {
|
||||||
|
u32 abstracted_pad_id;
|
||||||
|
u8 internal_index;
|
||||||
|
u8 player_number;
|
||||||
|
BusType bus_type;
|
||||||
|
bool is_valid;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(BusHandle) == 0x8, "BusHandle is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hidbus::JoyPollingReceivedData
|
||||||
|
struct JoyPollingReceivedData {
|
||||||
|
std::array<u8, 0x30> data;
|
||||||
|
u64 out_size;
|
||||||
|
u64 sampling_number;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyPollingReceivedData) == 0x40,
|
||||||
|
"JoyPollingReceivedData is an invalid size");
|
||||||
|
|
||||||
|
struct HidbusStatusManagerEntry {
|
||||||
|
u8 is_connected{};
|
||||||
|
INSERT_PADDING_BYTES(0x3);
|
||||||
|
ResultCode is_connected_result{0};
|
||||||
|
u8 is_enabled{};
|
||||||
|
u8 is_in_focus{};
|
||||||
|
u8 is_polling_mode{};
|
||||||
|
u8 reserved{};
|
||||||
|
JoyPollingMode polling_mode{};
|
||||||
|
INSERT_PADDING_BYTES(0x70); // Unknown
|
||||||
|
};
|
||||||
|
static_assert(sizeof(HidbusStatusManagerEntry) == 0x80,
|
||||||
|
"HidbusStatusManagerEntry is an invalid size");
|
||||||
|
|
||||||
|
struct HidbusStatusManager {
|
||||||
|
std::array<HidbusStatusManagerEntry, max_number_of_handles> entries{};
|
||||||
|
INSERT_PADDING_BYTES(0x680); // Unused
|
||||||
|
};
|
||||||
|
static_assert(sizeof(HidbusStatusManager) <= 0x1000, "HidbusStatusManager is an invalid size");
|
||||||
|
|
||||||
|
struct HidbusDevice {
|
||||||
|
bool is_device_initializated{};
|
||||||
|
BusHandle handle{};
|
||||||
|
std::unique_ptr<HidbusBase> device{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
void GetBusHandle(Kernel::HLERequestContext& ctx);
|
||||||
|
void IsExternalDeviceConnected(Kernel::HLERequestContext& ctx);
|
||||||
|
void Initialize(Kernel::HLERequestContext& ctx);
|
||||||
|
void Finalize(Kernel::HLERequestContext& ctx);
|
||||||
|
void EnableExternalDevice(Kernel::HLERequestContext& ctx);
|
||||||
|
void GetExternalDeviceId(Kernel::HLERequestContext& ctx);
|
||||||
|
void SendCommandAsync(Kernel::HLERequestContext& ctx);
|
||||||
|
void GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx);
|
||||||
|
void SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx);
|
||||||
|
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
|
||||||
|
void EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx);
|
||||||
|
void DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx);
|
||||||
|
void SetStatusManagerType(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
|
||||||
|
std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void MakeDevice(BusHandle handle) {
|
||||||
|
const auto device_index = GetDeviceIndexFromHandle(handle);
|
||||||
|
if (device_index) {
|
||||||
|
devices[device_index.value()].device =
|
||||||
|
std::make_unique<T>(system.HIDCore(), service_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_hidbus_enabled{false};
|
||||||
|
HidbusStatusManager hidbus_status{};
|
||||||
|
std::array<HidbusDevice, max_number_of_handles> devices{};
|
||||||
|
std::shared_ptr<Core::Timing::EventType> hidbus_update_event;
|
||||||
|
KernelHelpers::ServiceContext service_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hid/hid_core.h"
|
||||||
|
#include "core/hle/kernel/k_event.h"
|
||||||
|
#include "core/hle/kernel/k_readable_event.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||||
|
#include "core/hle/service/kernel_helpers.h"
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
|
||||||
|
HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_)
|
||||||
|
: service_context(service_context_) {
|
||||||
|
send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
|
||||||
|
}
|
||||||
|
HidbusBase::~HidbusBase() = default;
|
||||||
|
|
||||||
|
void HidbusBase::ActivateDevice() {
|
||||||
|
if (is_activated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
is_activated = true;
|
||||||
|
OnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidbusBase::DeactivateDevice() {
|
||||||
|
if (is_activated) {
|
||||||
|
OnRelease();
|
||||||
|
}
|
||||||
|
is_activated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HidbusBase::IsDeviceActivated() const {
|
||||||
|
return is_activated;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidbusBase::Enable(bool enable) {
|
||||||
|
device_enabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HidbusBase::IsEnabled() const {
|
||||||
|
return device_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HidbusBase::IsPollingMode() const {
|
||||||
|
return polling_mode_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
JoyPollingMode HidbusBase::GetPollingMode() const {
|
||||||
|
return polling_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidbusBase::SetPollingMode(JoyPollingMode mode) {
|
||||||
|
polling_mode = mode;
|
||||||
|
polling_mode_enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidbusBase::DisablePollingMode() {
|
||||||
|
polling_mode_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidbusBase::SetTransferMemoryPointer(u8* t_mem) {
|
||||||
|
is_transfer_memory_set = true;
|
||||||
|
transfer_memory = t_mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
|
||||||
|
return send_command_async_event->GetReadableEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,179 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
class KEvent;
|
||||||
|
class KReadableEvent;
|
||||||
|
} // namespace Kernel
|
||||||
|
|
||||||
|
namespace Service::KernelHelpers {
|
||||||
|
class ServiceContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
|
||||||
|
// This is nn::hidbus::JoyPollingMode
|
||||||
|
enum class JoyPollingMode : u32 {
|
||||||
|
SixAxisSensorDisable,
|
||||||
|
SixAxisSensorEnable,
|
||||||
|
ButtonOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DataAccessorHeader {
|
||||||
|
ResultCode result{ResultUnknown};
|
||||||
|
INSERT_PADDING_WORDS(0x1);
|
||||||
|
std::array<u8, 0x18> unused{};
|
||||||
|
u64 latest_entry{};
|
||||||
|
u64 total_entries{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size");
|
||||||
|
|
||||||
|
struct JoyDisableSixAxisPollingData {
|
||||||
|
std::array<u8, 0x26> data;
|
||||||
|
u8 out_size;
|
||||||
|
INSERT_PADDING_BYTES(0x1);
|
||||||
|
u64 sampling_number;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30,
|
||||||
|
"JoyDisableSixAxisPollingData is an invalid size");
|
||||||
|
|
||||||
|
struct JoyEnableSixAxisPollingData {
|
||||||
|
std::array<u8, 0x8> data;
|
||||||
|
u8 out_size;
|
||||||
|
INSERT_PADDING_BYTES(0x7);
|
||||||
|
u64 sampling_number;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18,
|
||||||
|
"JoyEnableSixAxisPollingData is an invalid size");
|
||||||
|
|
||||||
|
struct JoyButtonOnlyPollingData {
|
||||||
|
std::array<u8, 0x2c> data;
|
||||||
|
u8 out_size;
|
||||||
|
INSERT_PADDING_BYTES(0x3);
|
||||||
|
u64 sampling_number;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38,
|
||||||
|
"JoyButtonOnlyPollingData is an invalid size");
|
||||||
|
|
||||||
|
struct JoyDisableSixAxisPollingEntry {
|
||||||
|
u64 sampling_number;
|
||||||
|
JoyDisableSixAxisPollingData polling_data;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38,
|
||||||
|
"JoyDisableSixAxisPollingEntry is an invalid size");
|
||||||
|
|
||||||
|
struct JoyEnableSixAxisPollingEntry {
|
||||||
|
u64 sampling_number;
|
||||||
|
JoyEnableSixAxisPollingData polling_data;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20,
|
||||||
|
"JoyEnableSixAxisPollingEntry is an invalid size");
|
||||||
|
|
||||||
|
struct JoyButtonOnlyPollingEntry {
|
||||||
|
u64 sampling_number;
|
||||||
|
JoyButtonOnlyPollingData polling_data;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40,
|
||||||
|
"JoyButtonOnlyPollingEntry is an invalid size");
|
||||||
|
|
||||||
|
struct JoyDisableSixAxisDataAccessor {
|
||||||
|
DataAccessorHeader header{};
|
||||||
|
std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298,
|
||||||
|
"JoyDisableSixAxisDataAccessor is an invalid size");
|
||||||
|
|
||||||
|
struct JoyEnableSixAxisDataAccessor {
|
||||||
|
DataAccessorHeader header{};
|
||||||
|
std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190,
|
||||||
|
"JoyEnableSixAxisDataAccessor is an invalid size");
|
||||||
|
|
||||||
|
struct ButtonOnlyPollingDataAccessor {
|
||||||
|
DataAccessorHeader header;
|
||||||
|
std::array<JoyButtonOnlyPollingEntry, 0xb> entries;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0,
|
||||||
|
"ButtonOnlyPollingDataAccessor is an invalid size");
|
||||||
|
|
||||||
|
class HidbusBase {
|
||||||
|
public:
|
||||||
|
explicit HidbusBase(KernelHelpers::ServiceContext& service_context_);
|
||||||
|
virtual ~HidbusBase();
|
||||||
|
|
||||||
|
void ActivateDevice();
|
||||||
|
|
||||||
|
void DeactivateDevice();
|
||||||
|
|
||||||
|
bool IsDeviceActivated() const;
|
||||||
|
|
||||||
|
// Enables/disables the device
|
||||||
|
void Enable(bool enable);
|
||||||
|
|
||||||
|
// returns true if device is enabled
|
||||||
|
bool IsEnabled() const;
|
||||||
|
|
||||||
|
// returns true if polling mode is enabled
|
||||||
|
bool IsPollingMode() const;
|
||||||
|
|
||||||
|
// returns polling mode
|
||||||
|
JoyPollingMode GetPollingMode() const;
|
||||||
|
|
||||||
|
// Sets and enables JoyPollingMode
|
||||||
|
void SetPollingMode(JoyPollingMode mode);
|
||||||
|
|
||||||
|
// Disables JoyPollingMode
|
||||||
|
void DisablePollingMode();
|
||||||
|
|
||||||
|
// Called on EnableJoyPollingReceiveMode
|
||||||
|
void SetTransferMemoryPointer(u8* t_mem);
|
||||||
|
|
||||||
|
Kernel::KReadableEvent& GetSendCommandAsycEvent() const;
|
||||||
|
|
||||||
|
virtual void OnInit() {}
|
||||||
|
|
||||||
|
virtual void OnRelease() {}
|
||||||
|
|
||||||
|
// Updates device transfer memory
|
||||||
|
virtual void OnUpdate() {}
|
||||||
|
|
||||||
|
// Returns the device ID of the joycon
|
||||||
|
virtual u8 GetDeviceId() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assigns a command from data
|
||||||
|
virtual bool SetCommand(const std::vector<u8>& data) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a reply from a command
|
||||||
|
virtual std::vector<u8> GetReply() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool is_activated{};
|
||||||
|
bool device_enabled{};
|
||||||
|
bool polling_mode_enabled{};
|
||||||
|
JoyPollingMode polling_mode = {};
|
||||||
|
// TODO(German77): All data accessors need to be replaced with a ring lifo object
|
||||||
|
JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
|
||||||
|
JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
|
||||||
|
ButtonOnlyPollingDataAccessor button_only_data{};
|
||||||
|
|
||||||
|
u8* transfer_memory{nullptr};
|
||||||
|
bool is_transfer_memory_set{};
|
||||||
|
|
||||||
|
Kernel::KEvent* send_command_async_event;
|
||||||
|
KernelHelpers::ServiceContext& service_context;
|
||||||
|
};
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,286 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hid/emulated_devices.h"
|
||||||
|
#include "core/hid/hid_core.h"
|
||||||
|
#include "core/hle/kernel/k_event.h"
|
||||||
|
#include "core/hle/kernel/k_readable_event.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/ringcon.h"
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
|
||||||
|
RingController::RingController(Core::HID::HIDCore& hid_core_,
|
||||||
|
KernelHelpers::ServiceContext& service_context_)
|
||||||
|
: HidbusBase(service_context_) {
|
||||||
|
input = hid_core_.GetEmulatedDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
RingController::~RingController() = default;
|
||||||
|
|
||||||
|
void RingController::OnInit() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RingController::OnRelease() {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
void RingController::OnUpdate() {
|
||||||
|
if (!is_activated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!polling_mode_enabled || !is_transfer_memory_set) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Increment multitasking counters from motion and sensor data
|
||||||
|
|
||||||
|
switch (polling_mode) {
|
||||||
|
case JoyPollingMode::SixAxisSensorEnable: {
|
||||||
|
enable_sixaxis_data.header.total_entries = 10;
|
||||||
|
enable_sixaxis_data.header.result = ResultSuccess;
|
||||||
|
const auto& last_entry =
|
||||||
|
enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
|
||||||
|
|
||||||
|
enable_sixaxis_data.header.latest_entry =
|
||||||
|
(enable_sixaxis_data.header.latest_entry + 1) % 10;
|
||||||
|
auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
|
||||||
|
|
||||||
|
curr_entry.sampling_number = last_entry.sampling_number + 1;
|
||||||
|
curr_entry.polling_data.sampling_number = curr_entry.sampling_number;
|
||||||
|
|
||||||
|
const RingConData ringcon_value = GetSensorValue();
|
||||||
|
curr_entry.polling_data.out_size = sizeof(ringcon_value);
|
||||||
|
std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value));
|
||||||
|
|
||||||
|
std::memcpy(transfer_memory, &enable_sixaxis_data, sizeof(enable_sixaxis_data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RingController::RingConData RingController::GetSensorValue() const {
|
||||||
|
RingConData ringcon_sensor_value{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.data = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const f32 force_value = input->GetRingSensorForce().force * range;
|
||||||
|
ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value;
|
||||||
|
|
||||||
|
return ringcon_sensor_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 RingController::GetDeviceId() const {
|
||||||
|
return device_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetReply() const {
|
||||||
|
const RingConCommands current_command = command;
|
||||||
|
|
||||||
|
switch (current_command) {
|
||||||
|
case RingConCommands::GetFirmwareVersion:
|
||||||
|
return GetFirmwareVersionReply();
|
||||||
|
case RingConCommands::ReadId:
|
||||||
|
return GetReadIdReply();
|
||||||
|
case RingConCommands::c20105:
|
||||||
|
return GetC020105Reply();
|
||||||
|
case RingConCommands::ReadUnkCal:
|
||||||
|
return GetReadUnkCalReply();
|
||||||
|
case RingConCommands::ReadFactoryCal:
|
||||||
|
return GetReadFactoryCalReply();
|
||||||
|
case RingConCommands::ReadUserCal:
|
||||||
|
return GetReadUserCalReply();
|
||||||
|
case RingConCommands::ReadRepCount:
|
||||||
|
return GetReadRepCountReply();
|
||||||
|
case RingConCommands::ReadTotalPushCount:
|
||||||
|
return GetReadTotalPushCountReply();
|
||||||
|
case RingConCommands::ResetRepCount:
|
||||||
|
return GetResetRepCountReply();
|
||||||
|
case RingConCommands::SaveCalData:
|
||||||
|
return GetSaveDataReply();
|
||||||
|
default:
|
||||||
|
return GetErrorReply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RingController::SetCommand(const std::vector<u8>& data) {
|
||||||
|
if (data.size() < 4) {
|
||||||
|
LOG_ERROR(Service_HID, "Command size not supported {}", data.size());
|
||||||
|
command = RingConCommands::Error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(&command, data.data(), sizeof(RingConCommands));
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case RingConCommands::GetFirmwareVersion:
|
||||||
|
case RingConCommands::ReadId:
|
||||||
|
case RingConCommands::c20105:
|
||||||
|
case RingConCommands::ReadUnkCal:
|
||||||
|
case RingConCommands::ReadFactoryCal:
|
||||||
|
case RingConCommands::ReadUserCal:
|
||||||
|
case RingConCommands::ReadRepCount:
|
||||||
|
case RingConCommands::ReadTotalPushCount:
|
||||||
|
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
|
||||||
|
send_command_async_event->GetWritableEvent().Signal();
|
||||||
|
return true;
|
||||||
|
case RingConCommands::ResetRepCount:
|
||||||
|
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
|
||||||
|
total_rep_count = 0;
|
||||||
|
send_command_async_event->GetWritableEvent().Signal();
|
||||||
|
return true;
|
||||||
|
case RingConCommands::SaveCalData: {
|
||||||
|
ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
|
||||||
|
|
||||||
|
SaveCalData save_info{};
|
||||||
|
std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
|
||||||
|
user_calibration = save_info.calibration;
|
||||||
|
send_command_async_event->GetWritableEvent().Signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Service_HID, "Command not implemented {}", command);
|
||||||
|
command = RingConCommands::Error;
|
||||||
|
// Signal a reply to avoid softlocking the game
|
||||||
|
send_command_async_event->GetWritableEvent().Signal();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetFirmwareVersionReply() const {
|
||||||
|
const FirmwareVersionReply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.firmware = version,
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetReadIdReply() const {
|
||||||
|
// The values are hardcoded from a real joycon
|
||||||
|
const ReadIdReply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.id_l_x0 = 8,
|
||||||
|
.id_l_x0_2 = 41,
|
||||||
|
.id_l_x4 = 22294,
|
||||||
|
.id_h_x0 = 19777,
|
||||||
|
.id_h_x0_2 = 13621,
|
||||||
|
.id_h_x4 = 8245,
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetC020105Reply() const {
|
||||||
|
const Cmd020105Reply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.data = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetReadUnkCalReply() const {
|
||||||
|
const ReadUnkCalReply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.data = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetReadFactoryCalReply() const {
|
||||||
|
const ReadFactoryCalReply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.calibration = factory_calibration,
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetReadUserCalReply() const {
|
||||||
|
const ReadUserCalReply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.calibration = user_calibration,
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetReadRepCountReply() const {
|
||||||
|
const GetThreeByteReply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.data = {total_rep_count, 0, 0},
|
||||||
|
.crc = GetCrcValue({total_rep_count, 0, 0, 0}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetReadTotalPushCountReply() const {
|
||||||
|
const GetThreeByteReply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
.data = {total_push_count, 0, 0},
|
||||||
|
.crc = GetCrcValue({total_push_count, 0, 0, 0}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetResetRepCountReply() const {
|
||||||
|
return GetReadRepCountReply();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetSaveDataReply() const {
|
||||||
|
const StatusReply reply{
|
||||||
|
.status = DataValid::Valid,
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> RingController::GetErrorReply() const {
|
||||||
|
const ErrorReply reply{
|
||||||
|
.status = DataValid::BadCRC,
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetDataVector(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 RingController::GetCrcValue(const std::vector<u8>& data) const {
|
||||||
|
u8 crc = 0;
|
||||||
|
for (std::size_t index = 0; index < data.size(); index++) {
|
||||||
|
for (u8 i = 0x80; i > 0; i >>= 1) {
|
||||||
|
bool bit = (crc & 0x80) != 0;
|
||||||
|
if ((data[index] & i) != 0) {
|
||||||
|
bit = !bit;
|
||||||
|
}
|
||||||
|
crc <<= 1;
|
||||||
|
if (bit) {
|
||||||
|
crc ^= 0x8d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::vector<u8> RingController::GetDataVector(const T& reply) const {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
|
std::vector<u8> data;
|
||||||
|
data.resize(sizeof(reply));
|
||||||
|
std::memcpy(data.data(), &reply, sizeof(reply));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,254 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
class EmulatedDevices;
|
||||||
|
} // namespace Core::HID
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
|
||||||
|
class RingController final : public HidbusBase {
|
||||||
|
public:
|
||||||
|
explicit RingController(Core::HID::HIDCore& hid_core_,
|
||||||
|
KernelHelpers::ServiceContext& service_context_);
|
||||||
|
~RingController() override;
|
||||||
|
|
||||||
|
void OnInit() override;
|
||||||
|
|
||||||
|
void OnRelease() override;
|
||||||
|
|
||||||
|
// Updates ringcon transfer memory
|
||||||
|
void OnUpdate() override;
|
||||||
|
|
||||||
|
// Returns the device ID of the joycon
|
||||||
|
u8 GetDeviceId() const override;
|
||||||
|
|
||||||
|
// Assigns a command from data
|
||||||
|
bool SetCommand(const std::vector<u8>& data) override;
|
||||||
|
|
||||||
|
// Returns a reply from a command
|
||||||
|
std::vector<u8> GetReply() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// These values are obtained from a real ring controller
|
||||||
|
static constexpr s16 idle_value = 2280;
|
||||||
|
static constexpr s16 idle_deadzone = 120;
|
||||||
|
static constexpr s16 range = 2500;
|
||||||
|
|
||||||
|
// Most missing command names are leftovers from other firmware versions
|
||||||
|
enum class RingConCommands : u32 {
|
||||||
|
GetFirmwareVersion = 0x00020000,
|
||||||
|
ReadId = 0x00020100,
|
||||||
|
JoyPolling = 0x00020101,
|
||||||
|
Unknown1 = 0x00020104,
|
||||||
|
c20105 = 0x00020105,
|
||||||
|
Unknown2 = 0x00020204,
|
||||||
|
Unknown3 = 0x00020304,
|
||||||
|
Unknown4 = 0x00020404,
|
||||||
|
ReadUnkCal = 0x00020504,
|
||||||
|
ReadFactoryCal = 0x00020A04,
|
||||||
|
Unknown5 = 0x00021104,
|
||||||
|
Unknown6 = 0x00021204,
|
||||||
|
Unknown7 = 0x00021304,
|
||||||
|
ReadUserCal = 0x00021A04,
|
||||||
|
ReadRepCount = 0x00023104,
|
||||||
|
ReadTotalPushCount = 0x00023204,
|
||||||
|
ResetRepCount = 0x04013104,
|
||||||
|
Unknown8 = 0x04011104,
|
||||||
|
Unknown9 = 0x04011204,
|
||||||
|
Unknown10 = 0x04011304,
|
||||||
|
SaveCalData = 0x10011A04,
|
||||||
|
Error = 0xFFFFFFFF,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DataValid : u32 {
|
||||||
|
Valid,
|
||||||
|
BadCRC,
|
||||||
|
Cal,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FirmwareVersion {
|
||||||
|
u8 sub;
|
||||||
|
u8 main;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
|
||||||
|
|
||||||
|
struct FactoryCalibration {
|
||||||
|
s32_le os_max;
|
||||||
|
s32_le hk_max;
|
||||||
|
s32_le zero_min;
|
||||||
|
s32_le zero_max;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size");
|
||||||
|
|
||||||
|
struct CalibrationValue {
|
||||||
|
s16 value;
|
||||||
|
u16 crc;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size");
|
||||||
|
|
||||||
|
struct UserCalibration {
|
||||||
|
CalibrationValue os_max;
|
||||||
|
CalibrationValue hk_max;
|
||||||
|
CalibrationValue zero;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size");
|
||||||
|
|
||||||
|
struct SaveCalData {
|
||||||
|
RingConCommands command;
|
||||||
|
UserCalibration calibration;
|
||||||
|
INSERT_PADDING_BYTES_NOINIT(4);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size");
|
||||||
|
static_assert(std::is_trivially_copyable_v<SaveCalData>,
|
||||||
|
"SaveCalData must be trivially copyable");
|
||||||
|
|
||||||
|
struct FirmwareVersionReply {
|
||||||
|
DataValid status;
|
||||||
|
FirmwareVersion firmware;
|
||||||
|
INSERT_PADDING_BYTES(0x2);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size");
|
||||||
|
|
||||||
|
struct Cmd020105Reply {
|
||||||
|
DataValid status;
|
||||||
|
u8 data;
|
||||||
|
INSERT_PADDING_BYTES(0x3);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size");
|
||||||
|
|
||||||
|
struct StatusReply {
|
||||||
|
DataValid status;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size");
|
||||||
|
|
||||||
|
struct GetThreeByteReply {
|
||||||
|
DataValid status;
|
||||||
|
std::array<u8, 3> data;
|
||||||
|
u8 crc;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size");
|
||||||
|
|
||||||
|
struct ReadUnkCalReply {
|
||||||
|
DataValid status;
|
||||||
|
u16 data;
|
||||||
|
INSERT_PADDING_BYTES(0x2);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size");
|
||||||
|
|
||||||
|
struct ReadFactoryCalReply {
|
||||||
|
DataValid status;
|
||||||
|
FactoryCalibration calibration;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size");
|
||||||
|
|
||||||
|
struct ReadUserCalReply {
|
||||||
|
DataValid status;
|
||||||
|
UserCalibration calibration;
|
||||||
|
INSERT_PADDING_BYTES(0x4);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size");
|
||||||
|
|
||||||
|
struct ReadIdReply {
|
||||||
|
DataValid status;
|
||||||
|
u16 id_l_x0;
|
||||||
|
u16 id_l_x0_2;
|
||||||
|
u16 id_l_x4;
|
||||||
|
u16 id_h_x0;
|
||||||
|
u16 id_h_x0_2;
|
||||||
|
u16 id_h_x4;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size");
|
||||||
|
|
||||||
|
struct ErrorReply {
|
||||||
|
DataValid status;
|
||||||
|
INSERT_PADDING_BYTES(0x3);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size");
|
||||||
|
|
||||||
|
struct RingConData {
|
||||||
|
DataValid status;
|
||||||
|
s16_le data;
|
||||||
|
INSERT_PADDING_BYTES(0x2);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");
|
||||||
|
|
||||||
|
// Returns RingConData struct with pressure sensor values
|
||||||
|
RingConData GetSensorValue() const;
|
||||||
|
|
||||||
|
// Returns 8 byte reply with firmware version
|
||||||
|
std::vector<u8> GetFirmwareVersionReply() const;
|
||||||
|
|
||||||
|
// Returns 16 byte reply with ID values
|
||||||
|
std::vector<u8> GetReadIdReply() const;
|
||||||
|
|
||||||
|
// (STUBBED) Returns 8 byte reply
|
||||||
|
std::vector<u8> GetC020105Reply() const;
|
||||||
|
|
||||||
|
// (STUBBED) Returns 8 byte empty reply
|
||||||
|
std::vector<u8> GetReadUnkCalReply() const;
|
||||||
|
|
||||||
|
// Returns 20 byte reply with factory calibration values
|
||||||
|
std::vector<u8> GetReadFactoryCalReply() const;
|
||||||
|
|
||||||
|
// Returns 20 byte reply with user calibration values
|
||||||
|
std::vector<u8> GetReadUserCalReply() const;
|
||||||
|
|
||||||
|
// Returns 8 byte reply
|
||||||
|
std::vector<u8> GetReadRepCountReply() const;
|
||||||
|
|
||||||
|
// Returns 8 byte reply
|
||||||
|
std::vector<u8> GetReadTotalPushCountReply() const;
|
||||||
|
|
||||||
|
// Returns 8 byte reply
|
||||||
|
std::vector<u8> GetResetRepCountReply() const;
|
||||||
|
|
||||||
|
// Returns 4 byte save data reply
|
||||||
|
std::vector<u8> GetSaveDataReply() const;
|
||||||
|
|
||||||
|
// Returns 8 byte error reply
|
||||||
|
std::vector<u8> GetErrorReply() const;
|
||||||
|
|
||||||
|
// Returns 8 bit redundancy check from provided data
|
||||||
|
u8 GetCrcValue(const std::vector<u8>& data) const;
|
||||||
|
|
||||||
|
// Converts structs to an u8 vector equivalent
|
||||||
|
template <typename T>
|
||||||
|
std::vector<u8> GetDataVector(const T& reply) const;
|
||||||
|
|
||||||
|
RingConCommands command{RingConCommands::Error};
|
||||||
|
|
||||||
|
// These counters are used in multitasking mode while the switch is sleeping
|
||||||
|
// Total steps taken
|
||||||
|
u8 total_rep_count = 0;
|
||||||
|
// Total times the ring was pushed
|
||||||
|
u8 total_push_count = 0;
|
||||||
|
|
||||||
|
const u8 device_id = 0x20;
|
||||||
|
const FirmwareVersion version = {
|
||||||
|
.sub = 0x0,
|
||||||
|
.main = 0x2c,
|
||||||
|
};
|
||||||
|
const FactoryCalibration factory_calibration = {
|
||||||
|
.os_max = idle_value + range + idle_deadzone,
|
||||||
|
.hk_max = idle_value - range - idle_deadzone,
|
||||||
|
.zero_min = idle_value - idle_deadzone,
|
||||||
|
.zero_max = idle_value + idle_deadzone,
|
||||||
|
};
|
||||||
|
UserCalibration user_calibration = {
|
||||||
|
.os_max = {.value = range, .crc = 228},
|
||||||
|
.hk_max = {.value = -range, .crc = 239},
|
||||||
|
.zero = {.value = idle_value, .crc = 225},
|
||||||
|
};
|
||||||
|
|
||||||
|
Core::HID::EmulatedDevices* input;
|
||||||
|
};
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hid/emulated_controller.h"
|
||||||
|
#include "core/hid/hid_core.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/starlink.h"
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
constexpr u8 DEVICE_ID = 0x28;
|
||||||
|
|
||||||
|
Starlink::Starlink(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_)
|
||||||
|
: HidbusBase(service_context_) {}
|
||||||
|
Starlink::~Starlink() = default;
|
||||||
|
|
||||||
|
void Starlink::OnInit() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Starlink::OnRelease() {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
void Starlink::OnUpdate() {
|
||||||
|
if (!is_activated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!device_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!polling_mode_enabled || !is_transfer_memory_set) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 Starlink::GetDeviceId() const {
|
||||||
|
return DEVICE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> Starlink::GetReply() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Starlink::SetCommand(const std::vector<u8>& data) {
|
||||||
|
LOG_ERROR(Service_HID, "Command not implemented");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
class EmulatedController;
|
||||||
|
} // namespace Core::HID
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
|
||||||
|
class Starlink final : public HidbusBase {
|
||||||
|
public:
|
||||||
|
explicit Starlink(Core::HID::HIDCore& hid_core_,
|
||||||
|
KernelHelpers::ServiceContext& service_context_);
|
||||||
|
~Starlink() override;
|
||||||
|
|
||||||
|
void OnInit() override;
|
||||||
|
|
||||||
|
void OnRelease() override;
|
||||||
|
|
||||||
|
// Updates ringcon transfer memory
|
||||||
|
void OnUpdate() override;
|
||||||
|
|
||||||
|
// Returns the device ID of the joycon
|
||||||
|
u8 GetDeviceId() const override;
|
||||||
|
|
||||||
|
// Assigns a command from data
|
||||||
|
bool SetCommand(const std::vector<u8>& data) override;
|
||||||
|
|
||||||
|
// Returns a reply from a command
|
||||||
|
std::vector<u8> GetReply() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hid/emulated_controller.h"
|
||||||
|
#include "core/hid/hid_core.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/stubbed.h"
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
constexpr u8 DEVICE_ID = 0xFF;
|
||||||
|
|
||||||
|
HidbusStubbed::HidbusStubbed(Core::HID::HIDCore& hid_core_,
|
||||||
|
KernelHelpers::ServiceContext& service_context_)
|
||||||
|
: HidbusBase(service_context_) {}
|
||||||
|
HidbusStubbed::~HidbusStubbed() = default;
|
||||||
|
|
||||||
|
void HidbusStubbed::OnInit() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidbusStubbed::OnRelease() {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
void HidbusStubbed::OnUpdate() {
|
||||||
|
if (!is_activated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!device_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!polling_mode_enabled || !is_transfer_memory_set) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 HidbusStubbed::GetDeviceId() const {
|
||||||
|
return DEVICE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> HidbusStubbed::GetReply() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HidbusStubbed::SetCommand(const std::vector<u8>& data) {
|
||||||
|
LOG_ERROR(Service_HID, "Command not implemented");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::HID
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
class EmulatedController;
|
||||||
|
} // namespace Core::HID
|
||||||
|
|
||||||
|
namespace Service::HID {
|
||||||
|
|
||||||
|
class HidbusStubbed final : public HidbusBase {
|
||||||
|
public:
|
||||||
|
explicit HidbusStubbed(Core::HID::HIDCore& hid_core_,
|
||||||
|
KernelHelpers::ServiceContext& service_context_);
|
||||||
|
~HidbusStubbed() override;
|
||||||
|
|
||||||
|
void OnInit() override;
|
||||||
|
|
||||||
|
void OnRelease() override;
|
||||||
|
|
||||||
|
// Updates ringcon transfer memory
|
||||||
|
void OnUpdate() override;
|
||||||
|
|
||||||
|
// Returns the device ID of the joycon
|
||||||
|
u8 GetDeviceId() const override;
|
||||||
|
|
||||||
|
// Assigns a command from data
|
||||||
|
bool SetCommand(const std::vector<u8>& data) override;
|
||||||
|
|
||||||
|
// Returns a reply from a command
|
||||||
|
std::vector<u8> GetReply() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Service::HID
|
|
@ -99,6 +99,9 @@ add_executable(yuzu
|
||||||
configuration/configure_profile_manager.cpp
|
configuration/configure_profile_manager.cpp
|
||||||
configuration/configure_profile_manager.h
|
configuration/configure_profile_manager.h
|
||||||
configuration/configure_profile_manager.ui
|
configuration/configure_profile_manager.ui
|
||||||
|
configuration/configure_ringcon.cpp
|
||||||
|
configuration/configure_ringcon.h
|
||||||
|
configuration/configure_ringcon.ui
|
||||||
configuration/configure_network.cpp
|
configuration/configure_network.cpp
|
||||||
configuration/configure_network.h
|
configuration/configure_network.h
|
||||||
configuration/configure_network.ui
|
configuration/configure_network.ui
|
||||||
|
|
|
@ -60,6 +60,11 @@ const std::array<int, 2> Config::default_stick_mod = {
|
||||||
0,
|
0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const std::array<int, 2> Config::default_ringcon_analogs{{
|
||||||
|
Qt::Key_A,
|
||||||
|
Qt::Key_D,
|
||||||
|
}};
|
||||||
|
|
||||||
// This shouldn't have anything except static initializers (no functions). So
|
// This shouldn't have anything except static initializers (no functions). So
|
||||||
// QKeySequence(...).toString() is NOT ALLOWED HERE.
|
// QKeySequence(...).toString() is NOT ALLOWED HERE.
|
||||||
// This must be in alphabetical order according to action name as it must have the same order as
|
// This must be in alphabetical order according to action name as it must have the same order as
|
||||||
|
@ -346,6 +351,23 @@ void Config::ReadTouchscreenValues() {
|
||||||
ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
|
ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Config::ReadHidbusValues() {
|
||||||
|
Settings::values.enable_ring_controller =
|
||||||
|
ReadSetting(QStringLiteral("enable_ring_controller"), true).toBool();
|
||||||
|
|
||||||
|
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||||
|
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
|
||||||
|
auto& ringcon_analogs = Settings::values.ringcon_analogs;
|
||||||
|
|
||||||
|
ringcon_analogs =
|
||||||
|
qt_config->value(QStringLiteral("ring_controller"), QString::fromStdString(default_param))
|
||||||
|
.toString()
|
||||||
|
.toStdString();
|
||||||
|
if (ringcon_analogs.empty()) {
|
||||||
|
ringcon_analogs = default_param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Config::ReadAudioValues() {
|
void Config::ReadAudioValues() {
|
||||||
qt_config->beginGroup(QStringLiteral("Audio"));
|
qt_config->beginGroup(QStringLiteral("Audio"));
|
||||||
|
|
||||||
|
@ -369,6 +391,7 @@ void Config::ReadControlValues() {
|
||||||
ReadMouseValues();
|
ReadMouseValues();
|
||||||
ReadTouchscreenValues();
|
ReadTouchscreenValues();
|
||||||
ReadMotionTouchValues();
|
ReadMotionTouchValues();
|
||||||
|
ReadHidbusValues();
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
ReadBasicSetting(Settings::values.enable_raw_input);
|
ReadBasicSetting(Settings::values.enable_raw_input);
|
||||||
|
@ -962,6 +985,16 @@ void Config::SaveMotionTouchValues() {
|
||||||
qt_config->endArray();
|
qt_config->endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Config::SaveHidbusValues() {
|
||||||
|
WriteBasicSetting(Settings::values.enable_ring_controller);
|
||||||
|
|
||||||
|
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||||
|
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
|
||||||
|
WriteSetting(QStringLiteral("ring_controller"),
|
||||||
|
QString::fromStdString(Settings::values.ringcon_analogs),
|
||||||
|
QString::fromStdString(default_param));
|
||||||
|
}
|
||||||
|
|
||||||
void Config::SaveValues() {
|
void Config::SaveValues() {
|
||||||
if (global) {
|
if (global) {
|
||||||
SaveControlValues();
|
SaveControlValues();
|
||||||
|
@ -1002,6 +1035,7 @@ void Config::SaveControlValues() {
|
||||||
SaveMouseValues();
|
SaveMouseValues();
|
||||||
SaveTouchscreenValues();
|
SaveTouchscreenValues();
|
||||||
SaveMotionTouchValues();
|
SaveMotionTouchValues();
|
||||||
|
SaveHidbusValues();
|
||||||
|
|
||||||
WriteGlobalSetting(Settings::values.use_docked_mode);
|
WriteGlobalSetting(Settings::values.use_docked_mode);
|
||||||
WriteGlobalSetting(Settings::values.vibration_enabled);
|
WriteGlobalSetting(Settings::values.vibration_enabled);
|
||||||
|
|
|
@ -42,6 +42,7 @@ public:
|
||||||
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
|
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
|
||||||
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
||||||
static const std::array<int, 2> default_stick_mod;
|
static const std::array<int, 2> default_stick_mod;
|
||||||
|
static const std::array<int, 2> default_ringcon_analogs;
|
||||||
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
|
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
|
||||||
default_mouse_buttons;
|
default_mouse_buttons;
|
||||||
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
|
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
|
||||||
|
@ -66,6 +67,7 @@ private:
|
||||||
void ReadMouseValues();
|
void ReadMouseValues();
|
||||||
void ReadTouchscreenValues();
|
void ReadTouchscreenValues();
|
||||||
void ReadMotionTouchValues();
|
void ReadMotionTouchValues();
|
||||||
|
void ReadHidbusValues();
|
||||||
|
|
||||||
// Read functions bases off the respective config section names.
|
// Read functions bases off the respective config section names.
|
||||||
void ReadAudioValues();
|
void ReadAudioValues();
|
||||||
|
@ -93,6 +95,7 @@ private:
|
||||||
void SaveMouseValues();
|
void SaveMouseValues();
|
||||||
void SaveTouchscreenValues();
|
void SaveTouchscreenValues();
|
||||||
void SaveMotionTouchValues();
|
void SaveMotionTouchValues();
|
||||||
|
void SaveHidbusValues();
|
||||||
|
|
||||||
// Save functions based off the respective config section names.
|
// Save functions based off the respective config section names.
|
||||||
void SaveAudioValues();
|
void SaveAudioValues();
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "yuzu/configuration/configure_input_advanced.h"
|
#include "yuzu/configuration/configure_input_advanced.h"
|
||||||
#include "yuzu/configuration/configure_input_player.h"
|
#include "yuzu/configuration/configure_input_player.h"
|
||||||
#include "yuzu/configuration/configure_motion_touch.h"
|
#include "yuzu/configuration/configure_motion_touch.h"
|
||||||
|
#include "yuzu/configuration/configure_ringcon.h"
|
||||||
#include "yuzu/configuration/configure_touchscreen_advanced.h"
|
#include "yuzu/configuration/configure_touchscreen_advanced.h"
|
||||||
#include "yuzu/configuration/configure_vibration.h"
|
#include "yuzu/configuration/configure_vibration.h"
|
||||||
#include "yuzu/configuration/input_profiles.h"
|
#include "yuzu/configuration/input_profiles.h"
|
||||||
|
@ -158,6 +159,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
|
||||||
[this, input_subsystem] {
|
[this, input_subsystem] {
|
||||||
CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
|
CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
|
||||||
});
|
});
|
||||||
|
connect(advanced, &ConfigureInputAdvanced::CallRingControllerDialog,
|
||||||
|
[this, input_subsystem, &hid_core] {
|
||||||
|
CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
|
||||||
|
});
|
||||||
|
|
||||||
connect(ui->vibrationButton, &QPushButton::clicked,
|
connect(ui->vibrationButton, &QPushButton::clicked,
|
||||||
[this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
|
[this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
|
||||||
|
|
|
@ -79,13 +79,17 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
|
||||||
&ConfigureInputAdvanced::UpdateUIEnabled);
|
&ConfigureInputAdvanced::UpdateUIEnabled);
|
||||||
connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
|
connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
|
||||||
&ConfigureInputAdvanced::UpdateUIEnabled);
|
&ConfigureInputAdvanced::UpdateUIEnabled);
|
||||||
|
connect(ui->enable_ring_controller, &QCheckBox::stateChanged, this,
|
||||||
|
&ConfigureInputAdvanced::UpdateUIEnabled);
|
||||||
|
|
||||||
connect(ui->debug_configure, &QPushButton::clicked, this,
|
connect(ui->debug_configure, &QPushButton::clicked, this,
|
||||||
[this] { CallDebugControllerDialog(); });
|
[this] { CallDebugControllerDialog(); });
|
||||||
connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
|
connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
|
||||||
[this] { CallTouchscreenConfigDialog(); });
|
[this] { CallTouchscreenConfigDialog(); });
|
||||||
connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
|
connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
|
||||||
&ConfigureInputAdvanced::CallMotionTouchConfigDialog);
|
[this] { CallMotionTouchConfigDialog(); });
|
||||||
|
connect(ui->ring_controller_configure, &QPushButton::clicked, this,
|
||||||
|
[this] { CallRingControllerDialog(); });
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
ui->enable_raw_input->setVisible(false);
|
ui->enable_raw_input->setVisible(false);
|
||||||
|
@ -132,6 +136,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
|
||||||
Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
|
Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
|
||||||
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
|
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
|
||||||
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
|
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
|
||||||
|
Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureInputAdvanced::LoadConfiguration() {
|
void ConfigureInputAdvanced::LoadConfiguration() {
|
||||||
|
@ -164,6 +169,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
|
||||||
ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
|
ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
|
||||||
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
|
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
|
||||||
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
|
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
|
||||||
|
ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
|
||||||
|
|
||||||
UpdateUIEnabled();
|
UpdateUIEnabled();
|
||||||
}
|
}
|
||||||
|
@ -185,4 +191,5 @@ void ConfigureInputAdvanced::UpdateUIEnabled() {
|
||||||
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
|
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
|
||||||
ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
|
ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
|
||||||
ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
|
ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
|
||||||
|
ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked());
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ signals:
|
||||||
void CallMouseConfigDialog();
|
void CallMouseConfigDialog();
|
||||||
void CallTouchscreenConfigDialog();
|
void CallTouchscreenConfigDialog();
|
||||||
void CallMotionTouchConfigDialog();
|
void CallMotionTouchConfigDialog();
|
||||||
|
void CallRingControllerDialog();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void changeEvent(QEvent* event) override;
|
void changeEvent(QEvent* event) override;
|
||||||
|
|
|
@ -2603,6 +2603,20 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QCheckBox" name="enable_ring_controller">
|
||||||
|
<property name="text">
|
||||||
|
<string>Ring Controller</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="2">
|
||||||
|
<widget class="QPushButton" name="ring_controller_configure">
|
||||||
|
<property name="text">
|
||||||
|
<string>Configure</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -1407,10 +1407,10 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
|
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
|
||||||
event->ignore();
|
|
||||||
if (!input_setter || !event) {
|
if (!input_setter || !event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
event->ignore();
|
||||||
if (event->key() != Qt::Key_Escape) {
|
if (event->key() != Qt::Key_Escape) {
|
||||||
input_subsystem->GetKeyboard()->PressKey(event->key());
|
input_subsystem->GetKeyboard()->PressKey(event->key());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,424 @@
|
||||||
|
// Copyright 2022 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "core/hid/emulated_devices.h"
|
||||||
|
#include "core/hid/hid_core.h"
|
||||||
|
#include "input_common/drivers/keyboard.h"
|
||||||
|
#include "input_common/drivers/mouse.h"
|
||||||
|
#include "input_common/main.h"
|
||||||
|
#include "ui_configure_ringcon.h"
|
||||||
|
#include "yuzu/bootmanager.h"
|
||||||
|
#include "yuzu/configuration/config.h"
|
||||||
|
#include "yuzu/configuration/configure_ringcon.h"
|
||||||
|
|
||||||
|
const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM>
|
||||||
|
ConfigureRingController::analog_sub_buttons{{
|
||||||
|
"left",
|
||||||
|
"right",
|
||||||
|
}};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QString GetKeyName(int key_code) {
|
||||||
|
switch (key_code) {
|
||||||
|
case Qt::Key_Shift:
|
||||||
|
return QObject::tr("Shift");
|
||||||
|
case Qt::Key_Control:
|
||||||
|
return QObject::tr("Ctrl");
|
||||||
|
case Qt::Key_Alt:
|
||||||
|
return QObject::tr("Alt");
|
||||||
|
case Qt::Key_Meta:
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return QKeySequence(key_code).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GetButtonName(Common::Input::ButtonNames button_name) {
|
||||||
|
switch (button_name) {
|
||||||
|
case Common::Input::ButtonNames::ButtonLeft:
|
||||||
|
return QObject::tr("Left");
|
||||||
|
case Common::Input::ButtonNames::ButtonRight:
|
||||||
|
return QObject::tr("Right");
|
||||||
|
case Common::Input::ButtonNames::ButtonDown:
|
||||||
|
return QObject::tr("Down");
|
||||||
|
case Common::Input::ButtonNames::ButtonUp:
|
||||||
|
return QObject::tr("Up");
|
||||||
|
case Common::Input::ButtonNames::TriggerZ:
|
||||||
|
return QObject::tr("Z");
|
||||||
|
case Common::Input::ButtonNames::TriggerR:
|
||||||
|
return QObject::tr("R");
|
||||||
|
case Common::Input::ButtonNames::TriggerL:
|
||||||
|
return QObject::tr("L");
|
||||||
|
case Common::Input::ButtonNames::ButtonA:
|
||||||
|
return QObject::tr("A");
|
||||||
|
case Common::Input::ButtonNames::ButtonB:
|
||||||
|
return QObject::tr("B");
|
||||||
|
case Common::Input::ButtonNames::ButtonX:
|
||||||
|
return QObject::tr("X");
|
||||||
|
case Common::Input::ButtonNames::ButtonY:
|
||||||
|
return QObject::tr("Y");
|
||||||
|
case Common::Input::ButtonNames::ButtonStart:
|
||||||
|
return QObject::tr("Start");
|
||||||
|
case Common::Input::ButtonNames::L1:
|
||||||
|
return QObject::tr("L1");
|
||||||
|
case Common::Input::ButtonNames::L2:
|
||||||
|
return QObject::tr("L2");
|
||||||
|
case Common::Input::ButtonNames::L3:
|
||||||
|
return QObject::tr("L3");
|
||||||
|
case Common::Input::ButtonNames::R1:
|
||||||
|
return QObject::tr("R1");
|
||||||
|
case Common::Input::ButtonNames::R2:
|
||||||
|
return QObject::tr("R2");
|
||||||
|
case Common::Input::ButtonNames::R3:
|
||||||
|
return QObject::tr("R3");
|
||||||
|
case Common::Input::ButtonNames::Circle:
|
||||||
|
return QObject::tr("Circle");
|
||||||
|
case Common::Input::ButtonNames::Cross:
|
||||||
|
return QObject::tr("Cross");
|
||||||
|
case Common::Input::ButtonNames::Square:
|
||||||
|
return QObject::tr("Square");
|
||||||
|
case Common::Input::ButtonNames::Triangle:
|
||||||
|
return QObject::tr("Triangle");
|
||||||
|
case Common::Input::ButtonNames::Share:
|
||||||
|
return QObject::tr("Share");
|
||||||
|
case Common::Input::ButtonNames::Options:
|
||||||
|
return QObject::tr("Options");
|
||||||
|
default:
|
||||||
|
return QObject::tr("[undefined]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
|
||||||
|
const std::string& button_name) {
|
||||||
|
// The poller returned a complete axis, so set all the buttons
|
||||||
|
if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
|
||||||
|
analog_param = input_param;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if the current configuration has either no engine or an axis binding.
|
||||||
|
// Clears out the old binding and adds one with analog_from_button.
|
||||||
|
if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
|
||||||
|
analog_param = {
|
||||||
|
{"engine", "analog_from_button"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
analog_param.Set(button_name, input_param.Serialize());
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ConfigureRingController::ConfigureRingController(QWidget* parent,
|
||||||
|
InputCommon::InputSubsystem* input_subsystem_,
|
||||||
|
Core::HID::HIDCore& hid_core_)
|
||||||
|
: QDialog(parent), timeout_timer(std::make_unique<QTimer>()),
|
||||||
|
poll_timer(std::make_unique<QTimer>()), input_subsystem{input_subsystem_},
|
||||||
|
|
||||||
|
ui(std::make_unique<Ui::ConfigureRingController>()) {
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
analog_map_buttons = {
|
||||||
|
ui->buttonRingAnalogPull,
|
||||||
|
ui->buttonRingAnalogPush,
|
||||||
|
};
|
||||||
|
|
||||||
|
emulated_device = hid_core_.GetEmulatedDevices();
|
||||||
|
emulated_device->SaveCurrentConfig();
|
||||||
|
emulated_device->EnableConfiguration();
|
||||||
|
|
||||||
|
LoadConfiguration();
|
||||||
|
|
||||||
|
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
|
||||||
|
auto* const analog_button = analog_map_buttons[sub_button_id];
|
||||||
|
|
||||||
|
if (analog_button == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(analog_button, &QPushButton::clicked, [=, this] {
|
||||||
|
HandleClick(
|
||||||
|
analog_map_buttons[sub_button_id],
|
||||||
|
[=, this](const Common::ParamPackage& params) {
|
||||||
|
Common::ParamPackage param = emulated_device->GetRingParam();
|
||||||
|
SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
|
||||||
|
emulated_device->SetRingParam(param);
|
||||||
|
},
|
||||||
|
InputCommon::Polling::InputType::Stick);
|
||||||
|
});
|
||||||
|
|
||||||
|
analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
|
connect(analog_button, &QPushButton::customContextMenuRequested,
|
||||||
|
[=, this](const QPoint& menu_location) {
|
||||||
|
QMenu context_menu;
|
||||||
|
Common::ParamPackage param = emulated_device->GetRingParam();
|
||||||
|
context_menu.addAction(tr("Clear"), [&] {
|
||||||
|
emulated_device->SetRingParam({});
|
||||||
|
analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
|
||||||
|
});
|
||||||
|
context_menu.addAction(tr("Invert axis"), [&] {
|
||||||
|
const bool invert_value = param.Get("invert_x", "+") == "-";
|
||||||
|
const std::string invert_str = invert_value ? "+" : "-";
|
||||||
|
param.Set("invert_x", invert_str);
|
||||||
|
emulated_device->SetRingParam(param);
|
||||||
|
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM;
|
||||||
|
++sub_button_id) {
|
||||||
|
analog_map_buttons[sub_button_id]->setText(
|
||||||
|
AnalogToText(param, analog_sub_buttons[sub_button_id]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context_menu.exec(
|
||||||
|
analog_map_buttons[sub_button_id]->mapToGlobal(menu_location));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
|
||||||
|
Common::ParamPackage param = emulated_device->GetRingParam();
|
||||||
|
const auto slider_value = ui->sliderRingAnalogDeadzone->value();
|
||||||
|
ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
|
||||||
|
param.Set("deadzone", slider_value / 100.0f);
|
||||||
|
emulated_device->SetRingParam(param);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->restore_defaults_button, &QPushButton::clicked, this,
|
||||||
|
&ConfigureRingController::RestoreDefaults);
|
||||||
|
|
||||||
|
timeout_timer->setSingleShot(true);
|
||||||
|
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
|
||||||
|
|
||||||
|
connect(poll_timer.get(), &QTimer::timeout, [this] {
|
||||||
|
const auto& params = input_subsystem->GetNextInput();
|
||||||
|
if (params.Has("engine") && IsInputAcceptable(params)) {
|
||||||
|
SetPollingResult(params, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resize(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureRingController::~ConfigureRingController() {
|
||||||
|
emulated_device->DisableConfiguration();
|
||||||
|
};
|
||||||
|
|
||||||
|
void ConfigureRingController::changeEvent(QEvent* event) {
|
||||||
|
if (event->type() == QEvent::LanguageChange) {
|
||||||
|
RetranslateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog::changeEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::RetranslateUI() {
|
||||||
|
ui->retranslateUi(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::UpdateUI() {
|
||||||
|
RetranslateUI();
|
||||||
|
const Common::ParamPackage param = emulated_device->GetRingParam();
|
||||||
|
|
||||||
|
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
|
||||||
|
auto* const analog_button = analog_map_buttons[sub_button_id];
|
||||||
|
|
||||||
|
if (analog_button == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto deadzone_label = ui->labelRingAnalogDeadzone;
|
||||||
|
const auto deadzone_slider = ui->sliderRingAnalogDeadzone;
|
||||||
|
|
||||||
|
int slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100);
|
||||||
|
deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
|
||||||
|
deadzone_slider->setValue(slider_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::ApplyConfiguration() {
|
||||||
|
emulated_device->DisableConfiguration();
|
||||||
|
emulated_device->SaveCurrentConfig();
|
||||||
|
emulated_device->EnableConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::LoadConfiguration() {
|
||||||
|
UpdateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::RestoreDefaults() {
|
||||||
|
const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
|
||||||
|
0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
|
||||||
|
emulated_device->SetRingParam(Common::ParamPackage(default_ring_string));
|
||||||
|
UpdateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::HandleClick(
|
||||||
|
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||||
|
InputCommon::Polling::InputType type) {
|
||||||
|
button->setText(tr("[waiting]"));
|
||||||
|
button->setFocus();
|
||||||
|
|
||||||
|
input_setter = new_input_setter;
|
||||||
|
|
||||||
|
input_subsystem->BeginMapping(type);
|
||||||
|
|
||||||
|
QWidget::grabMouse();
|
||||||
|
QWidget::grabKeyboard();
|
||||||
|
|
||||||
|
timeout_timer->start(2500); // Cancel after 2.5 seconds
|
||||||
|
poll_timer->start(25); // Check for new inputs every 25ms
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::SetPollingResult(const Common::ParamPackage& params, bool abort) {
|
||||||
|
timeout_timer->stop();
|
||||||
|
poll_timer->stop();
|
||||||
|
input_subsystem->StopMapping();
|
||||||
|
|
||||||
|
QWidget::releaseMouse();
|
||||||
|
QWidget::releaseKeyboard();
|
||||||
|
|
||||||
|
if (!abort) {
|
||||||
|
(*input_setter)(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUI();
|
||||||
|
|
||||||
|
input_setter = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigureRingController::IsInputAcceptable(const Common::ParamPackage& params) const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::mousePressEvent(QMouseEvent* event) {
|
||||||
|
if (!input_setter || !event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
|
||||||
|
input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRingController::keyPressEvent(QKeyEvent* event) {
|
||||||
|
if (!input_setter || !event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event->ignore();
|
||||||
|
if (event->key() != Qt::Key_Escape) {
|
||||||
|
input_subsystem->GetKeyboard()->PressKey(event->key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConfigureRingController::ButtonToText(const Common::ParamPackage& param) {
|
||||||
|
if (!param.Has("engine")) {
|
||||||
|
return QObject::tr("[not set]");
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
|
||||||
|
const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
|
||||||
|
const auto common_button_name = input_subsystem->GetButtonName(param);
|
||||||
|
|
||||||
|
// Retrieve the names from Qt
|
||||||
|
if (param.Get("engine", "") == "keyboard") {
|
||||||
|
const QString button_str = GetKeyName(param.Get("code", 0));
|
||||||
|
return QObject::tr("%1%2").arg(toggle, button_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common_button_name == Common::Input::ButtonNames::Invalid) {
|
||||||
|
return QObject::tr("[invalid]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common_button_name == Common::Input::ButtonNames::Engine) {
|
||||||
|
return QString::fromStdString(param.Get("engine", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common_button_name == Common::Input::ButtonNames::Value) {
|
||||||
|
if (param.Has("hat")) {
|
||||||
|
const QString hat = QString::fromStdString(param.Get("direction", ""));
|
||||||
|
return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat);
|
||||||
|
}
|
||||||
|
if (param.Has("axis")) {
|
||||||
|
const QString axis = QString::fromStdString(param.Get("axis", ""));
|
||||||
|
return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis);
|
||||||
|
}
|
||||||
|
if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
|
||||||
|
const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
|
||||||
|
const QString axis_y = QString::fromStdString(param.Get("axis_y", ""));
|
||||||
|
const QString axis_z = QString::fromStdString(param.Get("axis_z", ""));
|
||||||
|
return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z);
|
||||||
|
}
|
||||||
|
if (param.Has("motion")) {
|
||||||
|
const QString motion = QString::fromStdString(param.Get("motion", ""));
|
||||||
|
return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion);
|
||||||
|
}
|
||||||
|
if (param.Has("button")) {
|
||||||
|
const QString button = QString::fromStdString(param.Get("button", ""));
|
||||||
|
return QObject::tr("%1%2Button %3").arg(toggle, inverted, button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString button_name = GetButtonName(common_button_name);
|
||||||
|
if (param.Has("hat")) {
|
||||||
|
return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name);
|
||||||
|
}
|
||||||
|
if (param.Has("axis")) {
|
||||||
|
return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
|
||||||
|
}
|
||||||
|
if (param.Has("motion")) {
|
||||||
|
return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
|
||||||
|
}
|
||||||
|
if (param.Has("button")) {
|
||||||
|
return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QObject::tr("[unknown]");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConfigureRingController::AnalogToText(const Common::ParamPackage& param,
|
||||||
|
const std::string& dir) {
|
||||||
|
if (!param.Has("engine")) {
|
||||||
|
return QObject::tr("[not set]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.Get("engine", "") == "analog_from_button") {
|
||||||
|
return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!param.Has("axis_x") || !param.Has("axis_y")) {
|
||||||
|
return QObject::tr("[unknown]");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto engine_str = param.Get("engine", "");
|
||||||
|
const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
|
||||||
|
const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
|
||||||
|
const bool invert_x = param.Get("invert_x", "+") == "-";
|
||||||
|
const bool invert_y = param.Get("invert_y", "+") == "-";
|
||||||
|
|
||||||
|
if (dir == "modifier") {
|
||||||
|
return QObject::tr("[unused]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir == "left") {
|
||||||
|
const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
|
||||||
|
return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
|
||||||
|
}
|
||||||
|
if (dir == "right") {
|
||||||
|
const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
|
||||||
|
return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
|
||||||
|
}
|
||||||
|
if (dir == "up") {
|
||||||
|
const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
|
||||||
|
return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
|
||||||
|
}
|
||||||
|
if (dir == "down") {
|
||||||
|
const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
|
||||||
|
return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QObject::tr("[unknown]");
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2022 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
class InputSubsystem;
|
||||||
|
} // namespace InputCommon
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
class HIDCore;
|
||||||
|
class EmulatedDevices;
|
||||||
|
} // namespace Core::HID
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ConfigureRingController;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
class ConfigureRingController : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ConfigureRingController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_,
|
||||||
|
Core::HID::HIDCore& hid_core_);
|
||||||
|
~ConfigureRingController() override;
|
||||||
|
|
||||||
|
void ApplyConfiguration();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void changeEvent(QEvent* event) override;
|
||||||
|
void RetranslateUI();
|
||||||
|
|
||||||
|
void UpdateUI();
|
||||||
|
|
||||||
|
/// Load configuration settings.
|
||||||
|
void LoadConfiguration();
|
||||||
|
|
||||||
|
/// Restore all buttons to their default values.
|
||||||
|
void RestoreDefaults();
|
||||||
|
|
||||||
|
/// Called when the button was pressed.
|
||||||
|
void HandleClick(QPushButton* button,
|
||||||
|
std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||||
|
InputCommon::Polling::InputType type);
|
||||||
|
|
||||||
|
/// Finish polling and configure input using the input_setter.
|
||||||
|
void SetPollingResult(const Common::ParamPackage& params, bool abort);
|
||||||
|
|
||||||
|
/// Checks whether a given input can be accepted.
|
||||||
|
bool IsInputAcceptable(const Common::ParamPackage& params) const;
|
||||||
|
|
||||||
|
/// Handle mouse button press events.
|
||||||
|
void mousePressEvent(QMouseEvent* event) override;
|
||||||
|
|
||||||
|
/// Handle key press events.
|
||||||
|
void keyPressEvent(QKeyEvent* event) override;
|
||||||
|
|
||||||
|
QString ButtonToText(const Common::ParamPackage& param);
|
||||||
|
|
||||||
|
QString AnalogToText(const Common::ParamPackage& param, const std::string& dir);
|
||||||
|
|
||||||
|
static constexpr int ANALOG_SUB_BUTTONS_NUM = 2;
|
||||||
|
|
||||||
|
// A group of four QPushButtons represent one analog input. The buttons each represent left,
|
||||||
|
// right, respectively.
|
||||||
|
std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM> analog_map_buttons;
|
||||||
|
|
||||||
|
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
|
||||||
|
|
||||||
|
std::unique_ptr<QTimer> timeout_timer;
|
||||||
|
std::unique_ptr<QTimer> poll_timer;
|
||||||
|
|
||||||
|
/// This will be the the setting function when an input is awaiting configuration.
|
||||||
|
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
|
||||||
|
|
||||||
|
InputCommon::InputSubsystem* input_subsystem;
|
||||||
|
Core::HID::EmulatedDevices* emulated_device;
|
||||||
|
|
||||||
|
std::unique_ptr<Ui::ConfigureRingController> ui;
|
||||||
|
};
|
|
@ -0,0 +1,278 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ConfigureRingController</class>
|
||||||
|
<widget class="QDialog" name="ConfigureRingController">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>298</width>
|
||||||
|
<height>339</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Configure Ring Controller</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>280</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>10</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="RingAnalog">
|
||||||
|
<property name="title">
|
||||||
|
<string>Ring Sensor Parameters</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetDefaultConstraint</enum>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item alignment="Qt::AlignHCenter">
|
||||||
|
<widget class="QGroupBox" name="buttonRingAnalogPullGroup">
|
||||||
|
<property name="title">
|
||||||
|
<string>Pull</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="buttonRingAnalogPull">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>68</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>68</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">min-width: 68px;</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Pull</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item alignment="Qt::AlignHCenter">
|
||||||
|
<widget class="QGroupBox" name="buttonRingAnalogPushGroup">
|
||||||
|
<property name="title">
|
||||||
|
<string>Push</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="buttonRingAnalogPush">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>68</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>68</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">min-width: 68px;</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Push</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetDefaultConstraint</enum>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelRingAnalogDeadzone">
|
||||||
|
<property name="text">
|
||||||
|
<string>Deadzone: 0%</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignHCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="sliderRingAnalogDeadzone">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="restore_defaults_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>Restore Defaults</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>ConfigureRingController</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>ConfigureRingController</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
Loading…
Reference in New Issue