Browse Source
Merge pull request #6558 from german77/ringcon2
Merge pull request #6558 from german77/ringcon2
hidbus: Implement hidbus and ringconnce_cpp
committed by
GitHub
29 changed files with 2608 additions and 28 deletions
-
3src/common/settings.h
-
1src/common/settings_input.h
-
10src/core/CMakeLists.txt
-
45src/core/hid/emulated_devices.cpp
-
34src/core/hid/emulated_devices.h
-
18src/core/hle/kernel/kernel.cpp
-
6src/core/hle/kernel/kernel.h
-
27src/core/hle/service/hid/hid.cpp
-
531src/core/hle/service/hid/hidbus.cpp
-
131src/core/hle/service/hid/hidbus.h
-
72src/core/hle/service/hid/hidbus/hidbus_base.cpp
-
179src/core/hle/service/hid/hidbus/hidbus_base.h
-
286src/core/hle/service/hid/hidbus/ringcon.cpp
-
254src/core/hle/service/hid/hidbus/ringcon.h
-
51src/core/hle/service/hid/hidbus/starlink.cpp
-
39src/core/hle/service/hid/hidbus/starlink.h
-
52src/core/hle/service/hid/hidbus/stubbed.cpp
-
39src/core/hle/service/hid/hidbus/stubbed.h
-
3src/yuzu/CMakeLists.txt
-
34src/yuzu/configuration/config.cpp
-
3src/yuzu/configuration/config.h
-
5src/yuzu/configuration/configure_input.cpp
-
9src/yuzu/configuration/configure_input_advanced.cpp
-
1src/yuzu/configuration/configure_input_advanced.h
-
14src/yuzu/configuration/configure_input_advanced.ui
-
2src/yuzu/configuration/configure_input_player.cpp
-
424src/yuzu/configuration/configure_ringcon.cpp
-
85src/yuzu/configuration/configure_ringcon.h
-
278src/yuzu/configuration/configure_ringcon.ui
@ -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 |
|||
@ -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> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue