Move tap delay to joystick variant

- Keypad variant already has a key repeat threshold
    - Improved variant touch lifecycle naming
This commit is contained in:
Aaron Culliney 2015-09-02 22:47:48 -07:00
parent f046af0bef
commit 287df44133
4 changed files with 224 additions and 160 deletions

View File

@ -15,13 +15,6 @@
#error this is a touch interface module, possibly you mean to not compile this at all?
#endif
#define DEBUG_TOUCH_JOY 0
#if DEBUG_TOUCH_JOY
# define TOUCH_JOY_LOG(...) LOG(__VA_ARGS__)
#else
# define TOUCH_JOY_LOG(...)
#endif
#define MODEL_DEPTH -1/32.f
#define TRACKING_NONE (-1)
@ -48,7 +41,6 @@
#define BUTTON_OBJ_HALF_H (BUTTON_OBJ_H/2.f)
#define BUTTON_SWITCH_THRESHOLD_DEFAULT 22
#define BUTTON_TAP_DELAY_NANOS_DEFAULT 50000000
GLTouchJoyGlobals joyglobals = { 0 };
GLTouchJoyAxes axes = { 0 };
@ -199,76 +191,6 @@ static inline void _setup_button_object_with_char(char newChar) {
// ----------------------------------------------------------------------------
// Tap Delay Thread : delays processing of touch-down so that a different joystick button/key can be fired
static inline void _signal_tap_delay(void) {
pthread_mutex_lock(&buttons.tapDelayMutex);
pthread_cond_signal(&buttons.tapDelayCond);
pthread_mutex_unlock(&buttons.tapDelayMutex);
}
static void *_button_tap_delayed_thread(void *dummyptr) {
LOG(">>> [DELAYEDTAP] thread start ...");
pthread_mutex_lock(&buttons.tapDelayMutex);
do {
pthread_cond_wait(&buttons.tapDelayCond, &buttons.tapDelayMutex);
TOUCH_JOY_LOG(">>> [DELAYEDTAP] begin ...");
if (UNLIKELY(joyglobals.isShuttingDown)) {
break;
}
struct timespec ts = { .tv_sec=0, .tv_nsec=buttons.tapDelayNanos };
// sleep for the configured delay time
pthread_mutex_unlock(&buttons.tapDelayMutex);
nanosleep(&ts, NULL);
pthread_mutex_lock(&buttons.tapDelayMutex);
// wait until touch up/cancel
do {
// now set the emulator's joystick button values (or keypad value)
uint8_t displayChar = variant.curr->buttonPress();
_setup_button_object_with_char(displayChar);
if ( (buttons.trackingIndex == TRACKING_NONE) || joyglobals.isShuttingDown) {
break;
}
pthread_cond_wait(&buttons.tapDelayCond, &buttons.tapDelayMutex);
if ( (buttons.trackingIndex == TRACKING_NONE) || joyglobals.isShuttingDown) {
break;
}
TOUCH_JOY_LOG(">>> [DELAYEDTAP] looping ...");
} while (1);
if (UNLIKELY(joyglobals.isShuttingDown)) {
break;
}
// delay the ending of button tap or touch/move event by the configured delay time
pthread_mutex_unlock(&buttons.tapDelayMutex);
nanosleep(&ts, NULL);
pthread_mutex_lock(&buttons.tapDelayMutex);
variant.curr->buttonRelease();
TOUCH_JOY_LOG(">>> [DELAYEDTAP] end ...");
} while (1);
pthread_mutex_unlock(&buttons.tapDelayMutex);
LOG(">>> [DELAYEDTAP] thread exit ...");
return NULL;
}
// ----------------------------------------------------------------------------
static inline void resetState() {
LOG("%s", "");
axes.trackingIndex = TRACKING_NONE;
@ -286,8 +208,6 @@ static void gltouchjoy_setup(void) {
mdlDestroyModel(&buttons.model);
joyglobals.isShuttingDown = false;
assert((buttons.tapDelayThreadId == 0) && "setup called multiple times!");
pthread_create(&buttons.tapDelayThreadId, NULL, (void *)&_button_tap_delayed_thread, (void *)NULL);
axes.model = mdlCreateQuad(-1.05, -1.0, AXIS_OBJ_W, AXIS_OBJ_H, MODEL_DEPTH, AXIS_FB_WIDTH, AXIS_FB_HEIGHT, (GLCustom){
.create = &_create_touchjoy_hud,
@ -319,6 +239,9 @@ static void gltouchjoy_setup(void) {
return;
}
variant.joys->setup(&_setup_button_object_with_char);
variant.kpad->setup(&_setup_button_object_with_char);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
axes.timingBegin = now;
@ -336,15 +259,10 @@ static void gltouchjoy_shutdown(void) {
resetState();
joyglobals.isAvailable = false;
joyglobals.isShuttingDown = true;
pthread_cond_signal(&buttons.tapDelayCond);
if (pthread_join(buttons.tapDelayThreadId, NULL)) {
ERRLOG("OOPS: pthread_join tap delay thread ...");
}
buttons.tapDelayThreadId = 0;
buttons.tapDelayMutex = (pthread_mutex_t){ 0 };
buttons.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
variant.joys->shutdown();
variant.kpad->shutdown();
mdlDestroyModel(&axes.model);
mdlDestroyModel(&buttons.model);
@ -495,9 +413,6 @@ static inline void _axis_touch_down(int x, int y) {
}
static inline void _button_touch_down(int x, int y) {
variant.curr->setCurrButtonValue(buttons.touchDownChar, buttons.touchDownScancode);
_signal_tap_delay();
buttons.centerX = x;
buttons.centerY = y;
@ -505,11 +420,12 @@ static inline void _button_touch_down(int x, int y) {
buttons.modelDirty = true;
TOUCH_JOY_LOG("---TOUCH %sDOWN (buttons index %d) center:(%d,%d) -> buttons(0x%02X,0x%02X)", (action == TOUCH_DOWN ? "" : "POINTER "), buttons.trackingIndex, buttons.centerX, buttons.centerY, joy_button0, joy_button1);
variant.curr->buttonDown();
}
static inline void _axis_move(int x, int y) {
x = (x - axes.centerX);
y = (y - axes.centerY);
x -= axes.centerX;
y -= axes.centerY;
TOUCH_JOY_LOG("---TOUCH MOVE ...tracking axis:%d (%d,%d) -> joy(0x%02X,0x%02X)", axes.trackingIndex, x, y, joy_x, joy_y);
variant.curr->axisMove(x, y);
}
@ -517,21 +433,8 @@ static inline void _axis_move(int x, int y) {
static inline void _button_move(int x, int y) {
x -= buttons.centerX;
y -= buttons.centerY;
if ((y < -joyglobals.switchThreshold) || (y > joyglobals.switchThreshold)) {
touchjoy_button_type_t theButtonChar = -1;
int theButtonScancode = -1;
if (y < 0) {
theButtonChar = buttons.northChar;
theButtonScancode = buttons.northScancode;
} else {
theButtonChar = buttons.southChar;
theButtonScancode = buttons.southScancode;
}
variant.curr->setCurrButtonValue(theButtonChar, theButtonScancode);
_signal_tap_delay();
TOUCH_JOY_LOG("+++TOUCH MOVE ...tracking button:%d (%d,%d) -> buttons(0x%02X,0x%02X)", buttons.trackingIndex, x, y, joy_button0, joy_button1);
}
TOUCH_JOY_LOG("+++TOUCH MOVE ...tracking button:%d (%d,%d) -> buttons(0x%02X,0x%02X)", buttons.trackingIndex, x, y, joy_button0, joy_button1);
variant.curr->buttonMove(x, y);
}
static inline void _axis_touch_up(int x, int y) {
@ -544,8 +447,8 @@ static inline void _axis_touch_up(int x, int y) {
TOUCH_JOY_LOG("---TOUCH %sUP (axis went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset buttons index!)" : ""));
#endif
LOG("%s", "");
x = (x - axes.centerX);
y = (y - axes.centerY);
x -= axes.centerX;
y -= axes.centerY;
if (buttons.trackingIndex > axes.trackingIndex) {
LOG("!!! : DECREMENTING buttons.trackingIndex");
--buttons.trackingIndex;
@ -554,7 +457,7 @@ static inline void _axis_touch_up(int x, int y) {
axes.trackingIndex = TRACKING_NONE;
}
static inline void _button_touch_up(void) {
static inline void _button_touch_up(int x, int y) {
#if DEBUG_TOUCH_JOY
bool resetIndex = false;
if (axes.trackingIndex > buttons.trackingIndex) {
@ -564,12 +467,14 @@ static inline void _button_touch_up(void) {
TOUCH_JOY_LOG("---TOUCH %sUP (buttons went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset axis index!)" : ""));
#endif
LOG("%s", "");
x -= buttons.centerX;
y -= buttons.centerY;
if (axes.trackingIndex > buttons.trackingIndex) {
LOG("!!! : DECREMENTING axes.trackingIndex");
--axes.trackingIndex;
}
variant.curr->buttonUp(x, y);
buttons.trackingIndex = TRACKING_NONE;
_signal_tap_delay();
}
@ -648,7 +553,9 @@ static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int point
int y = (int)y_coords[axes.trackingIndex];
_axis_touch_up(x, y);
} else if (pointer_idx == buttons.trackingIndex) {
_button_touch_up();
int x = (int)x_coords[buttons.trackingIndex];
int y = (int)y_coords[buttons.trackingIndex];
_button_touch_up(x, y);
} else {
if (pointer_count == 1) {
LOG("!!! : RESETTING TOUCH JOYSTICK STATE MACHINE");
@ -764,16 +671,6 @@ static void gltouchjoy_setTouchButtonTypes(
_setup_button_object_with_char(currButtonDisplayChar);
}
static void gltouchjoy_setTapDelay(float secs) {
if (UNLIKELY(secs < 0.f)) {
ERRLOG("Clamping tap delay to 0.0 secs");
}
if (UNLIKELY(secs > 1.f)) {
ERRLOG("Clamping tap delay to 1.0 secs");
}
buttons.tapDelayNanos = (unsigned int)((float)NANOSECONDS_PER_SECOND * secs);
}
static void gltouchjoy_setTouchAxisSensitivity(float multiplier) {
axes.multiplier = multiplier;
}
@ -883,11 +780,6 @@ static void _init_gltouchjoy(void) {
buttons.activeChar = MOUSETEXT_OPENAPPLE;
buttons.tapDelayThreadId = 0;
buttons.tapDelayMutex = (pthread_mutex_t){ 0 };
buttons.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
buttons.tapDelayNanos = BUTTON_TAP_DELAY_NANOS_DEFAULT;
joyglobals.isEnabled = true;
joyglobals.ownsScreen = true;
joyglobals.showControls = true;
@ -904,7 +796,6 @@ static void _init_gltouchjoy(void) {
joydriver_ownsScreen = &gltouchjoy_ownsScreen;
joydriver_setShowControls = &gltouchjoy_setShowControls;
joydriver_setTouchButtonTypes = &gltouchjoy_setTouchButtonTypes;
joydriver_setTapDelay = &gltouchjoy_setTapDelay;
joydriver_setTouchAxisSensitivity = &gltouchjoy_setTouchAxisSensitivity;
joydriver_setButtonSwitchThreshold = &gltouchjoy_setButtonSwitchThreshold;
joydriver_setTouchVariant = &gltouchjoy_setTouchVariant;

View File

@ -17,6 +17,13 @@
#include "video/glhudmodel.h"
#include "video/glnode.h"
#define DEBUG_TOUCH_JOY 0
#if DEBUG_TOUCH_JOY
# define TOUCH_JOY_LOG(...) LOG(__VA_ARGS__)
#else
# define TOUCH_JOY_LOG(...)
#endif
// globals
typedef struct GLTouchJoyGlobals {
@ -75,23 +82,23 @@ typedef struct GLTouchJoyButtons {
int trackingIndex;
struct timespec timingBegin;
pthread_t tapDelayThreadId;
pthread_mutex_t tapDelayMutex;
pthread_cond_t tapDelayCond;
unsigned int tapDelayNanos;
} GLTouchJoyButtons;
extern GLTouchJoyButtons buttons;
typedef struct GLTouchJoyVariant {
touchjoy_variant_t (*variant)(void);
void (*resetState)(void);
void (*setCurrButtonValue)(touchjoy_button_type_t theButtonChar, int theButtonScancode);
uint8_t (*buttonPress)(void);
void (*buttonRelease)(void);
void (*axisDown)(void);
void (*axisMove)(int dx, int dy);
void (*axisUp)(int dx, int dy);
void (*resetState)(void);
void (*setup)(void (*buttonDrawCallback)(char newChar));
void (*shutdown)(void);
void (*buttonDown)(void);
void (*buttonMove)(int dx, int dy);
void (*buttonUp)(int dx, int dy);
void (*axisDown)(void);
void (*axisMove)(int dx, int dy);
void (*axisUp)(int dx, int dy);
} GLTouchJoyVariant;
// registers a touch joystick variant with manager

View File

@ -15,14 +15,25 @@
#error this is a touch interface module, possibly you mean to not compile this at all?
#endif
#define BUTTON_TAP_DELAY_NANOS_DEFAULT 50000000
static GLTouchJoyVariant happyHappyJoyJoy = { 0 };
static struct {
uint8_t currJoyButtonValue0;
uint8_t currJoyButtonValue1;
uint8_t currButtonDisplayChar;
void (*buttonDrawCallback)(char newChar);
bool trackingButton;
pthread_t tapDelayThreadId;
pthread_mutex_t tapDelayMutex;
pthread_cond_t tapDelayCond;
unsigned int tapDelayNanos;
} joys;
// ----------------------------------------------------------------------------
static touchjoy_variant_t touchjoy_variant(void) {
return EMULATED_JOYSTICK;
}
@ -45,6 +56,93 @@ static void touchjoy_resetState(void) {
_reset_buttons_state();
}
// ----------------------------------------------------------------------------
// Tap Delay Thread : delays processing of touch-down so that a different joystick button can be fired
static inline void _signal_tap_delay(void) {
pthread_mutex_lock(&joys.tapDelayMutex);
pthread_cond_signal(&joys.tapDelayCond);
pthread_mutex_unlock(&joys.tapDelayMutex);
}
static void *_button_tap_delayed_thread(void *dummyptr) {
LOG(">>> [DELAYEDTAP] thread start ...");
pthread_mutex_lock(&joys.tapDelayMutex);
do {
pthread_cond_wait(&joys.tapDelayCond, &joys.tapDelayMutex);
TOUCH_JOY_LOG(">>> [DELAYEDTAP] begin ...");
if (UNLIKELY(joyglobals.isShuttingDown)) {
break;
}
struct timespec ts = { .tv_sec=0, .tv_nsec=joys.tapDelayNanos };
// sleep for the configured delay time
pthread_mutex_unlock(&joys.tapDelayMutex);
nanosleep(&ts, NULL);
pthread_mutex_lock(&joys.tapDelayMutex);
// wait until touch up/cancel
do {
// now set the joystick button values
joy_button0 = joys.currJoyButtonValue0;
joy_button1 = joys.currJoyButtonValue1;
joys.buttonDrawCallback(joys.currButtonDisplayChar);
if (!joys.trackingButton || joyglobals.isShuttingDown) {
break;
}
pthread_cond_wait(&joys.tapDelayCond, &joys.tapDelayMutex);
if (!joys.trackingButton || joyglobals.isShuttingDown) {
break;
}
TOUCH_JOY_LOG(">>> [DELAYEDTAP] looping ...");
} while (1);
if (UNLIKELY(joyglobals.isShuttingDown)) {
break;
}
// delay the ending of button tap or touch/move event by the configured delay time
pthread_mutex_unlock(&joys.tapDelayMutex);
nanosleep(&ts, NULL);
pthread_mutex_lock(&joys.tapDelayMutex);
_reset_buttons_state();
TOUCH_JOY_LOG(">>> [DELAYEDTAP] end ...");
} while (1);
pthread_mutex_unlock(&joys.tapDelayMutex);
LOG(">>> [DELAYEDTAP] thread exit ...");
return NULL;
}
static void touchjoy_setup(void (*buttonDrawCallback)(char newChar)) {
assert((joys.tapDelayThreadId == 0) && "setup called multiple times!");
joys.buttonDrawCallback = buttonDrawCallback;
pthread_create(&joys.tapDelayThreadId, NULL, (void *)&_button_tap_delayed_thread, (void *)NULL);
}
static void touchjoy_shutdown(void) {
pthread_cond_signal(&joys.tapDelayCond);
if (pthread_join(joys.tapDelayThreadId, NULL)) {
ERRLOG("OOPS: pthread_join tap delay thread ...");
}
joys.tapDelayThreadId = 0;
joys.tapDelayMutex = (pthread_mutex_t){ 0 };
joys.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
}
// ----------------------------------------------------------------------------
// axis state
@ -85,7 +183,7 @@ static void touchjoy_axisUp(int x, int y) {
// ----------------------------------------------------------------------------
// button state
static void touchjoy_setCurrButtonValue(touchjoy_button_type_t theButtonChar, int theButtonScancode) {
static void _set_current_button_state(touchjoy_button_type_t theButtonChar, int theButtonScancode) {
if (theButtonChar == TOUCH_BUTTON0) {
joys.currJoyButtonValue0 = 0x80;
joys.currJoyButtonValue1 = 0;
@ -105,14 +203,42 @@ static void touchjoy_setCurrButtonValue(touchjoy_button_type_t theButtonChar, in
}
}
static uint8_t touchjoy_buttonPress(void) {
joy_button0 = joys.currJoyButtonValue0;
joy_button1 = joys.currJoyButtonValue1;
return joys.currButtonDisplayChar;
static void touchjoy_buttonDown(void) {
_set_current_button_state(buttons.touchDownChar, buttons.touchDownScancode);
joys.trackingButton = true;
_signal_tap_delay();
}
static void touchjoy_buttonRelease(void) {
_reset_buttons_state();
static void touchjoy_buttonMove(int dx, int dy) {
if ((dy < -joyglobals.switchThreshold) || (dy > joyglobals.switchThreshold)) {
touchjoy_button_type_t theButtonChar = -1;
int theButtonScancode = -1;
if (dy < 0) {
theButtonChar = buttons.northChar;
theButtonScancode = buttons.northScancode;
} else {
theButtonChar = buttons.southChar;
theButtonScancode = buttons.southScancode;
}
_set_current_button_state(theButtonChar, theButtonScancode);
_signal_tap_delay();
}
}
static void touchjoy_buttonUp(int dx, int dy) {
joys.trackingButton = false;
_signal_tap_delay();
}
static void gltouchjoy_setTapDelay(float secs) {
if (UNLIKELY(secs < 0.f)) {
ERRLOG("Clamping tap delay to 0.0 secs");
}
if (UNLIKELY(secs > 1.f)) {
ERRLOG("Clamping tap delay to 1.0 secs");
}
joys.tapDelayNanos = (unsigned int)((float)NANOSECONDS_PER_SECOND * secs);
}
// ----------------------------------------------------------------------------
@ -123,15 +249,23 @@ static void _init_gltouchjoy_joy(void) {
happyHappyJoyJoy.variant = &touchjoy_variant,
happyHappyJoyJoy.resetState = &touchjoy_resetState,
happyHappyJoyJoy.setup = &touchjoy_setup,
happyHappyJoyJoy.shutdown = &touchjoy_shutdown,
happyHappyJoyJoy.setCurrButtonValue = &touchjoy_setCurrButtonValue,
happyHappyJoyJoy.buttonPress = &touchjoy_buttonPress,
happyHappyJoyJoy.buttonRelease = &touchjoy_buttonRelease,
happyHappyJoyJoy.buttonDown = &touchjoy_buttonDown,
happyHappyJoyJoy.buttonMove = &touchjoy_buttonMove,
happyHappyJoyJoy.buttonUp = &touchjoy_buttonUp,
happyHappyJoyJoy.axisDown = &touchjoy_axisDown,
happyHappyJoyJoy.axisMove = &touchjoy_axisMove,
happyHappyJoyJoy.axisUp = &touchjoy_axisUp,
joys.tapDelayMutex = (pthread_mutex_t){ 0 };
joys.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
joys.tapDelayNanos = BUTTON_TAP_DELAY_NANOS_DEFAULT;
joydriver_setTapDelay = &gltouchjoy_setTapDelay;
gltouchjoy_registerVariant(EMULATED_JOYSTICK, &happyHappyJoyJoy);
}

View File

@ -50,6 +50,7 @@ static struct {
keypad_octant_t axisCurrentOctant;
uint8_t currButtonDisplayChar;
void (*buttonDrawCallback)(char newChar);
// index of repeating scancodes to fire
keypad_fire_t fireIdx;
@ -162,6 +163,10 @@ static void touchkpad_keyboardReadCallback(void) {
}
}
if (fired == REPEAT_BUTTON) {
kpad.buttonDrawCallback(kpad.currButtonDisplayChar);
}
bool lockedAxis = _callback_sourceTryLock(&kpad.axisLock);
if (lockedAxis) {
if (fired == REPEAT_AXIS || fired == REPEAT_AXIS_ALT) {
@ -227,6 +232,14 @@ static void touchkpad_resetState(void) {
c_keys_handle_input(buttons.southScancode, /*pressed:*/false, /*ASCII:*/false);
}
static void touchkpad_setup(void (*buttonDrawCallback)(char newChar)) {
kpad.buttonDrawCallback = buttonDrawCallback;
}
static void touchkpad_shutdown(void) {
// ...
}
// ----------------------------------------------------------------------------
// axis key(s) state
@ -465,7 +478,7 @@ static void touchkpad_axisUp(int dx, int dy) {
// ----------------------------------------------------------------------------
// button key state
static void touchkpad_setCurrButtonValue(touchjoy_button_type_t theButtonChar, int theButtonScancode) {
static void _set_current_button_state(touchjoy_button_type_t theButtonChar, int theButtonScancode) {
LOG("%s", "");
if (theButtonChar >= 0) {
kpad.currButtonDisplayChar = theButtonChar;
@ -476,23 +489,40 @@ static void touchkpad_setCurrButtonValue(touchjoy_button_type_t theButtonChar, i
}
}
static uint8_t touchkpad_buttonPress(void) {
LOG("%s", "");
static void touchkpad_buttonDown(void) {
if (!kpad.buttonBegan) {
// avoid multiple locks on extra buttonPress()
kpad.buttonBegan = true;
_touch_sourceBegin(&kpad.buttonLock);
}
_set_current_button_state(buttons.touchDownChar, buttons.touchDownScancode);
if (kpad.scancodes[REPEAT_BUTTON] >= 0) {
LOG("->BUTT : %d/'%c'", kpad.scancodes[REPEAT_BUTTON], kpad.currButtonDisplayChar);
clock_gettime(CLOCK_MONOTONIC, &kpad.timingBegins[REPEAT_BUTTON]);
keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback;
}
return kpad.currButtonDisplayChar;
}
static void touchkpad_buttonRelease(void) {
static void touchkpad_buttonMove(int dx, int dy) {
// Currently this is the same logic as the "regular" joystick variant ... but we could likely be more creative here
// in a future revision, like having a full octant of key possibilities ... (for example, playing Bolo with a friend
// on the same tablet, one driving, the other shooting and controlling the turret)
if ((dy < -joyglobals.switchThreshold) || (dy > joyglobals.switchThreshold)) {
touchjoy_button_type_t theButtonChar = -1;
int theButtonScancode = -1;
if (dy < 0) {
theButtonChar = buttons.northChar;
theButtonScancode = buttons.northScancode;
} else {
theButtonChar = buttons.southChar;
theButtonScancode = buttons.southScancode;
}
_set_current_button_state(theButtonChar, theButtonScancode);
}
}
static void touchkpad_buttonUp(int dx, int dy) {
LOG("%s", "");
touchkpad_buttonMove(dx, dy);
kpad.timingBegins[REPEAT_BUTTON] = (struct timespec){ 0 };
if (kpad.buttonBegan) {
kpad.buttonBegan = false;
@ -521,10 +551,12 @@ static void _init_gltouchjoy_kpad(void) {
kpadJoy.variant = &touchkpad_variant,
kpadJoy.resetState = &touchkpad_resetState,
kpadJoy.setup = &touchkpad_setup,
kpadJoy.shutdown = &touchkpad_shutdown,
kpadJoy.setCurrButtonValue = &touchkpad_setCurrButtonValue,
kpadJoy.buttonPress = &touchkpad_buttonPress,
kpadJoy.buttonRelease = &touchkpad_buttonRelease,
kpadJoy.buttonDown = &touchkpad_buttonDown,
kpadJoy.buttonMove = &touchkpad_buttonMove,
kpadJoy.buttonUp = &touchkpad_buttonUp,
kpadJoy.axisDown = &touchkpad_axisDown,
kpadJoy.axisMove = &touchkpad_axisMove,