// 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);
if (aruid == 0) {
return;
}
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