2015-08-16 05:02:51 +00:00
|
|
|
/*
|
2015-10-22 05:13:26 +00:00
|
|
|
* Apple // emulator for *ix
|
2015-08-16 05:02:51 +00:00
|
|
|
*
|
|
|
|
* This software package is subject to the GNU General Public License
|
2015-10-22 05:13:26 +00:00
|
|
|
* version 3 or later (your choice) as published by the Free Software
|
2015-08-16 05:02:51 +00:00
|
|
|
* Foundation.
|
|
|
|
*
|
2015-10-22 05:13:26 +00:00
|
|
|
* Copyright 2013-2015 Aaron Culliney
|
2015-08-16 05:02:51 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "video/gltouchjoy.h"
|
|
|
|
|
|
|
|
#if !INTERFACE_TOUCH
|
|
|
|
#error this is a touch interface module, possibly you mean to not compile this at all?
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define KEY_REPEAT_THRESHOLD_NANOS (NANOSECONDS_PER_SECOND / 4)
|
2015-08-30 20:38:23 +00:00
|
|
|
#define CALLBACK_LOCK_VALUE (1000)
|
2015-08-16 05:02:51 +00:00
|
|
|
|
|
|
|
// WARNING : this does not match the rosette left-right-down layout, but what is returned by atan2f()
|
|
|
|
typedef enum keypad_octant_t {
|
|
|
|
OCTANT_WEST = 0,
|
|
|
|
OCTANT_NORTHWEST,
|
|
|
|
OCTANT_NORTH,
|
|
|
|
OCTANT_NORTHEAST,
|
|
|
|
OCTANT_EAST,
|
|
|
|
OCTANT_SOUTHEAST,
|
|
|
|
OCTANT_SOUTH,
|
|
|
|
OCTANT_SOUTHWEST,
|
|
|
|
ORIGIN,
|
|
|
|
} keypad_octant_t;
|
|
|
|
|
|
|
|
// Assuming radian value between -PI <-> PI
|
|
|
|
#define RADIANS_NORTHWEST ((-3.f * M_PI) / 4.f)
|
|
|
|
#define RADIANS_NORTH ((-1.f * M_PI) / 2.f)
|
|
|
|
#define RADIANS_NORTHEAST ((-1.f * M_PI) / 4.f)
|
2015-09-04 05:24:06 +00:00
|
|
|
#define RADIANS_WEST_NEG (-M_PI)
|
|
|
|
#define RADIANS_EAST (0.f)
|
2015-08-16 05:02:51 +00:00
|
|
|
#define RADIANS_SOUTHWEST (( 3.f * M_PI) / 4.f)
|
|
|
|
#define RADIANS_SOUTH (( 1.f * M_PI) / 2.f)
|
|
|
|
#define RADIANS_SOUTHEAST (( 1.f * M_PI) / 4.f)
|
|
|
|
|
|
|
|
typedef enum keypad_fire_t {
|
|
|
|
REPEAT_AXIS = 0,
|
|
|
|
REPEAT_AXIS_ALT,
|
|
|
|
REPEAT_BUTTON,
|
|
|
|
MAX_REPEATING,
|
|
|
|
} keypad_fire_t;
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
keypad_octant_t axisCurrentOctant;
|
|
|
|
|
|
|
|
uint8_t currButtonDisplayChar;
|
2015-09-03 05:47:48 +00:00
|
|
|
void (*buttonDrawCallback)(char newChar);
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2015-08-30 20:38:23 +00:00
|
|
|
// index of repeating scancodes to fire
|
2015-08-16 05:02:51 +00:00
|
|
|
keypad_fire_t fireIdx;
|
2015-08-30 20:38:23 +00:00
|
|
|
|
2015-09-03 05:41:05 +00:00
|
|
|
volatile int axisLock; // modified by both CPU thread and touch handling thread
|
|
|
|
volatile int buttonLock; // modified by both CPU thread and touch handling thread
|
2015-08-30 20:38:23 +00:00
|
|
|
|
2015-08-16 05:02:51 +00:00
|
|
|
int scancodes[MAX_REPEATING];
|
|
|
|
struct timespec timingBegins[MAX_REPEATING];
|
2015-08-30 20:38:23 +00:00
|
|
|
bool buttonBegan;
|
|
|
|
bool axisBegan;
|
|
|
|
int lastScancode;
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2015-08-21 05:15:56 +00:00
|
|
|
float repeatThresholdNanos;
|
|
|
|
|
2016-04-06 05:04:57 +00:00
|
|
|
// axis rosette
|
|
|
|
uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS];
|
|
|
|
long rosetteScancodes[ROSETTE_ROWS * ROSETTE_COLS];
|
|
|
|
|
|
|
|
// touch/swipe buttons
|
|
|
|
touchjoy_button_type_t touchDownChar;
|
|
|
|
int touchDownScancode;
|
|
|
|
touchjoy_button_type_t northChar;
|
|
|
|
int northScancode;
|
|
|
|
touchjoy_button_type_t southChar;
|
|
|
|
int southScancode;
|
|
|
|
|
2015-08-16 05:02:51 +00:00
|
|
|
} kpad = { 0 };
|
|
|
|
|
|
|
|
static GLTouchJoyVariant kpadJoy = { 0 };
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
2015-08-30 20:38:23 +00:00
|
|
|
// repeat key callback scheduling and unscheduling
|
|
|
|
//
|
|
|
|
// Assumptions :
|
|
|
|
// - All touch sources run on a single thread
|
|
|
|
// - callback itself runs on CPU thread
|
|
|
|
|
|
|
|
// unlock callback section
|
2015-09-03 05:41:05 +00:00
|
|
|
static inline void _callback_sourceUnlock(volatile int *source) {
|
|
|
|
int val = __sync_add_and_fetch(source, (int)(CALLBACK_LOCK_VALUE));
|
2015-08-30 20:38:23 +00:00
|
|
|
assert(val >= 0 && "inconsistent lock state for callback detected");
|
2018-01-21 21:18:49 +00:00
|
|
|
(void)val;
|
2015-08-30 20:38:23 +00:00
|
|
|
}
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2015-08-30 20:38:23 +00:00
|
|
|
// attempt to lock the critical section where we unschedule the function pointer callback
|
|
|
|
// there should be no outstanding touch sources being tracked
|
2015-09-03 05:41:05 +00:00
|
|
|
static inline bool _callback_sourceTryLock(volatile int *source) {
|
|
|
|
int val = __sync_sub_and_fetch(source, (int)(CALLBACK_LOCK_VALUE));
|
2015-08-30 20:38:23 +00:00
|
|
|
if (val == -CALLBACK_LOCK_VALUE) {
|
|
|
|
return true;
|
|
|
|
} else {
|
2015-09-03 05:41:05 +00:00
|
|
|
_callback_sourceUnlock(source);
|
2015-08-30 20:38:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2015-08-30 20:38:23 +00:00
|
|
|
// touch source has ended
|
2015-09-03 05:41:05 +00:00
|
|
|
static inline void _touch_sourceEnd(volatile int *source) {
|
|
|
|
__sync_sub_and_fetch(source, 1);
|
2015-08-30 20:38:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// touch source has begun
|
2015-09-03 05:41:05 +00:00
|
|
|
static inline void _touch_sourceBegin(volatile int *source) {
|
2015-08-30 20:38:23 +00:00
|
|
|
do {
|
2015-09-03 05:41:05 +00:00
|
|
|
int val = __sync_add_and_fetch(source, 1);
|
2015-08-30 20:38:23 +00:00
|
|
|
if (val >= 0) {
|
|
|
|
assert(val > 0 && "inconsistent lock state for touch source detected");
|
2015-09-03 05:41:05 +00:00
|
|
|
return;
|
2015-08-30 20:38:23 +00:00
|
|
|
}
|
|
|
|
// spin waiting on callback critical
|
2015-09-03 05:41:05 +00:00
|
|
|
_touch_sourceEnd(source);
|
2015-08-30 20:38:23 +00:00
|
|
|
} while (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touchkpad_keyboardReadCallback(void) {
|
2018-08-07 14:57:51 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2015-09-04 05:23:06 +00:00
|
|
|
// HACK FIXME TODO :
|
2015-08-30 20:38:23 +00:00
|
|
|
//
|
2015-09-04 05:23:06 +00:00
|
|
|
// There are a number of cases where the emulated software is reading the keyboard state but not using the value.
|
|
|
|
// This is quite noticeable in a number of games that take keyboard input.
|
|
|
|
//
|
|
|
|
// This indicates that we are incorrectly emulating the keyboard hardware. The proper fix for this touch keypad
|
|
|
|
// joystick will be to properly emulate the original hardware timing, using the existing facility to count 65c02 CPU
|
|
|
|
// machine cyles.
|
2015-08-30 20:38:23 +00:00
|
|
|
#warning FIXME TODO : implement proper keyboard repeat callback timing
|
|
|
|
|
|
|
|
if (kpad.lastScancode >= 0) {
|
|
|
|
c_keys_handle_input(kpad.lastScancode, /*pressed:*/false, /*ASCII:*/false);
|
|
|
|
kpad.lastScancode = -1;
|
|
|
|
}
|
|
|
|
|
2015-08-16 05:02:51 +00:00
|
|
|
struct timespec now = { 0 };
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
|
2015-08-30 20:38:23 +00:00
|
|
|
int fired = -1;
|
2015-08-16 05:02:51 +00:00
|
|
|
for (unsigned int i=0; i<MAX_REPEATING; i++) {
|
|
|
|
int scancode = kpad.scancodes[kpad.fireIdx];
|
|
|
|
if (scancode >= 0) {
|
|
|
|
struct timespec deltat = timespec_diff(kpad.timingBegins[kpad.fireIdx], now, NULL);
|
2015-08-21 05:15:56 +00:00
|
|
|
if (deltat.tv_sec || deltat.tv_nsec > kpad.repeatThresholdNanos) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("ACTIVE(%d,%d) REPEAT #%d/%lu/%lu: %d", kpad.axisLock, kpad.buttonLock, kpad.fireIdx, deltat.tv_sec, deltat.tv_nsec, scancode);
|
2015-08-16 05:02:51 +00:00
|
|
|
c_keys_handle_input(scancode, /*pressed:*/true, /*ASCII:*/false);
|
2015-08-30 20:38:23 +00:00
|
|
|
kpad.lastScancode = scancode;
|
|
|
|
fired = kpad.fireIdx;
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
++kpad.fireIdx;
|
|
|
|
if (kpad.fireIdx >= MAX_REPEATING) {
|
|
|
|
kpad.fireIdx = 0;
|
|
|
|
}
|
2015-08-30 20:38:23 +00:00
|
|
|
if (fired >= 0) {
|
2015-08-16 05:02:51 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-03 05:41:05 +00:00
|
|
|
bool lockedAxis = _callback_sourceTryLock(&kpad.axisLock);
|
|
|
|
if (lockedAxis) {
|
|
|
|
if (fired == REPEAT_AXIS || fired == REPEAT_AXIS_ALT) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("RESETTING AXIS INDEX %d ...", fired);
|
2015-08-30 20:38:23 +00:00
|
|
|
kpad.scancodes[fired] = -1;
|
|
|
|
}
|
2015-09-03 05:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool lockedButton = _callback_sourceTryLock(&kpad.buttonLock);
|
|
|
|
if (lockedButton) {
|
|
|
|
if (fired == REPEAT_BUTTON) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("RESETTING BUTTON INDEX %d ...", fired);
|
2015-09-03 05:41:05 +00:00
|
|
|
kpad.scancodes[fired] = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lockedButton && lockedAxis && fired < 0) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("REPEAT KEY CALLBACK DONE ...");
|
2015-09-03 05:41:05 +00:00
|
|
|
keydriver_keyboardReadCallback = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lockedAxis) {
|
|
|
|
_callback_sourceUnlock(&kpad.axisLock);
|
|
|
|
}
|
|
|
|
if (lockedButton) {
|
|
|
|
_callback_sourceUnlock(&kpad.buttonLock);
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2016-04-06 05:04:57 +00:00
|
|
|
static interface_device_t touchkpad_variant(void) {
|
|
|
|
return TOUCH_DEVICE_JOYSTICK_KEYPAD;
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void touchkpad_resetState(void) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("...");
|
2015-09-03 05:41:05 +00:00
|
|
|
kpad.axisLock = 0;
|
|
|
|
kpad.buttonLock = 0;
|
2015-08-30 20:38:23 +00:00
|
|
|
keydriver_keyboardReadCallback = NULL;
|
|
|
|
|
2015-08-16 05:02:51 +00:00
|
|
|
kpad.axisCurrentOctant = ORIGIN;
|
2015-08-30 20:38:23 +00:00
|
|
|
kpad.lastScancode = -1;
|
2015-08-16 05:02:51 +00:00
|
|
|
|
|
|
|
for (unsigned int i=0; i<MAX_REPEATING; i++) {
|
|
|
|
kpad.scancodes[i] = -1;
|
2015-08-30 20:38:23 +00:00
|
|
|
kpad.timingBegins[i] = (struct timespec){ 0 };
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
kpad.currButtonDisplayChar = ' ';
|
2015-08-30 20:38:23 +00:00
|
|
|
kpad.axisBegan = false;
|
|
|
|
kpad.buttonBegan = false;
|
2015-08-16 05:02:51 +00:00
|
|
|
|
|
|
|
for (unsigned int i=0; i<ROSETTE_COLS; i++) {
|
|
|
|
for (unsigned int j=0; j<ROSETTE_ROWS; j++) {
|
2016-04-06 05:04:57 +00:00
|
|
|
c_keys_handle_input(kpad.rosetteScancodes[i], /*pressed:*/false, /*ASCII:*/false);
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-06 05:04:57 +00:00
|
|
|
c_keys_handle_input(kpad.touchDownScancode, /*pressed:*/false, /*ASCII:*/false);
|
|
|
|
c_keys_handle_input(kpad.northScancode, /*pressed:*/false, /*ASCII:*/false);
|
|
|
|
c_keys_handle_input(kpad.southScancode, /*pressed:*/false, /*ASCII:*/false);
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
|
2015-09-03 05:47:48 +00:00
|
|
|
static void touchkpad_setup(void (*buttonDrawCallback)(char newChar)) {
|
|
|
|
kpad.buttonDrawCallback = buttonDrawCallback;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touchkpad_shutdown(void) {
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
|
2015-08-16 05:02:51 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// axis key(s) state
|
|
|
|
|
|
|
|
static void touchkpad_axisDown(void) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("...");
|
2015-08-30 20:38:23 +00:00
|
|
|
if (!kpad.axisBegan) {
|
|
|
|
// avoid multiple locks on extra axisDown()
|
|
|
|
kpad.axisBegan = true;
|
2015-09-03 05:41:05 +00:00
|
|
|
_touch_sourceBegin(&kpad.axisLock);
|
2015-08-30 20:38:23 +00:00
|
|
|
}
|
2015-09-04 05:22:44 +00:00
|
|
|
|
|
|
|
keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback;
|
|
|
|
struct timespec now = { 0 };
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
kpad.timingBegins[REPEAT_AXIS] = now;
|
|
|
|
kpad.timingBegins[REPEAT_AXIS_ALT] = now;
|
|
|
|
|
2015-08-16 05:02:51 +00:00
|
|
|
kpad.axisCurrentOctant = ORIGIN;
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_CENTER] >= 0) {
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_CENTER];
|
2015-08-16 05:02:51 +00:00
|
|
|
kpad.scancodes[REPEAT_AXIS_ALT] = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touchkpad_axisMove(int dx, int dy) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("...");
|
2015-08-16 05:02:51 +00:00
|
|
|
|
|
|
|
if ((dx > -joyglobals.switchThreshold) && (dx < joyglobals.switchThreshold) && (dy > -joyglobals.switchThreshold) && (dy < joyglobals.switchThreshold)) {
|
2016-04-06 05:04:57 +00:00
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_CENTER];
|
2015-08-16 05:02:51 +00:00
|
|
|
kpad.scancodes[REPEAT_AXIS_ALT] = -1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dx == 0 && dy == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
// determine the octant of this dx/dy
|
|
|
|
|
|
|
|
keypad_octant_t axisLastOctant = kpad.axisCurrentOctant;
|
|
|
|
|
|
|
|
const float radians = atan2f(dy, dx); // -180-180deg range
|
|
|
|
float radnorm = radians + M_PI; // 0-360deg range
|
|
|
|
if (UNLIKELY(radnorm < 0.f)) {
|
|
|
|
radnorm = 0.f; // clamp positive
|
|
|
|
}
|
|
|
|
float octant = radnorm + (M_PI/8.f); // rotate to correct octant (+45deg)
|
|
|
|
octant /= (M_PI/4.f); // divide to octant (/90deg)
|
|
|
|
kpad.axisCurrentOctant = (keypad_octant_t) ((int)octant & 0x7);// integer modulo maps to enum
|
|
|
|
|
|
|
|
if (kpad.axisCurrentOctant == axisLastOctant) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("radians:%f radnorm:%f octant:%f, currOctant:%d", radians, radnorm, octant, kpad.axisCurrentOctant);
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2015-09-04 05:24:06 +00:00
|
|
|
// Current implementation NOTE : four cardinal directions are handled slightly different than the intercardinal
|
|
|
|
// ones.
|
|
|
|
// - The intercardinals might generate 2 scanscodes (for example north and west scancodes for a northwest axis)
|
|
|
|
// if there is not a specific scancode to handle it (e.g., the northwest scancode).
|
|
|
|
// - The cardinals will only ever generate one scancode (the cardinal in question if it's set, or the scancode
|
|
|
|
// of the adjacent intercardinal where the point lies).
|
2015-08-16 05:02:51 +00:00
|
|
|
kpad.scancodes[REPEAT_AXIS_ALT] = -1;
|
|
|
|
switch (kpad.axisCurrentOctant) {
|
|
|
|
case OCTANT_NORTHWEST:
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_NORTHWEST] >= 0) {
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST];
|
|
|
|
TOUCH_JOY_LOG("XY : NORTHWEST, (%d)", kpad.rosetteScancodes[ROSETTE_WEST]);
|
2015-08-16 05:02:51 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : WEST (%d) & NORTH (%d)", kpad.rosetteScancodes[ROSETTE_WEST], kpad.rosetteScancodes[ROSETTE_NORTH]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST];
|
|
|
|
kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_NORTH];
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OCTANT_NORTH:
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_NORTH] >= 0) {
|
|
|
|
TOUCH_JOY_LOG("Y : NORTH (%d)", kpad.rosetteScancodes[ROSETTE_NORTH]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTH];
|
2015-09-04 05:24:06 +00:00
|
|
|
} else if (radians < RADIANS_NORTH) {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : NORTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHWEST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST];
|
2015-09-04 05:24:06 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST];
|
2015-09-04 05:24:06 +00:00
|
|
|
}
|
2015-08-16 05:02:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case OCTANT_NORTHEAST:
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_NORTHEAST] >= 0) {
|
|
|
|
TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST];
|
2015-08-16 05:02:51 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : EAST (%d) & NORTH (%d)", kpad.rosetteScancodes[ROSETTE_EAST], kpad.rosetteScancodes[ROSETTE_NORTH]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST];
|
|
|
|
kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_NORTH];
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OCTANT_WEST:
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_WEST] >= 0) {
|
|
|
|
TOUCH_JOY_LOG("Y : WEST (%d)", kpad.rosetteScancodes[ROSETTE_WEST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST];
|
2015-09-04 05:24:06 +00:00
|
|
|
} else if (radians > RADIANS_WEST_NEG && radians < 0) {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : NORTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHWEST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST];
|
2015-09-04 05:24:06 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST];
|
2015-09-04 05:24:06 +00:00
|
|
|
}
|
2015-08-16 05:02:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case OCTANT_EAST:
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_EAST] >= 0) {
|
|
|
|
TOUCH_JOY_LOG("Y : EAST (%d)", kpad.rosetteScancodes[ROSETTE_EAST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST];
|
2015-09-04 05:24:06 +00:00
|
|
|
} else if (radians < RADIANS_EAST) {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST];
|
2015-09-04 05:24:06 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST];
|
2015-09-04 05:24:06 +00:00
|
|
|
}
|
2015-08-16 05:02:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case OCTANT_SOUTHWEST:
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_SOUTHWEST] >= 0) {
|
|
|
|
TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST];
|
2015-08-16 05:02:51 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : WEST (%d) & SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_WEST], kpad.rosetteScancodes[ROSETTE_SOUTH]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST];
|
|
|
|
kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_SOUTH];
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OCTANT_SOUTH:
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_SOUTH] >= 0) {
|
|
|
|
TOUCH_JOY_LOG("Y : SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_SOUTH]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTH];
|
2015-09-04 05:24:06 +00:00
|
|
|
} else if (radians > RADIANS_SOUTH) {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST];
|
2015-09-04 05:24:06 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST];
|
2015-09-04 05:24:06 +00:00
|
|
|
}
|
2015-08-16 05:02:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case OCTANT_SOUTHEAST:
|
2016-04-06 05:04:57 +00:00
|
|
|
if (kpad.rosetteScancodes[ROSETTE_SOUTHEAST] >= 0) {
|
|
|
|
TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST];
|
2015-08-16 05:02:51 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
TOUCH_JOY_LOG("XY : EAST (%d) & SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_EAST], kpad.rosetteScancodes[ROSETTE_SOUTH]);
|
|
|
|
kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST];
|
|
|
|
kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_SOUTH];
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
assert(false && "should not happen");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touchkpad_axisUp(int dx, int dy) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("...");
|
2015-08-16 05:02:51 +00:00
|
|
|
touchkpad_axisMove(dx, dy);
|
|
|
|
kpad.axisCurrentOctant = ORIGIN;
|
2015-08-30 20:38:23 +00:00
|
|
|
kpad.timingBegins[REPEAT_AXIS] = (struct timespec){ 0 };
|
|
|
|
kpad.timingBegins[REPEAT_AXIS_ALT] = (struct timespec){ 0 };
|
|
|
|
if (kpad.axisBegan) {
|
|
|
|
kpad.axisBegan = false;
|
2015-09-03 05:41:05 +00:00
|
|
|
_touch_sourceEnd(&kpad.axisLock);
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// button key state
|
|
|
|
|
2015-09-03 05:47:48 +00:00
|
|
|
static void _set_current_button_state(touchjoy_button_type_t theButtonChar, int theButtonScancode) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("...");
|
2015-08-16 05:02:51 +00:00
|
|
|
if (theButtonChar >= 0) {
|
|
|
|
kpad.currButtonDisplayChar = theButtonChar;
|
|
|
|
kpad.scancodes[REPEAT_BUTTON] = theButtonScancode;
|
|
|
|
} else {
|
|
|
|
kpad.currButtonDisplayChar = ' ';
|
|
|
|
kpad.scancodes[REPEAT_BUTTON] = -1;
|
|
|
|
}
|
2015-09-05 03:35:42 +00:00
|
|
|
kpad.buttonDrawCallback(kpad.currButtonDisplayChar);
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
|
2015-09-03 05:47:48 +00:00
|
|
|
static void touchkpad_buttonDown(void) {
|
2015-08-30 20:38:23 +00:00
|
|
|
if (!kpad.buttonBegan) {
|
|
|
|
kpad.buttonBegan = true;
|
2015-09-03 05:41:05 +00:00
|
|
|
_touch_sourceBegin(&kpad.buttonLock);
|
2015-08-30 20:38:23 +00:00
|
|
|
}
|
2016-04-06 05:04:57 +00:00
|
|
|
_set_current_button_state(kpad.touchDownChar, kpad.touchDownScancode);
|
2015-08-16 05:02:51 +00:00
|
|
|
if (kpad.scancodes[REPEAT_BUTTON] >= 0) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("->BUTT : %d/'%c'", kpad.scancodes[REPEAT_BUTTON], kpad.currButtonDisplayChar);
|
2015-08-16 05:02:51 +00:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &kpad.timingBegins[REPEAT_BUTTON]);
|
|
|
|
keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-03 05:47:48 +00:00
|
|
|
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) {
|
2016-04-06 05:04:57 +00:00
|
|
|
theButtonChar = kpad.northChar;
|
|
|
|
theButtonScancode = kpad.northScancode;
|
2015-09-03 05:47:48 +00:00
|
|
|
} else {
|
2016-04-06 05:04:57 +00:00
|
|
|
theButtonChar = kpad.southChar;
|
|
|
|
theButtonScancode = kpad.southScancode;
|
2015-09-03 05:47:48 +00:00
|
|
|
}
|
|
|
|
_set_current_button_state(theButtonChar, theButtonScancode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touchkpad_buttonUp(int dx, int dy) {
|
2015-09-16 03:39:30 +00:00
|
|
|
TOUCH_JOY_LOG("...");
|
2015-09-03 05:47:48 +00:00
|
|
|
touchkpad_buttonMove(dx, dy);
|
2015-08-30 20:38:23 +00:00
|
|
|
kpad.timingBegins[REPEAT_BUTTON] = (struct timespec){ 0 };
|
|
|
|
if (kpad.buttonBegan) {
|
|
|
|
kpad.buttonBegan = false;
|
2015-09-03 05:41:05 +00:00
|
|
|
_touch_sourceEnd(&kpad.buttonLock);
|
2015-08-30 20:38:23 +00:00
|
|
|
}
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
|
2016-04-06 05:04:57 +00:00
|
|
|
static void touchkpad_prefsChanged(const char *domain) {
|
|
|
|
assert(video_isRenderThread());
|
|
|
|
|
|
|
|
float fVal = 0.f;
|
|
|
|
long lVal = 0;
|
|
|
|
|
|
|
|
kpad.repeatThresholdNanos = prefs_parseFloatValue(domain, PREF_KPAD_REPEAT_THRESH, &fVal) ? fVal*NANOSECONDS_PER_SECOND : NANOSECONDS_PER_SECOND;
|
|
|
|
|
|
|
|
kpad.touchDownChar = prefs_parseLongValue (domain, PREF_KPAD_TOUCHDOWN_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_SPACE_VISUAL;
|
|
|
|
kpad.touchDownScancode = prefs_parseLongValue (domain, PREF_KPAD_TOUCHDOWN_SCAN, &lVal, /*base:*/10) ? lVal : c_keys_ascii_to_scancode(' ');
|
|
|
|
|
|
|
|
kpad.southChar = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_SOUTH_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_NONACTIONABLE;
|
|
|
|
kpad.southScancode = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_SOUTH_SCAN, &lVal, /*base:*/10) ? lVal : -1;
|
|
|
|
|
|
|
|
kpad.northChar = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_NORTH_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_NONACTIONABLE;
|
|
|
|
kpad.northScancode = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_NORTH_SCAN, &lVal, /*base:*/10) ? lVal : -1;
|
|
|
|
|
|
|
|
const unsigned long rosetteCount = ROSETTE_ROWS*ROSETTE_COLS;
|
|
|
|
|
|
|
|
do {
|
|
|
|
const int rosetteChars[ROSETTE_ROWS*ROSETTE_COLS] = {
|
|
|
|
ICONTEXT_NONACTIONABLE, 'I', ICONTEXT_NONACTIONABLE,
|
|
|
|
'J', ICONTEXT_NONACTIONABLE, 'K',
|
|
|
|
ICONTEXT_NONACTIONABLE, 'M', ICONTEXT_NONACTIONABLE,
|
|
|
|
};
|
|
|
|
const int rosetteScans[ROSETTE_ROWS*ROSETTE_COLS] = {
|
|
|
|
-1, c_keys_ascii_to_scancode('I'), -1,
|
|
|
|
c_keys_ascii_to_scancode('J'), -1, c_keys_ascii_to_scancode('K'),
|
|
|
|
-1, c_keys_ascii_to_scancode('M'), -1,
|
|
|
|
};
|
|
|
|
for (unsigned long i=0; i<rosetteCount; i++) {
|
|
|
|
kpad.rosetteChars[i] = rosetteChars[i];
|
|
|
|
kpad.rosetteScancodes[i] = rosetteScans[i];
|
|
|
|
}
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
// ASCII : "rosetteChars" : [ 121, 127, 130, 100, 101, 121, 132, 120, 99 ]
|
|
|
|
JSON_ref array = NULL;
|
|
|
|
do {
|
|
|
|
if (!prefs_copyJSONValue(domain, PREF_KPAD_ROSETTE_CHAR_ARRAY, &array)) {
|
|
|
|
LOG("could not parse touch keypad rosette");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
long count = 0;
|
|
|
|
if (!json_arrayCount(array, &count)) {
|
|
|
|
LOG("rosette is not an array!");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (count != rosetteCount) {
|
|
|
|
LOG("rosette count unexpected : %lu!", rosetteCount);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (unsigned long i=0; i<rosetteCount; i++) {
|
|
|
|
kpad.rosetteChars[i] = json_arrayParseLongValueAtIndex(array, i, &lVal, /*base:*/10) ? (uint8_t)lVal : ' ';
|
|
|
|
}
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
json_destroy(&array);
|
|
|
|
|
|
|
|
// long : "rosetteScancodes" : [ -1, 100, -1, 99, -1, 96, -1, 101, -1 ]
|
|
|
|
do {
|
|
|
|
if (!prefs_copyJSONValue(domain, PREF_KPAD_ROSETTE_SCAN_ARRAY, &array)) {
|
|
|
|
LOG("could not parse touch keypad rosette");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
long count = 0;
|
|
|
|
if (!json_arrayCount(array, &count)) {
|
|
|
|
LOG("rosette is not an array!");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (count != rosetteCount) {
|
|
|
|
LOG("rosette count unexpected : %lu!", rosetteCount);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (unsigned long i=0; i<rosetteCount; i++) {
|
|
|
|
kpad.rosetteScancodes[i] = json_arrayParseLongValueAtIndex(array, i, &lVal, /*base:*/10) ? lVal : -1;
|
|
|
|
}
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
json_destroy(&array);
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t *touchkpad_rosetteChars(void) {
|
|
|
|
return kpad.rosetteChars;
|
2015-08-21 05:15:56 +00:00
|
|
|
}
|
|
|
|
|
2015-08-16 05:02:51 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
static void _init_gltouchjoy_kpad(void) {
|
|
|
|
LOG("Registering OpenGL software touch joystick (keypad variant)");
|
|
|
|
|
|
|
|
for (unsigned int i=0; i<MAX_REPEATING; i++) {
|
|
|
|
kpad.scancodes[i] = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
kpad.currButtonDisplayChar = ' ';
|
|
|
|
|
2018-01-21 21:18:49 +00:00
|
|
|
kpadJoy.variant = &touchkpad_variant;
|
|
|
|
kpadJoy.resetState = &touchkpad_resetState;
|
|
|
|
kpadJoy.setup = &touchkpad_setup;
|
|
|
|
kpadJoy.shutdown = &touchkpad_shutdown;
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2016-04-06 05:04:57 +00:00
|
|
|
kpadJoy.prefsChanged = &touchkpad_prefsChanged;
|
|
|
|
|
2018-01-21 21:18:49 +00:00
|
|
|
kpadJoy.buttonDown = &touchkpad_buttonDown;
|
|
|
|
kpadJoy.buttonMove = &touchkpad_buttonMove;
|
|
|
|
kpadJoy.buttonUp = &touchkpad_buttonUp;
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2018-01-21 21:18:49 +00:00
|
|
|
kpadJoy.axisDown = &touchkpad_axisDown;
|
|
|
|
kpadJoy.axisMove = &touchkpad_axisMove;
|
|
|
|
kpadJoy.axisUp = &touchkpad_axisUp;
|
2015-08-16 05:02:51 +00:00
|
|
|
|
2016-04-06 05:04:57 +00:00
|
|
|
kpadJoy.rosetteChars = &touchkpad_rosetteChars;
|
2015-08-21 05:15:56 +00:00
|
|
|
|
2016-04-06 05:04:57 +00:00
|
|
|
gltouchjoy_registerVariant(TOUCH_DEVICE_JOYSTICK_KEYPAD, &kpadJoy);
|
2015-08-16 05:02:51 +00:00
|
|
|
}
|
|
|
|
|
2016-02-23 05:36:08 +00:00
|
|
|
static __attribute__((constructor)) void __init_gltouchjoy_kpad(void) {
|
|
|
|
emulator_registerStartupCallback(CTOR_PRIORITY_EARLY, &_init_gltouchjoy_kpad);
|
|
|
|
}
|
|
|
|
|