summaryrefslogtreecommitdiffstats
path: root/src/hid_core/resources/npad/npad.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_core/resources/npad/npad.cpp')
-rw-r--r--src/hid_core/resources/npad/npad.cpp1342
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