diff options
author | Narr the Reg <juangerman-13@hotmail.com> | 2024-01-05 03:37:43 +0100 |
---|---|---|
committer | Narr the Reg <juangerman-13@hotmail.com> | 2024-01-05 18:41:15 +0100 |
commit | ee847f8ff0b1b0aec39c1b78c010bc0c08a0a613 (patch) | |
tree | 3b95cbb74be05f0ce7a007353f1f9f95e1ed3901 /src/hid_core/resources/npad/npad.cpp | |
parent | Merge pull request #12437 from ameerj/gl-amd-fixes (diff) | |
download | yuzu-ee847f8ff0b1b0aec39c1b78c010bc0c08a0a613.tar yuzu-ee847f8ff0b1b0aec39c1b78c010bc0c08a0a613.tar.gz yuzu-ee847f8ff0b1b0aec39c1b78c010bc0c08a0a613.tar.bz2 yuzu-ee847f8ff0b1b0aec39c1b78c010bc0c08a0a613.tar.lz yuzu-ee847f8ff0b1b0aec39c1b78c010bc0c08a0a613.tar.xz yuzu-ee847f8ff0b1b0aec39c1b78c010bc0c08a0a613.tar.zst yuzu-ee847f8ff0b1b0aec39c1b78c010bc0c08a0a613.zip |
Diffstat (limited to 'src/hid_core/resources/npad/npad.cpp')
-rw-r--r-- | src/hid_core/resources/npad/npad.cpp | 1342 |
1 files changed, 1342 insertions, 0 deletions
diff --git a/src/hid_core/resources/npad/npad.cpp b/src/hid_core/resources/npad/npad.cpp new file mode 100644 index 000000000..e6c035628 --- /dev/null +++ b/src/hid_core/resources/npad/npad.cpp @@ -0,0 +1,1342 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <array> +#include <chrono> +#include <cstring> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/service/kernel_helpers.h" +#include "hid_core/frontend/emulated_controller.h" +#include "hid_core/hid_core.h" +#include "hid_core/hid_result.h" +#include "hid_core/hid_util.h" +#include "hid_core/resources/applet_resource.h" +#include "hid_core/resources/npad/npad.h" +#include "hid_core/resources/shared_memory_format.h" + +namespace Service::HID { + +NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_) + : hid_core{hid_core_}, service_context{service_context_}, npad_resource{service_context} { + for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; ++aruid_index) { + for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { + auto& controller = controller_data[aruid_index][i]; + controller.device = hid_core.GetEmulatedControllerByIndex(i); + controller.vibration[Core::HID::EmulatedDeviceIndex::LeftIndex].latest_vibration_value = + Core::HID::DEFAULT_VIBRATION_VALUE; + controller.vibration[Core::HID::EmulatedDeviceIndex::RightIndex] + .latest_vibration_value = Core::HID::DEFAULT_VIBRATION_VALUE; + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = + [this, i](Core::HID::ControllerTriggerType type) { ControllerUpdate(type, i); }, + .is_npad_service = true, + }; + controller.callback_key = controller.device->SetCallback(engine_callback); + } + } +} + +NPad::~NPad() { + for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; ++aruid_index) { + for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { + auto& controller = controller_data[aruid_index][i]; + controller.device->DeleteCallback(controller.callback_key); + } + } +} + +Result NPad::Activate() { + if (ref_counter == std::numeric_limits<s32>::max() - 1) { + return ResultNpadResourceOverflow; + } + + if (ref_counter == 0) { + std::scoped_lock lock{mutex}; + + // TODO: Activate handlers and AbstractedPad + } + + ref_counter++; + return ResultSuccess; +} + +Result NPad::Activate(u64 aruid) { + std::scoped_lock lock{mutex}; + std::scoped_lock shared_lock{*applet_resource_holder.shared_mutex}; + + auto* data = applet_resource_holder.applet_resource->GetAruidData(aruid); + const auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + + if (data == nullptr || !data->flag.is_assigned) { + return ResultSuccess; + } + + for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { + auto& controller = controller_data[aruid_index][i]; + controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state; + } + + // Prefill controller buffers + for (auto& controller : controller_data[aruid_index]) { + auto* npad = controller.shared_memory; + npad->fullkey_color = { + .attribute = ColorAttribute::NoController, + .fullkey = {}, + }; + npad->joycon_color = { + .attribute = ColorAttribute::NoController, + .left = {}, + .right = {}, + }; + // HW seems to initialize the first 19 entries + for (std::size_t i = 0; i < 19; ++i) { + WriteEmptyEntry(npad); + } + } + + return ResultSuccess; +} + +Result NPad::ActivateNpadResource() { + return npad_resource.Activate(); +} + +Result NPad::ActivateNpadResource(u64 aruid) { + return npad_resource.Activate(aruid); +} + +void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx) { + if (type == Core::HID::ControllerTriggerType::All) { + ControllerUpdate(Core::HID::ControllerTriggerType::Connected, controller_idx); + ControllerUpdate(Core::HID::ControllerTriggerType::Battery, controller_idx); + return; + } + + for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { + if (controller_idx >= controller_data[aruid_index].size()) { + return; + } + + auto* data = applet_resource_holder.applet_resource->GetAruidDataByIndex(aruid_index); + + if (!data->flag.is_assigned) { + continue; + } + + auto& controller = controller_data[aruid_index][controller_idx]; + const auto is_connected = controller.device->IsConnected(); + const auto npad_type = controller.device->GetNpadStyleIndex(); + const auto npad_id = controller.device->GetNpadIdType(); + switch (type) { + case Core::HID::ControllerTriggerType::Connected: + case Core::HID::ControllerTriggerType::Disconnected: + if (is_connected == controller.is_connected) { + return; + } + UpdateControllerAt(data->aruid, npad_type, npad_id, is_connected); + break; + case Core::HID::ControllerTriggerType::Battery: { + if (!controller.device->IsConnected()) { + return; + } + auto* shared_memory = controller.shared_memory; + const auto& battery_level = controller.device->GetBattery(); + shared_memory->battery_level_dual = battery_level.dual.battery_level; + shared_memory->battery_level_left = battery_level.left.battery_level; + shared_memory->battery_level_right = battery_level.right.battery_level; + break; + } + default: + break; + } + } +} + +void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) { + auto& controller = GetControllerFromNpadIdType(aruid, npad_id); + if (!npad_resource.IsControllerSupported(aruid, controller.device->GetNpadStyleIndex())) { + return; + } + LOG_DEBUG(Service_HID, "Npad connected {}", npad_id); + const auto controller_type = controller.device->GetNpadStyleIndex(); + const auto& body_colors = controller.device->GetColors(); + const auto& battery_level = controller.device->GetBattery(); + auto* shared_memory = controller.shared_memory; + if (controller_type == Core::HID::NpadStyleIndex::None) { + npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); + return; + } + + // Reset memory values + shared_memory->style_tag.raw = Core::HID::NpadStyleSet::None; + shared_memory->device_type.raw = 0; + shared_memory->system_properties.raw = 0; + shared_memory->joycon_color.attribute = ColorAttribute::NoController; + shared_memory->joycon_color.attribute = ColorAttribute::NoController; + shared_memory->fullkey_color = {}; + shared_memory->joycon_color.left = {}; + shared_memory->joycon_color.right = {}; + shared_memory->battery_level_dual = {}; + shared_memory->battery_level_left = {}; + shared_memory->battery_level_right = {}; + + switch (controller_type) { + case Core::HID::NpadStyleIndex::None: + ASSERT(false); + break; + case Core::HID::NpadStyleIndex::ProController: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->fullkey_color.fullkey = body_colors.fullkey; + shared_memory->battery_level_dual = battery_level.dual.battery_level; + shared_memory->style_tag.fullkey.Assign(1); + shared_memory->device_type.fullkey.Assign(1); + shared_memory->system_properties.is_vertical.Assign(1); + shared_memory->system_properties.use_plus.Assign(1); + shared_memory->system_properties.use_minus.Assign(1); + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.dual.is_charging); + shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController; + shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); + break; + case Core::HID::NpadStyleIndex::Handheld: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->joycon_color.attribute = ColorAttribute::Ok; + shared_memory->fullkey_color.fullkey = body_colors.fullkey; + shared_memory->joycon_color.left = body_colors.left; + shared_memory->joycon_color.right = body_colors.right; + shared_memory->style_tag.handheld.Assign(1); + shared_memory->device_type.handheld_left.Assign(1); + shared_memory->device_type.handheld_right.Assign(1); + shared_memory->system_properties.is_vertical.Assign(1); + shared_memory->system_properties.use_plus.Assign(1); + shared_memory->system_properties.use_minus.Assign(1); + shared_memory->system_properties.use_directional_buttons.Assign(1); + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.left.is_charging); + shared_memory->system_properties.is_charging_joy_left.Assign( + battery_level.left.is_charging); + shared_memory->system_properties.is_charging_joy_right.Assign( + battery_level.right.is_charging); + shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; + shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; + shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); + break; + case Core::HID::NpadStyleIndex::JoyconDual: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->joycon_color.attribute = ColorAttribute::Ok; + shared_memory->style_tag.joycon_dual.Assign(1); + if (controller.is_dual_left_connected) { + shared_memory->joycon_color.left = body_colors.left; + shared_memory->battery_level_left = battery_level.left.battery_level; + shared_memory->device_type.joycon_left.Assign(1); + shared_memory->system_properties.use_minus.Assign(1); + shared_memory->system_properties.is_charging_joy_left.Assign( + battery_level.left.is_charging); + shared_memory->sixaxis_dual_left_properties.is_newly_assigned.Assign(1); + } + if (controller.is_dual_right_connected) { + shared_memory->joycon_color.right = body_colors.right; + shared_memory->battery_level_right = battery_level.right.battery_level; + shared_memory->device_type.joycon_right.Assign(1); + shared_memory->system_properties.use_plus.Assign(1); + shared_memory->system_properties.is_charging_joy_right.Assign( + battery_level.right.is_charging); + shared_memory->sixaxis_dual_right_properties.is_newly_assigned.Assign(1); + } + shared_memory->system_properties.use_directional_buttons.Assign(1); + shared_memory->system_properties.is_vertical.Assign(1); + shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; + + if (controller.is_dual_left_connected && controller.is_dual_right_connected) { + shared_memory->applet_footer_type = AppletFooterUiType::JoyDual; + shared_memory->fullkey_color.fullkey = body_colors.left; + shared_memory->battery_level_dual = battery_level.left.battery_level; + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.left.is_charging); + } else if (controller.is_dual_left_connected) { + shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly; + shared_memory->fullkey_color.fullkey = body_colors.left; + shared_memory->battery_level_dual = battery_level.left.battery_level; + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.left.is_charging); + } else { + shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly; + shared_memory->fullkey_color.fullkey = body_colors.right; + shared_memory->battery_level_dual = battery_level.right.battery_level; + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.right.is_charging); + } + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->fullkey_color.fullkey = body_colors.left; + shared_memory->joycon_color.attribute = ColorAttribute::Ok; + shared_memory->joycon_color.left = body_colors.left; + shared_memory->battery_level_dual = battery_level.left.battery_level; + shared_memory->style_tag.joycon_left.Assign(1); + shared_memory->device_type.joycon_left.Assign(1); + shared_memory->system_properties.is_horizontal.Assign(1); + shared_memory->system_properties.use_minus.Assign(1); + shared_memory->system_properties.is_charging_joy_left.Assign( + battery_level.left.is_charging); + shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal; + shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); + break; + case Core::HID::NpadStyleIndex::JoyconRight: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->fullkey_color.fullkey = body_colors.right; + shared_memory->joycon_color.attribute = ColorAttribute::Ok; + shared_memory->joycon_color.right = body_colors.right; + shared_memory->battery_level_right = battery_level.right.battery_level; + shared_memory->style_tag.joycon_right.Assign(1); + shared_memory->device_type.joycon_right.Assign(1); + shared_memory->system_properties.is_horizontal.Assign(1); + shared_memory->system_properties.use_plus.Assign(1); + shared_memory->system_properties.is_charging_joy_right.Assign( + battery_level.right.is_charging); + shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal; + shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); + break; + case Core::HID::NpadStyleIndex::GameCube: + shared_memory->style_tag.gamecube.Assign(1); + shared_memory->device_type.fullkey.Assign(1); + shared_memory->system_properties.is_vertical.Assign(1); + shared_memory->system_properties.use_plus.Assign(1); + break; + case Core::HID::NpadStyleIndex::Pokeball: + shared_memory->style_tag.palma.Assign(1); + shared_memory->device_type.palma.Assign(1); + shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); + break; + case Core::HID::NpadStyleIndex::NES: + shared_memory->style_tag.lark.Assign(1); + shared_memory->device_type.fullkey.Assign(1); + break; + case Core::HID::NpadStyleIndex::SNES: + shared_memory->style_tag.lucia.Assign(1); + shared_memory->device_type.fullkey.Assign(1); + shared_memory->applet_footer_type = AppletFooterUiType::Lucia; + break; + case Core::HID::NpadStyleIndex::N64: + shared_memory->style_tag.lagoon.Assign(1); + shared_memory->device_type.fullkey.Assign(1); + shared_memory->applet_footer_type = AppletFooterUiType::Lagon; + break; + case Core::HID::NpadStyleIndex::SegaGenesis: + shared_memory->style_tag.lager.Assign(1); + shared_memory->device_type.fullkey.Assign(1); + break; + default: + break; + } + + controller.is_connected = true; + controller.device->Connect(); + controller.device->SetLedPattern(); + if (controller_type == Core::HID::NpadStyleIndex::JoyconDual) { + if (controller.is_dual_left_connected) { + controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::LeftIndex, + Common::Input::PollingMode::Active); + } + if (controller.is_dual_right_connected) { + controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); + } + } else { + controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices, + Common::Input::PollingMode::Active); + } + + npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); + WriteEmptyEntry(controller.shared_memory); + hid_core.SetLastActiveController(npad_id); +} + +void NPad::WriteEmptyEntry(NpadInternalState* npad) { + NPadGenericState dummy_pad_state{}; + NpadGcTriggerState dummy_gc_state{}; + dummy_pad_state.sampling_number = npad->fullkey_lifo.ReadCurrentEntry().sampling_number + 1; + npad->fullkey_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad->handheld_lifo.ReadCurrentEntry().sampling_number + 1; + npad->handheld_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad->joy_dual_lifo.ReadCurrentEntry().sampling_number + 1; + npad->joy_dual_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad->joy_left_lifo.ReadCurrentEntry().sampling_number + 1; + npad->joy_left_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad->joy_right_lifo.ReadCurrentEntry().sampling_number + 1; + npad->joy_right_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad->palma_lifo.ReadCurrentEntry().sampling_number + 1; + npad->palma_lifo.WriteNextEntry(dummy_pad_state); + dummy_pad_state.sampling_number = npad->system_ext_lifo.ReadCurrentEntry().sampling_number + 1; + npad->system_ext_lifo.WriteNextEntry(dummy_pad_state); + dummy_gc_state.sampling_number = npad->gc_trigger_lifo.ReadCurrentEntry().sampling_number + 1; + npad->gc_trigger_lifo.WriteNextEntry(dummy_gc_state); +} + +void NPad::RequestPadStateUpdate(u64 aruid, Core::HID::NpadIdType npad_id) { + std::scoped_lock lock{*applet_resource_holder.shared_mutex}; + auto& controller = GetControllerFromNpadIdType(aruid, npad_id); + const auto controller_type = controller.device->GetNpadStyleIndex(); + + if (!controller.device->IsConnected() && controller.is_connected) { + DisconnectNpad(aruid, npad_id); + return; + } + if (!controller.device->IsConnected()) { + return; + } + if (controller.device->IsConnected() && !controller.is_connected) { + InitNewlyAddedController(aruid, npad_id); + } + + // This function is unique to yuzu for the turbo buttons and motion to work properly + controller.device->StatusUpdate(); + + auto& pad_entry = controller.npad_pad_state; + auto& trigger_entry = controller.npad_trigger_state; + const auto button_state = controller.device->GetNpadButtons(); + const auto stick_state = controller.device->GetSticks(); + + using btn = Core::HID::NpadButton; + pad_entry.npad_buttons.raw = btn::None; + if (controller_type != Core::HID::NpadStyleIndex::JoyconLeft) { + constexpr btn right_button_mask = btn::A | btn::B | btn::X | btn::Y | btn::StickR | btn::R | + btn::ZR | btn::Plus | btn::StickRLeft | btn::StickRUp | + btn::StickRRight | btn::StickRDown; + pad_entry.npad_buttons.raw = button_state.raw & right_button_mask; + pad_entry.r_stick = stick_state.right; + } + + if (controller_type != Core::HID::NpadStyleIndex::JoyconRight) { + constexpr btn left_button_mask = + btn::Left | btn::Up | btn::Right | btn::Down | btn::StickL | btn::L | btn::ZL | + btn::Minus | btn::StickLLeft | btn::StickLUp | btn::StickLRight | btn::StickLDown; + pad_entry.npad_buttons.raw |= button_state.raw & left_button_mask; + pad_entry.l_stick = stick_state.left; + } + + if (controller_type == Core::HID::NpadStyleIndex::JoyconLeft || + controller_type == Core::HID::NpadStyleIndex::JoyconDual) { + pad_entry.npad_buttons.left_sl.Assign(button_state.left_sl); + pad_entry.npad_buttons.left_sr.Assign(button_state.left_sr); + } + + if (controller_type == Core::HID::NpadStyleIndex::JoyconRight || + controller_type == Core::HID::NpadStyleIndex::JoyconDual) { + pad_entry.npad_buttons.right_sl.Assign(button_state.right_sl); + pad_entry.npad_buttons.right_sr.Assign(button_state.right_sr); + } + + if (controller_type == Core::HID::NpadStyleIndex::GameCube) { + const auto& trigger_state = controller.device->GetTriggers(); + trigger_entry.l_analog = trigger_state.left; + trigger_entry.r_analog = trigger_state.right; + pad_entry.npad_buttons.zl.Assign(false); + pad_entry.npad_buttons.zr.Assign(button_state.r); + pad_entry.npad_buttons.l.Assign(button_state.zl); + pad_entry.npad_buttons.r.Assign(button_state.zr); + } + + if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) { + hid_core.SetLastActiveController(npad_id); + } +} + +void NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { + if (ref_counter == 0) { + return; + } + + std::scoped_lock lock{*applet_resource_holder.shared_mutex}; + for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; ++aruid_index) { + const auto* data = applet_resource_holder.applet_resource->GetAruidDataByIndex(aruid_index); + const auto aruid = data->aruid; + + if (!data->flag.is_assigned) { + continue; + } + + for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { + auto& controller = controller_data[aruid_index][i]; + controller.shared_memory = + &data->shared_memory_format->npad.npad_entry[i].internal_state; + auto* npad = controller.shared_memory; + + const auto& controller_type = controller.device->GetNpadStyleIndex(); + + if (controller_type == Core::HID::NpadStyleIndex::None || + !controller.device->IsConnected()) { + continue; + } + + RequestPadStateUpdate(aruid, controller.device->GetNpadIdType()); + auto& pad_state = controller.npad_pad_state; + auto& libnx_state = controller.npad_libnx_state; + auto& trigger_state = controller.npad_trigger_state; + + // LibNX exclusively uses this section, so we always update it since LibNX doesn't + // activate any controllers. + libnx_state.connection_status.raw = 0; + libnx_state.connection_status.is_connected.Assign(1); + switch (controller_type) { + case Core::HID::NpadStyleIndex::None: + ASSERT(false); + break; + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::NES: + case Core::HID::NpadStyleIndex::SNES: + case Core::HID::NpadStyleIndex::N64: + case Core::HID::NpadStyleIndex::SegaGenesis: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_wired.Assign(1); + + libnx_state.connection_status.is_wired.Assign(1); + pad_state.sampling_number = + npad->fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad->fullkey_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::Handheld: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_wired.Assign(1); + pad_state.connection_status.is_left_connected.Assign(1); + pad_state.connection_status.is_right_connected.Assign(1); + pad_state.connection_status.is_left_wired.Assign(1); + pad_state.connection_status.is_right_wired.Assign(1); + + libnx_state.connection_status.is_wired.Assign(1); + libnx_state.connection_status.is_left_connected.Assign(1); + libnx_state.connection_status.is_right_connected.Assign(1); + libnx_state.connection_status.is_left_wired.Assign(1); + libnx_state.connection_status.is_right_wired.Assign(1); + pad_state.sampling_number = + npad->handheld_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad->handheld_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::JoyconDual: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + if (controller.is_dual_left_connected) { + pad_state.connection_status.is_left_connected.Assign(1); + libnx_state.connection_status.is_left_connected.Assign(1); + } + if (controller.is_dual_right_connected) { + pad_state.connection_status.is_right_connected.Assign(1); + libnx_state.connection_status.is_right_connected.Assign(1); + } + + pad_state.sampling_number = + npad->joy_dual_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad->joy_dual_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::JoyconLeft: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_left_connected.Assign(1); + + libnx_state.connection_status.is_left_connected.Assign(1); + pad_state.sampling_number = + npad->joy_left_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad->joy_left_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::JoyconRight: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_right_connected.Assign(1); + + libnx_state.connection_status.is_right_connected.Assign(1); + pad_state.sampling_number = + npad->joy_right_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad->joy_right_lifo.WriteNextEntry(pad_state); + break; + case Core::HID::NpadStyleIndex::GameCube: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.connection_status.is_wired.Assign(1); + + libnx_state.connection_status.is_wired.Assign(1); + pad_state.sampling_number = + npad->fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1; + trigger_state.sampling_number = + npad->gc_trigger_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad->fullkey_lifo.WriteNextEntry(pad_state); + npad->gc_trigger_lifo.WriteNextEntry(trigger_state); + break; + case Core::HID::NpadStyleIndex::Pokeball: + pad_state.connection_status.raw = 0; + pad_state.connection_status.is_connected.Assign(1); + pad_state.sampling_number = + npad->palma_lifo.ReadCurrentEntry().state.sampling_number + 1; + npad->palma_lifo.WriteNextEntry(pad_state); + break; + default: + break; + } + + libnx_state.npad_buttons.raw = pad_state.npad_buttons.raw; + libnx_state.l_stick = pad_state.l_stick; + libnx_state.r_stick = pad_state.r_stick; + npad->system_ext_lifo.WriteNextEntry(pad_state); + + press_state |= static_cast<u64>(pad_state.npad_buttons.raw); + } + } +} + +Result NPad::SetSupportedNpadStyleSet(u64 aruid, Core::HID::NpadStyleSet supported_style_set) { + std::scoped_lock lock{mutex}; + hid_core.SetSupportedStyleTag({supported_style_set}); + const Result result = npad_resource.SetSupportedNpadStyleSet(aruid, supported_style_set); + if (result.IsSuccess()) { + OnUpdate({}); + } + return result; +} + +Result NPad::GetSupportedNpadStyleSet(u64 aruid, + Core::HID::NpadStyleSet& out_supported_style_set) const { + std::scoped_lock lock{mutex}; + const Result result = npad_resource.GetSupportedNpadStyleSet(out_supported_style_set, aruid); + + if (result == ResultUndefinedStyleset) { + out_supported_style_set = Core::HID::NpadStyleSet::None; + return ResultSuccess; + } + + return result; +} + +Result NPad::GetMaskedSupportedNpadStyleSet( + u64 aruid, Core::HID::NpadStyleSet& out_supported_style_set) const { + std::scoped_lock lock{mutex}; + const Result result = + npad_resource.GetMaskedSupportedNpadStyleSet(out_supported_style_set, aruid); + + if (result == ResultUndefinedStyleset) { + out_supported_style_set = Core::HID::NpadStyleSet::None; + return ResultSuccess; + } + + return result; +} + +Result NPad::SetSupportedNpadIdType(u64 aruid, + std::span<const Core::HID::NpadIdType> supported_npad_list) { + std::scoped_lock lock{mutex}; + if (supported_npad_list.size() > MaxSupportedNpadIdTypes) { + return ResultInvalidArraySize; + } + + Result result = npad_resource.SetSupportedNpadIdType(aruid, supported_npad_list); + + if (result.IsSuccess()) { + OnUpdate({}); + } + + return result; +} + +Result NPad::SetNpadJoyHoldType(u64 aruid, NpadJoyHoldType hold_type) { + std::scoped_lock lock{mutex}; + return npad_resource.SetNpadJoyHoldType(aruid, hold_type); +} + +Result NPad::GetNpadJoyHoldType(u64 aruid, NpadJoyHoldType& out_hold_type) const { + std::scoped_lock lock{mutex}; + return npad_resource.GetNpadJoyHoldType(out_hold_type, aruid); +} + +Result NPad::SetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode mode) { + std::scoped_lock lock{mutex}; + Result result = npad_resource.SetNpadHandheldActivationMode(aruid, mode); + if (result.IsSuccess()) { + OnUpdate({}); + } + return result; +} + +Result NPad::GetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode& out_mode) const { + std::scoped_lock lock{mutex}; + return npad_resource.GetNpadHandheldActivationMode(out_mode, aruid); +} + +bool NPad::SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id, + NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + return false; + } + + auto& controller = GetControllerFromNpadIdType(aruid, npad_id); + if (controller.shared_memory->assignment_mode != assignment_mode) { + controller.shared_memory->assignment_mode = assignment_mode; + } + + if (!controller.device->IsConnected()) { + return false; + } + + if (assignment_mode == NpadJoyAssignmentMode::Dual) { + if (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconLeft) { + DisconnectNpad(aruid, npad_id); + controller.is_dual_left_connected = true; + controller.is_dual_right_connected = false; + UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id, true); + return false; + } + if (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight) { + DisconnectNpad(aruid, npad_id); + controller.is_dual_left_connected = false; + controller.is_dual_right_connected = true; + UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id, true); + return false; + } + return false; + } + + // This is for NpadJoyAssignmentMode::Single + + // Only JoyconDual get affected by this function + if (controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::JoyconDual) { + return false; + } + + if (controller.is_dual_left_connected && !controller.is_dual_right_connected) { + DisconnectNpad(aruid, npad_id); + UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconLeft, npad_id, true); + return false; + } + if (!controller.is_dual_left_connected && controller.is_dual_right_connected) { + DisconnectNpad(aruid, npad_id); + UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconRight, npad_id, true); + return false; + } + + // We have two controllers connected to the same npad_id we need to split them + new_npad_id = hid_core.GetFirstDisconnectedNpadId(); + auto& controller_2 = GetControllerFromNpadIdType(aruid, new_npad_id); + DisconnectNpad(aruid, npad_id); + if (npad_device_type == NpadJoyDeviceType::Left) { + UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconLeft, npad_id, true); + controller_2.is_dual_left_connected = false; + controller_2.is_dual_right_connected = true; + UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, new_npad_id, true); + } else { + UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconRight, npad_id, true); + controller_2.is_dual_left_connected = true; + controller_2.is_dual_right_connected = false; + UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, new_npad_id, true); + } + return true; +} + +bool NPad::VibrateControllerAtIndex(u64 aruid, Core::HID::NpadIdType npad_id, + std::size_t device_index, + const Core::HID::VibrationValue& vibration_value) { + auto& controller = GetControllerFromNpadIdType(aruid, npad_id); + if (!controller.device->IsConnected()) { + return false; + } + + if (!controller.device->IsVibrationEnabled(device_index)) { + if (controller.vibration[device_index].latest_vibration_value.low_amplitude != 0.0f || + controller.vibration[device_index].latest_vibration_value.high_amplitude != 0.0f) { + // Send an empty vibration to stop any vibrations. + Core::HID::VibrationValue vibration{0.0f, 160.0f, 0.0f, 320.0f}; + controller.device->SetVibration(device_index, vibration); + // Then reset the vibration value to its default value. + controller.vibration[device_index].latest_vibration_value = + Core::HID::DEFAULT_VIBRATION_VALUE; + } + + return false; + } + + if (!Settings::values.enable_accurate_vibrations.GetValue()) { + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using std::chrono::steady_clock; + + const auto now = steady_clock::now(); + + // Filter out non-zero vibrations that are within 15ms of each other. + if ((vibration_value.low_amplitude != 0.0f || vibration_value.high_amplitude != 0.0f) && + duration_cast<milliseconds>( + now - controller.vibration[device_index].last_vibration_timepoint) < + milliseconds(15)) { + return false; + } + + controller.vibration[device_index].last_vibration_timepoint = now; + } + + Core::HID::VibrationValue vibration{ + vibration_value.low_amplitude, vibration_value.low_frequency, + vibration_value.high_amplitude, vibration_value.high_frequency}; + return controller.device->SetVibration(device_index, vibration); +} + +void NPad::VibrateController(u64 aruid, + const Core::HID::VibrationDeviceHandle& vibration_device_handle, + const Core::HID::VibrationValue& vibration_value) { + if (IsVibrationHandleValid(vibration_device_handle).IsError()) { + return; + } + + if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { + return; + } + + auto& controller = GetControllerFromHandle(aruid, vibration_device_handle); + const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); + + if (!controller.vibration[device_index].device_mounted || !controller.device->IsConnected()) { + return; + } + + if (vibration_device_handle.device_index == Core::HID::DeviceIndex::None) { + ASSERT_MSG(false, "DeviceIndex should never be None!"); + return; + } + + // Some games try to send mismatched parameters in the device handle, block these. + if ((controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconLeft && + (vibration_device_handle.npad_type == Core::HID::NpadStyleIndex::JoyconRight || + vibration_device_handle.device_index == Core::HID::DeviceIndex::Right)) || + (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight && + (vibration_device_handle.npad_type == Core::HID::NpadStyleIndex::JoyconLeft || + vibration_device_handle.device_index == Core::HID::DeviceIndex::Left))) { + return; + } + + // Filter out vibrations with equivalent values to reduce unnecessary state changes. + if (vibration_value.low_amplitude == + controller.vibration[device_index].latest_vibration_value.low_amplitude && + vibration_value.high_amplitude == + controller.vibration[device_index].latest_vibration_value.high_amplitude) { + return; + } + + if (VibrateControllerAtIndex(aruid, controller.device->GetNpadIdType(), device_index, + vibration_value)) { + controller.vibration[device_index].latest_vibration_value = vibration_value; + } +} + +void NPad::VibrateControllers( + u64 aruid, std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles, + std::span<const Core::HID::VibrationValue> vibration_values) { + if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { + return; + } + + ASSERT_OR_EXECUTE_MSG( + vibration_device_handles.size() == vibration_values.size(), { return; }, + "The amount of device handles does not match with the amount of vibration values," + "this is undefined behavior!"); + + for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) { + VibrateController(aruid, vibration_device_handles[i], vibration_values[i]); + } +} + +Core::HID::VibrationValue NPad::GetLastVibration( + u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const { + if (IsVibrationHandleValid(vibration_device_handle).IsError()) { + return {}; + } + + const auto& controller = GetControllerFromHandle(aruid, vibration_device_handle); + const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); + return controller.vibration[device_index].latest_vibration_value; +} + +void NPad::InitializeVibrationDevice( + const Core::HID::VibrationDeviceHandle& vibration_device_handle) { + if (IsVibrationHandleValid(vibration_device_handle).IsError()) { + return; + } + + const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid(); + const auto npad_index = static_cast<Core::HID::NpadIdType>(vibration_device_handle.npad_id); + const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); + InitializeVibrationDeviceAtIndex(aruid, npad_index, device_index); +} + +void NPad::InitializeVibrationDeviceAtIndex(u64 aruid, Core::HID::NpadIdType npad_id, + std::size_t device_index) { + auto& controller = GetControllerFromNpadIdType(aruid, npad_id); + if (!Settings::values.vibration_enabled.GetValue()) { + controller.vibration[device_index].device_mounted = false; + return; + } + + controller.vibration[device_index].device_mounted = + controller.device->IsVibrationEnabled(device_index); +} + +void NPad::SetPermitVibrationSession(bool permit_vibration_session) { + permit_vibration_session_enabled = permit_vibration_session; +} + +bool NPad::IsVibrationDeviceMounted( + u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const { + if (IsVibrationHandleValid(vibration_device_handle).IsError()) { + return false; + } + + const auto& controller = GetControllerFromHandle(aruid, vibration_device_handle); + const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); + return controller.vibration[device_index].device_mounted; +} + +Result NPad::AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event, + Core::HID::NpadIdType npad_id) { + std::scoped_lock lock{mutex}; + return npad_resource.AcquireNpadStyleSetUpdateEventHandle(aruid, out_event, npad_id); +} + +void NPad::AddNewControllerAt(u64 aruid, Core::HID::NpadStyleIndex controller, + Core::HID::NpadIdType npad_id) { + UpdateControllerAt(aruid, controller, npad_id, true); +} + +void NPad::UpdateControllerAt(u64 aruid, Core::HID::NpadStyleIndex type, + Core::HID::NpadIdType npad_id, bool connected) { + auto& controller = GetControllerFromNpadIdType(aruid, npad_id); + if (!connected) { + DisconnectNpad(aruid, npad_id); + return; + } + + controller.device->SetNpadStyleIndex(type); + InitNewlyAddedController(aruid, npad_id); +} + +Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + return ResultInvalidNpadId; + } + + LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id); + auto& controller = GetControllerFromNpadIdType(aruid, npad_id); + for (std::size_t device_idx = 0; device_idx < controller.vibration.size(); ++device_idx) { + // Send an empty vibration to stop any vibrations. + VibrateControllerAtIndex(aruid, npad_id, device_idx, {}); + controller.vibration[device_idx].device_mounted = false; + } + + auto* shared_memory = controller.shared_memory; + // Don't reset shared_memory->assignment_mode this value is persistent + shared_memory->style_tag.raw = Core::HID::NpadStyleSet::None; // Zero out + shared_memory->device_type.raw = 0; + shared_memory->system_properties.raw = 0; + shared_memory->button_properties.raw = 0; + shared_memory->sixaxis_fullkey_properties.raw = 0; + shared_memory->sixaxis_handheld_properties.raw = 0; + shared_memory->sixaxis_dual_left_properties.raw = 0; + shared_memory->sixaxis_dual_right_properties.raw = 0; + shared_memory->sixaxis_left_properties.raw = 0; + shared_memory->sixaxis_right_properties.raw = 0; + shared_memory->battery_level_dual = Core::HID::NpadBatteryLevel::Empty; + shared_memory->battery_level_left = Core::HID::NpadBatteryLevel::Empty; + shared_memory->battery_level_right = Core::HID::NpadBatteryLevel::Empty; + shared_memory->fullkey_color = { + .attribute = ColorAttribute::NoController, + .fullkey = {}, + }; + shared_memory->joycon_color = { + .attribute = ColorAttribute::NoController, + .left = {}, + .right = {}, + }; + shared_memory->applet_footer_type = AppletFooterUiType::None; + + controller.is_dual_left_connected = true; + controller.is_dual_right_connected = true; + controller.is_connected = false; + controller.device->Disconnect(); + npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); + WriteEmptyEntry(shared_memory); + return ResultSuccess; +} + +Result NPad::IsFirmwareUpdateAvailableForSixAxisSensor( + u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle, + bool& is_firmware_available) const { + const auto is_valid = IsSixaxisHandleValid(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; + } + + const auto& sixaxis_properties = GetSixaxisProperties(aruid, sixaxis_handle); + is_firmware_available = sixaxis_properties.is_firmware_update_available != 0; + return ResultSuccess; +} + +Result NPad::ResetIsSixAxisSensorDeviceNewlyAssigned( + u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle) { + const auto is_valid = IsSixaxisHandleValid(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; + } + + auto& sixaxis_properties = GetSixaxisProperties(aruid, sixaxis_handle); + sixaxis_properties.is_newly_assigned.Assign(0); + + return ResultSuccess; +} + +Result NPad::MergeSingleJoyAsDualJoy(u64 aruid, Core::HID::NpadIdType npad_id_1, + Core::HID::NpadIdType npad_id_2) { + if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, + npad_id_2); + return ResultInvalidNpadId; + } + auto& controller_1 = GetControllerFromNpadIdType(aruid, npad_id_1); + auto& controller_2 = GetControllerFromNpadIdType(aruid, npad_id_2); + auto controller_style_1 = controller_1.device->GetNpadStyleIndex(); + auto controller_style_2 = controller_2.device->GetNpadStyleIndex(); + + // Simplify this code by converting dualjoycon with only a side connected to single joycons + if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual) { + if (controller_1.is_dual_left_connected && !controller_1.is_dual_right_connected) { + controller_style_1 = Core::HID::NpadStyleIndex::JoyconLeft; + } + if (!controller_1.is_dual_left_connected && controller_1.is_dual_right_connected) { + controller_style_1 = Core::HID::NpadStyleIndex::JoyconRight; + } + } + if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual) { + if (controller_2.is_dual_left_connected && !controller_2.is_dual_right_connected) { + controller_style_2 = Core::HID::NpadStyleIndex::JoyconLeft; + } + if (!controller_2.is_dual_left_connected && controller_2.is_dual_right_connected) { + controller_style_2 = Core::HID::NpadStyleIndex::JoyconRight; + } + } + + // Invalid merge errors + if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual || + controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual) { + return NpadIsDualJoycon; + } + if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconLeft && + controller_style_2 == Core::HID::NpadStyleIndex::JoyconLeft) { + return NpadIsSameType; + } + if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconRight && + controller_style_2 == Core::HID::NpadStyleIndex::JoyconRight) { + return NpadIsSameType; + } + + // These exceptions are handled as if they where dual joycon + if (controller_style_1 != Core::HID::NpadStyleIndex::JoyconLeft && + controller_style_1 != Core::HID::NpadStyleIndex::JoyconRight) { + return NpadIsDualJoycon; + } + if (controller_style_2 != Core::HID::NpadStyleIndex::JoyconLeft && + controller_style_2 != Core::HID::NpadStyleIndex::JoyconRight) { + return NpadIsDualJoycon; + } + + // Disconnect the joycons and connect them as dual joycon at the first index. + DisconnectNpad(aruid, npad_id_1); + DisconnectNpad(aruid, npad_id_2); + controller_1.is_dual_left_connected = true; + controller_1.is_dual_right_connected = true; + AddNewControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id_1); + return ResultSuccess; +} + +Result NPad::StartLrAssignmentMode(u64 aruid) { + std::scoped_lock lock{mutex}; + bool is_enabled{}; + Result result = npad_resource.GetLrAssignmentMode(is_enabled, aruid); + if (result.IsSuccess() && is_enabled == false) { + result = npad_resource.SetLrAssignmentMode(aruid, true); + } + return result; +} + +Result NPad::StopLrAssignmentMode(u64 aruid) { + std::scoped_lock lock{mutex}; + bool is_enabled{}; + Result result = npad_resource.GetLrAssignmentMode(is_enabled, aruid); + if (result.IsSuccess() && is_enabled == true) { + result = npad_resource.SetLrAssignmentMode(aruid, false); + } + return result; +} + +Result NPad::SwapNpadAssignment(u64 aruid, Core::HID::NpadIdType npad_id_1, + Core::HID::NpadIdType npad_id_2) { + if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, + npad_id_2); + return ResultInvalidNpadId; + } + if (npad_id_1 == Core::HID::NpadIdType::Handheld || + npad_id_2 == Core::HID::NpadIdType::Handheld || npad_id_1 == Core::HID::NpadIdType::Other || + npad_id_2 == Core::HID::NpadIdType::Other) { + return ResultSuccess; + } + const auto& controller_1 = GetControllerFromNpadIdType(aruid, npad_id_1).device; + const auto& controller_2 = GetControllerFromNpadIdType(aruid, npad_id_2).device; + const auto type_index_1 = controller_1->GetNpadStyleIndex(); + const auto type_index_2 = controller_2->GetNpadStyleIndex(); + const auto is_connected_1 = controller_1->IsConnected(); + const auto is_connected_2 = controller_2->IsConnected(); + + if (!npad_resource.IsControllerSupported(aruid, type_index_1) && is_connected_1) { + return ResultNpadNotConnected; + } + if (!npad_resource.IsControllerSupported(aruid, type_index_2) && is_connected_2) { + return ResultNpadNotConnected; + } + + UpdateControllerAt(aruid, type_index_2, npad_id_1, is_connected_2); + UpdateControllerAt(aruid, type_index_1, npad_id_2, is_connected_1); + + return ResultSuccess; +} + +Result NPad::GetLedPattern(Core::HID::NpadIdType npad_id, Core::HID::LedPattern& pattern) const { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + return ResultInvalidNpadId; + } + const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid(); + const auto& controller = GetControllerFromNpadIdType(aruid, npad_id).device; + pattern = controller->GetLedPattern(); + return ResultSuccess; +} + +Result NPad::IsUnintendedHomeButtonInputProtectionEnabled(bool& out_is_enabled, u64 aruid, + Core::HID::NpadIdType npad_id) const { + std::scoped_lock lock{mutex}; + return npad_resource.GetHomeProtectionEnabled(out_is_enabled, aruid, npad_id); +} + +Result NPad::EnableUnintendedHomeButtonInputProtection(u64 aruid, Core::HID::NpadIdType npad_id, + bool is_enabled) { + std::scoped_lock lock{mutex}; + return npad_resource.SetHomeProtectionEnabled(aruid, npad_id, is_enabled); +} + +void NPad::SetNpadAnalogStickUseCenterClamp(u64 aruid, bool is_enabled) { + std::scoped_lock lock{mutex}; + npad_resource.SetNpadAnalogStickUseCenterClamp(aruid, is_enabled); +} + +void NPad::ClearAllConnectedControllers() { + for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { + for (auto& controller : controller_data[aruid_index]) { + if (controller.device->IsConnected() && + controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::None) { + controller.device->Disconnect(); + controller.device->SetNpadStyleIndex(Core::HID::NpadStyleIndex::None); + } + } + } +} + +void NPad::DisconnectAllConnectedControllers() { + for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { + for (auto& controller : controller_data[aruid_index]) { + controller.device->Disconnect(); + } + } +} + +void NPad::ConnectAllDisconnectedControllers() { + for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { + for (auto& controller : controller_data[aruid_index]) { + if (controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::None && + !controller.device->IsConnected()) { + controller.device->Connect(); + } + } + } +} + +void NPad::ClearAllControllers() { + for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { + for (auto& controller : controller_data[aruid_index]) { + controller.device->Disconnect(); + controller.device->SetNpadStyleIndex(Core::HID::NpadStyleIndex::None); + } + } +} + +Core::HID::NpadButton NPad::GetAndResetPressState() { + return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); +} + +Result NPad::ApplyNpadSystemCommonPolicy(u64 aruid) { + std::scoped_lock lock{mutex}; + const Result result = npad_resource.ApplyNpadSystemCommonPolicy(aruid, false); + if (result.IsSuccess()) { + OnUpdate({}); + } + return result; +} + +Result NPad::ApplyNpadSystemCommonPolicyFull(u64 aruid) { + std::scoped_lock lock{mutex}; + const Result result = npad_resource.ApplyNpadSystemCommonPolicy(aruid, true); + if (result.IsSuccess()) { + OnUpdate({}); + } + return result; +} + +Result NPad::ClearNpadSystemCommonPolicy(u64 aruid) { + std::scoped_lock lock{mutex}; + const Result result = npad_resource.ClearNpadSystemCommonPolicy(aruid); + if (result.IsSuccess()) { + OnUpdate({}); + } + return result; +} + +void NPad::SetRevision(u64 aruid, NpadRevision revision) { + npad_resource.SetNpadRevision(aruid, revision); +} + +NpadRevision NPad::GetRevision(u64 aruid) { + return npad_resource.GetNpadRevision(aruid); +} + +Result NPad::RegisterAppletResourceUserId(u64 aruid) { + return npad_resource.RegisterAppletResourceUserId(aruid); +} + +void NPad::UnregisterAppletResourceUserId(u64 aruid) { + npad_resource.UnregisterAppletResourceUserId(aruid); +} + +void NPad::SetNpadExternals(std::shared_ptr<AppletResource> resource, + std::recursive_mutex* shared_mutex) { + applet_resource_holder.applet_resource = resource; + applet_resource_holder.shared_mutex = shared_mutex; + applet_resource_holder.shared_npad_resource = &npad_resource; +} + +NPad::NpadControllerData& NPad::GetControllerFromHandle( + u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) { + const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id); + return GetControllerFromNpadIdType(aruid, npad_id); +} + +const NPad::NpadControllerData& NPad::GetControllerFromHandle( + u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) const { + const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id); + return GetControllerFromNpadIdType(aruid, npad_id); +} + +NPad::NpadControllerData& NPad::GetControllerFromHandle( + u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle) { + const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id); + return GetControllerFromNpadIdType(aruid, npad_id); +} + +const NPad::NpadControllerData& NPad::GetControllerFromHandle( + u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle) const { + const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id); + return GetControllerFromNpadIdType(aruid, npad_id); +} + +NPad::NpadControllerData& NPad::GetControllerFromNpadIdType(u64 aruid, + Core::HID::NpadIdType npad_id) { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + npad_id = Core::HID::NpadIdType::Player1; + } + const auto npad_index = NpadIdTypeToIndex(npad_id); + const auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + return controller_data[aruid_index][npad_index]; +} + +const NPad::NpadControllerData& NPad::GetControllerFromNpadIdType( + u64 aruid, Core::HID::NpadIdType npad_id) const { + if (!IsNpadIdValid(npad_id)) { + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); + npad_id = Core::HID::NpadIdType::Player1; + } + const auto npad_index = NpadIdTypeToIndex(npad_id); + const auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + return controller_data[aruid_index][npad_index]; +} + +Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties( + u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle) { + auto& controller = GetControllerFromHandle(aruid, sixaxis_handle); + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::Pokeball: + return controller.shared_memory->sixaxis_fullkey_properties; + case Core::HID::NpadStyleIndex::Handheld: + return controller.shared_memory->sixaxis_handheld_properties; + case Core::HID::NpadStyleIndex::JoyconDual: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + return controller.shared_memory->sixaxis_dual_left_properties; + } + return controller.shared_memory->sixaxis_dual_right_properties; + case Core::HID::NpadStyleIndex::JoyconLeft: + return controller.shared_memory->sixaxis_left_properties; + case Core::HID::NpadStyleIndex::JoyconRight: + return controller.shared_memory->sixaxis_right_properties; + default: + return controller.shared_memory->sixaxis_fullkey_properties; + } +} + +const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties( + u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle) const { + const auto& controller = GetControllerFromHandle(aruid, sixaxis_handle); + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::Pokeball: + return controller.shared_memory->sixaxis_fullkey_properties; + case Core::HID::NpadStyleIndex::Handheld: + return controller.shared_memory->sixaxis_handheld_properties; + case Core::HID::NpadStyleIndex::JoyconDual: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + return controller.shared_memory->sixaxis_dual_left_properties; + } + return controller.shared_memory->sixaxis_dual_right_properties; + case Core::HID::NpadStyleIndex::JoyconLeft: + return controller.shared_memory->sixaxis_left_properties; + case Core::HID::NpadStyleIndex::JoyconRight: + return controller.shared_memory->sixaxis_right_properties; + default: + return controller.shared_memory->sixaxis_fullkey_properties; + } +} + +AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) { + const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid(); + const auto& shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory; + + return { + .ui_variant = 0, + .footer = shared_memory->applet_footer_type, + }; +} + +} // namespace Service::HID |