Add pad.h - new wrapper API for reading controller input

This commit is contained in:
fincs 2020-12-01 12:52:37 +01:00
parent 5551d42b29
commit d290697bfd
No known key found for this signature in database
GPG Key ID: 62C7609ADA219C60
3 changed files with 352 additions and 0 deletions

View File

@ -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"

View File

@ -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;
}

127
nx/source/runtime/pad.c Normal file
View File

@ -0,0 +1,127 @@
#include <string.h>
#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;
}
}