diff --git a/nx/include/switch.h b/nx/include/switch.h index fcc2b4f5..2dbcc022 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -159,6 +159,7 @@ extern "C" { #include "switch/runtime/diag.h" #include "switch/runtime/nxlink.h" #include "switch/runtime/resolver.h" +#include "switch/runtime/pad.h" #include "switch/runtime/ringcon.h" #include "switch/runtime/btdev.h" diff --git a/nx/include/switch/runtime/pad.h b/nx/include/switch/runtime/pad.h new file mode 100644 index 00000000..9f5b414d --- /dev/null +++ b/nx/include/switch/runtime/pad.h @@ -0,0 +1,224 @@ +/** + * @file pad.h + * @brief Simple wrapper for the HID Npad API. + * @author fincs + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../services/hid.h" + +/// Mask including all existing controller IDs. +#define PAD_ANY_ID_MASK 0x1000100FFUL + +/// Pad state object. +typedef struct { + u8 id_mask; + u8 active_id_mask; + bool read_handheld; + bool active_handheld; + u32 style_set; + u32 attributes; + u64 buttons_cur; + u64 buttons_old; + HidAnalogStickState sticks[2]; + u32 gc_triggers[2]; +} PadState; + +/// Pad button repeater state object. +typedef struct { + u64 button_mask; + s32 counter; + u16 delay; + u16 repeat; +} PadRepeater; + +/** + * @brief Configures the input layout supported by the application. + * @param[in] max_players The maximum supported number of players (1 to 8). + * @param[in] style_set Bitfield of supported controller styles (see \ref HidNpadStyleTag). + */ +void padConfigureInput(u32 max_players, u32 style_set); + +/** + * @brief Initializes a \ref PadState object to read input from one or more controller input sources. + * @param[in] _pad Pointer to \ref PadState. + * @remarks This is a variadic macro, pass the \ref HidNpadIdType value of each controller to add to the set. + */ +#define padInitialize(_pad, ...) ({ \ + const HidNpadIdType _pad_ids[] = { __VA_ARGS__ }; \ + u64 _pad_mask = 0; \ + for (unsigned _pad_i = 0; _pad_i < (sizeof(_pad_ids)/sizeof(_pad_ids[0])); ++_pad_i) \ + _pad_mask |= 1UL << (_pad_ids[_pad_i]); \ + padInitializeWithMask((_pad), _pad_mask); \ +}) + +/** + * @brief Same as \ref padInitialize, but taking a bitfield of controller IDs directly. + * @param[in] pad Pointer to \ref PadState. + * @param[in] mask Bitfield of controller IDs (each bit's position indicates a different \ref HidNpadIdType value). + */ +void padInitializeWithMask(PadState* pad, u64 mask); + +/** + * @brief Same as \ref padInitialize, but including every single controller input source. + * @param[in] pad Pointer to \ref PadState. + * @remark Use this function if you want to accept input from any controller. + */ +NX_INLINE void padInitializeAny(PadState* pad) { + padInitializeWithMask(pad, PAD_ANY_ID_MASK); +} + +/** + * @brief Same as \ref padInitialize, but including \ref HidNpadIdType_No1 and \ref HidNpadIdType_Handheld. + * @param[in] pad Pointer to \ref PadState. + * @remark Use this function if you just want to accept input for a single-player application. + */ +NX_INLINE void padInitializeDefault(PadState* pad) { + padInitialize(pad, HidNpadIdType_No1, HidNpadIdType_Handheld); +} + +/** + * @brief Updates pad state by reading from the controller input sources specified during initialization. + * @param[in] pad Pointer to \ref PadState. + */ +void padUpdate(PadState* pad); + +/** + * @brief Retrieves whether \ref HidNpadIdType_Handheld is an active input source (i.e. it was possible to read from it). + * @param[in] pad Pointer to \ref PadState. + * @return Boolean value. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR bool padIsHandheld(const PadState* pad) { + return pad->active_handheld; +} + +/** + * @brief Retrieves whether the specified controller is an active input source (i.e. it was possible to read from it). + * @param[in] pad Pointer to \ref PadState. + * @param[in] id ID of the controller input source (see \ref HidNpadIdType) + * @return Boolean value. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR bool padIsNpadActive(const PadState* pad, HidNpadIdType id) { + if (id <= HidNpadIdType_No8) + return pad->active_id_mask & BIT(id); + else if (id == HidNpadIdType_Handheld) + return pad->active_handheld; + else + return false; +} + +/** + * @brief Retrieves the set of input styles supported by the selected controller input sources. + * @param[in] pad Pointer to \ref PadState. + * @return Bitfield of \ref HidNpadStyleTag. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR u32 padGetStyleSet(const PadState* pad) { + return pad->style_set; +} + +/** + * @brief Retrieves the set of attributes reported by the system for the selected controller input sources. + * @param[in] pad Pointer to \ref PadState. + * @return Bitfield of \ref HidNpadAttribute. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR u32 padGetAttributes(const PadState* pad) { + return pad->attributes; +} + +/** + * @brief Retrieves whether any of the selected controller input sources is connected. + * @param[in] pad Pointer to \ref PadState. + * @return Boolean value. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR bool padIsConnected(const PadState* pad) { + return pad->attributes & HidNpadAttribute_IsConnected; +} + +/** + * @brief Retrieves the current set of pressed buttons across all selected controller input sources. + * @param[in] pad Pointer to \ref PadState. + * @return Bitfield of \ref HidNpadButton. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR u64 padGetButtons(const PadState* pad) { + return pad->buttons_cur; +} + +/** + * @brief Retrieves the set of buttons that are newly pressed. + * @param[in] pad Pointer to \ref PadState. + * @return Bitfield of \ref HidNpadButton. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR u64 padGetButtonsDown(const PadState* pad) { + return ~pad->buttons_old & pad->buttons_cur; +} + +/** + * @brief Retrieves the set of buttons that are newly released. + * @param[in] pad Pointer to \ref PadState. + * @return Bitfield of \ref HidNpadButton. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR u64 padGetButtonsUp(const PadState* pad) { + return pad->buttons_old & ~pad->buttons_cur; +} + +/** + * @brief Retrieves the position of an analog stick in a controller. + * @param[in] pad Pointer to \ref PadState. + * @param[in] i ID of the analog stick to read (0=left, 1=right). + * @return \ref HidAnalogStickState. + * @remark \ref padUpdate must have been previously called. + */ +NX_CONSTEXPR HidAnalogStickState padGetStickPos(const PadState* pad, unsigned i) { + return pad->sticks[i]; +} + +/** + * @brief Retrieves the position of an analog trigger in a GameCube controller. + * @param[in] pad Pointer to \ref PadState. + * @param[in] i ID of the analog trigger to read (0=left, 1=right). + * @return Analog trigger position (range is 0 to 0x7fff). + * @remark \ref padUpdate must have been previously called. + * @remark \ref HidNpadStyleTag_NpadGc must have been previously configured as a supported style in \ref padConfigureInput for GC trigger data to be readable. + */ +NX_CONSTEXPR u32 padGetGcTriggerPos(const PadState* pad, unsigned i) { + return pad->gc_triggers[i]; +} + +/** + * @brief Initializes a \ref PadRepeater object with the specified settings. + * @param[in] r Pointer to \ref PadRepeater. + * @param[in] delay Number of input updates between button presses being first detected and them being considered for repeat. + * @param[in] repeat Number of input updates between autogenerated repeat button presses. + */ +NX_CONSTEXPR void padRepeaterInitialize(PadRepeater* r, u16 delay, u16 repeat) { + r->button_mask = 0; + r->counter = 0; + r->delay = delay; + r->repeat = repeat; +} + +/** + * @brief Updates pad repeat state. + * @param[in] r Pointer to \ref PadRepeater. + * @param[in] button_mask Bitfield of currently pressed \ref HidNpadButton that will be considered for repeat. + */ +void padRepeaterUpdate(PadRepeater* r, u64 button_mask); + +/** + * @brief Retrieves the set of buttons that are being repeated according to the parameters specified in \ref padRepeaterInitialize. + * @param[in] r Pointer to \ref PadRepeater. + * @return Bitfield of \ref HidNpadButton. + * @remark It is suggested to bitwise-OR the return value of this function with that of \ref padGetButtonsDown. + */ +NX_CONSTEXPR u64 padRepeaterGetButtons(const PadRepeater* r) { + return r->counter == 0 ? r->button_mask : 0; +} diff --git a/nx/source/runtime/pad.c b/nx/source/runtime/pad.c new file mode 100644 index 00000000..72072243 --- /dev/null +++ b/nx/source/runtime/pad.c @@ -0,0 +1,127 @@ +#include +#include "runtime/diag.h" +#include "runtime/pad.h" + +NX_CONSTEXPR bool _isStickMoving(const HidAnalogStickState* stick) { + return stick->x != 0 && stick->y != 0; +} + +void padConfigureInput(u32 max_players, u32 style_set) { + Result rc; + HidNpadIdType id_set[9]; + + if (max_players == 0 || max_players > 8) + diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_BadInput)); + + for (u32 i = 0; i < max_players; i ++) + id_set[i] = HidNpadIdType_No1 + i; + if (style_set & (HidNpadStyleTag_NpadHandheld|HidNpadStyleTag_NpadHandheldLark)) + id_set[max_players++] = HidNpadIdType_Handheld; + + hidInitializeNpad(); + rc = hidSetSupportedNpadIdType(id_set, max_players); + if (R_FAILED(rc)) diagAbortWithResult(rc); + rc = hidSetSupportedNpadStyleSet(style_set); + if (R_FAILED(rc)) diagAbortWithResult(rc); +} + +void padInitializeWithMask(PadState* pad, u64 mask) { + memset(pad, 0, sizeof(*pad)); + pad->id_mask = mask & 0xff; + pad->read_handheld = (mask & (1UL << HidNpadIdType_Handheld)) != 0; +} + +static void _padUpdateWithCommonState(PadState* pad, const HidNpadCommonState* state) { + pad->attributes |= state->attributes; + pad->buttons_cur |= state->buttons; + if (!_isStickMoving(&pad->sticks[0])) + pad->sticks[0] = state->analog_stick_l; + if (!_isStickMoving(&pad->sticks[1])) + pad->sticks[1] = state->analog_stick_r; +} + +static void _padUpdateWithGcState(PadState* pad, const HidNpadGcState* state) { + pad->attributes |= state->attributes; + pad->buttons_cur |= state->buttons; + if (!_isStickMoving(&pad->sticks[0])) + pad->sticks[0] = state->analog_stick_l; + if (!_isStickMoving(&pad->sticks[1])) + pad->sticks[1] = state->analog_stick_r; + if (pad->gc_triggers[0] < state->trigger_l) + pad->gc_triggers[0] = state->trigger_l; + if (pad->gc_triggers[1] < state->trigger_r) + pad->gc_triggers[1] = state->trigger_r; +} + +void padUpdate(PadState* pad) { + pad->active_id_mask = 0; + pad->active_handheld = false; + pad->style_set = 0; + pad->attributes = 0; + pad->buttons_old = pad->buttons_cur; + pad->buttons_cur = 0; + pad->sticks[0] = pad->sticks[1] = (HidAnalogStickState){ 0, 0 }; + pad->gc_triggers[0] = pad->gc_triggers[1] = 0; + + if (pad->read_handheld) { + HidNpadCommonState state; + if (hidGetNpadStatesHandheld(HidNpadIdType_Handheld, &state, 1) && (state.attributes & HidNpadAttribute_IsConnected)) { + pad->active_handheld = true; + pad->style_set |= HidNpadStyleTag_NpadHandheld; + _padUpdateWithCommonState(pad, &state); + } + } + + u32 id_mask = pad->id_mask; + while (id_mask) { + u32 i = __builtin_ffs(id_mask)-1; + HidNpadIdType id = HidNpadIdType_No1 + i; + id_mask &= ~BIT(i); + + u32 style_set = hidGetNpadStyleSet(id); + if (style_set == 0) + continue; + + if (style_set & HidNpadStyleTag_NpadGc) { + HidNpadGcState state; + if (hidGetNpadStatesGc(id, &state, 1) && (state.attributes & HidNpadAttribute_IsConnected)) { + pad->active_id_mask |= BIT(i); + pad->style_set |= style_set; + _padUpdateWithGcState(pad, &state); + } + } + else { + HidNpadCommonState state; + size_t count = 0; + + if (style_set & HidNpadStyleTag_NpadSystemExt) + count = hidGetNpadStatesSystemExt(id, &state, 1); + else if (style_set & HidNpadStyleTag_NpadFullKey) + count = hidGetNpadStatesFullKey(id, &state, 1); + else if (style_set & HidNpadStyleTag_NpadJoyDual) + count = hidGetNpadStatesJoyDual(id, &state, 1); + else if (style_set & HidNpadStyleTag_NpadJoyLeft) + count = hidGetNpadStatesJoyLeft(id, &state, 1); + else if (style_set & HidNpadStyleTag_NpadJoyRight) + count = hidGetNpadStatesJoyRight(id, &state, 1); + + if (count != 0 && (state.attributes & HidNpadAttribute_IsConnected)) { + pad->active_id_mask |= BIT(i); + pad->style_set |= style_set; + _padUpdateWithCommonState(pad, &state); + } + } + } +} + +void padRepeaterUpdate(PadRepeater* r, u64 button_mask) { + if (button_mask != r->button_mask) { + r->button_mask = button_mask; + r->counter = -r->delay; + } + else if (button_mask != 0) { + r->counter++; + if (r->counter >= r->repeat) + r->counter = 0; + } +}