diff --git a/Android/jni/jniprefs.c b/Android/jni/jniprefs.c index 8f1776ef..fb609de3 100644 --- a/Android/jni/jniprefs.c +++ b/Android/jni/jniprefs.c @@ -109,7 +109,7 @@ jint Java_org_deadc0de_apple2ix_Apple2Preferences_nativeGetCurrentTouchDevice(JN touchjoy_variant_t variant = joydriver_getTouchVariant(); if (variant == EMULATED_JOYSTICK) { return ANDROID_TOUCH_JOYSTICK; - } else if (variant == EMULATED_JOYSTICK) { + } else if (variant == EMULATED_KEYPAD) { return ANDROID_TOUCH_JOYSTICK_KEYPAD; } } else if (keydriver_ownsScreen()) { @@ -147,7 +147,19 @@ void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickButtonTy return; } - joydriver_setTouchButtonTypes((touchjoy_button_type_t)touchDownButton, (touchjoy_button_type_t)northButton, (touchjoy_button_type_t)southButton); + uint8_t rosetteChars[ROSETTE_COLS * ROSETTE_ROWS]; + int rosetteScancodes[ROSETTE_COLS * ROSETTE_ROWS]; + rosetteChars[ROSETTE_NORTHWEST] = ' '; rosetteScancodes[ROSETTE_NORTHWEST] = -1; + rosetteChars[ROSETTE_NORTH] = (uint8_t)MOUSETEXT_UP; rosetteScancodes[ROSETTE_NORTH] = -1; + rosetteChars[ROSETTE_NORTHEAST] = ' '; rosetteScancodes[ROSETTE_NORTHEAST] = -1; + rosetteChars[ROSETTE_WEST] = (uint8_t)MOUSETEXT_LEFT; rosetteScancodes[ROSETTE_WEST] = -1; + rosetteChars[ROSETTE_CENTER] = '+'; rosetteScancodes[ROSETTE_CENTER] = -1; + rosetteChars[ROSETTE_EAST] = (uint8_t)MOUSETEXT_RIGHT; rosetteScancodes[ROSETTE_EAST] = -1; + rosetteChars[ROSETTE_SOUTHWEST] = ' '; rosetteScancodes[ROSETTE_SOUTHWEST] = -1; + rosetteChars[ROSETTE_SOUTH] = (uint8_t)MOUSETEXT_DOWN; rosetteScancodes[ROSETTE_SOUTH] = -1; + rosetteChars[ROSETTE_SOUTHEAST] = ' '; rosetteScancodes[ROSETTE_SOUTHEAST] = -1; + joydriver_setTouchAxisTypes(rosetteChars, rosetteScancodes); + joydriver_setTouchButtonTypes((touchjoy_button_type_t)touchDownButton, -1, (touchjoy_button_type_t)northButton, -1, (touchjoy_button_type_t)southButton, -1); } void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickTapDelay(JNIEnv *env, jclass cls, jfloat secs) { @@ -175,6 +187,41 @@ void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetAxisOnLe joydriver_setAxisOnLeft(axisIsOnLeft); } +void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetKeypadTypes(JNIEnv *env, jclass cls, + jintArray jRosetteChars, jintArray jRosetteScans, jintArray jButtonsChars, jintArray jButtonsScans) +{ + jint *rosetteChars = (*env)->GetIntArrayElements(env, jRosetteChars, 0); + jint *rosetteScans = (*env)->GetIntArrayElements(env, jRosetteScans, 0); + jint *buttonsChars = (*env)->GetIntArrayElements(env, jButtonsChars, 0); + jint *buttonsScans = (*env)->GetIntArrayElements(env, jButtonsScans, 0); + + LOG("nativeTouchJoystickSetKeypadValues() : NW:%c/%d, N:%c/%d, NE:%c/%d, ... SWIPEUP:%c/%d", + (char)rosetteChars[0], rosetteScans[0], (char)rosetteChars[1], rosetteScans[1], (char)rosetteChars[2], rosetteScans[2], + (char)buttonsChars[1], buttonsScans[1]); + LOG("nativeTouchJoystickSetKeypadValues() : W:%c/%d, C:%c/%d, E:%c/%d, ... TAP:%c/%d", + (char)rosetteChars[3], rosetteScans[3], (char)rosetteChars[4], rosetteScans[4], (char)rosetteChars[5], rosetteScans[5], + (char)buttonsChars[0], buttonsScans[0]); + LOG("nativeTouchJoystickSetKeypadValues() : SW:%c/%d, S:%c/%d, SE:%c/%d, ... SWIPEDN:%c/%d", + (char)rosetteChars[6], rosetteScans[6], (char)rosetteChars[7], rosetteScans[7], (char)rosetteChars[8], rosetteScans[8], + (char)buttonsChars[2], buttonsScans[2]); + + // we could just pass these as jcharArray ... but this isn't a tight loop =P + uint8_t actualChars[ROSETTE_ROWS * ROSETTE_COLS]; + for (unsigned int i=0; i<(ROSETTE_ROWS * ROSETTE_COLS); i++) { + actualChars[i] = (uint8_t)rosetteChars[i]; + } + joydriver_setTouchAxisTypes(actualChars, rosetteScans); + joydriver_setTouchButtonTypes( + (touchjoy_button_type_t)buttonsChars[0], buttonsScans[0], + (touchjoy_button_type_t)buttonsChars[1], buttonsScans[1], + (touchjoy_button_type_t)buttonsChars[2], buttonsScans[2]); + + (*env)->ReleaseIntArrayElements(env, jRosetteChars, rosetteChars, 0); + (*env)->ReleaseIntArrayElements(env, jRosetteScans, rosetteScans, 0); + (*env)->ReleaseIntArrayElements(env, jButtonsChars, buttonsChars, 0); + (*env)->ReleaseIntArrayElements(env, jButtonsScans, buttonsScans, 0); +} + void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickBeginCalibrationMode(JNIEnv *env, jclass cls) { LOG("nativeTouchJoystickBeginCalibrationMode() ..."); joydriver_beginCalibration(); diff --git a/Android/jni/sources.mk b/Android/jni/sources.mk index f2f03bd3..f3273931 100644 --- a/Android/jni/sources.mk +++ b/Android/jni/sources.mk @@ -15,6 +15,8 @@ APPLE2_VIDEO_SRC = \ $(APPLE2_SRC_PATH)/video/glhudmodel.c \ $(APPLE2_SRC_PATH)/video/glalert.c \ $(APPLE2_SRC_PATH)/video/gltouchjoy.c \ + $(APPLE2_SRC_PATH)/video/gltouchjoy_joy.c \ + $(APPLE2_SRC_PATH)/video/gltouchjoy_kpad.c \ $(APPLE2_SRC_PATH)/video/gltouchkbd.c \ $(APPLE2_SRC_PATH)/video/gltouchmenu.c \ $(APPLE2_SRC_PATH)/video_util/matrixUtil.c \ diff --git a/src/joystick.c b/src/joystick.c index c0f53fc4..8b66613a 100644 --- a/src/joystick.c +++ b/src/joystick.c @@ -261,17 +261,20 @@ bool (*joydriver_isTouchJoystickAvailable)(void) = NULL; void (*joydriver_setTouchJoystickEnabled)(bool enabled) = NULL; void (*joydriver_setTouchJoystickOwnsScreen)(bool pwnd) = NULL; bool (*joydriver_ownsScreen)(void) = NULL; -void (*joydriver_setTouchButtonValues)(char button0Val, char button1Val, char buttonBothVal) = NULL; -void (*joydriver_setTouchButtonTypes)(touchjoy_button_type_t down, touchjoy_button_type_t north, touchjoy_button_type_t south) = NULL; +void (*joydriver_setTouchButtonTypes)( + touchjoy_button_type_t touchDownChar, int downScancode, + touchjoy_button_type_t northChar, int northScancode, + touchjoy_button_type_t southChar, int southScancode) = NULL; void (*joydriver_setTapDelay)(float secs) = NULL; void (*joydriver_setTouchAxisSensitivity)(float multiplier) = NULL; void (*joydriver_setButtonSwitchThreshold)(int delta) = NULL; void (*joydriver_setTouchVariant)(touchjoy_variant_t variant) = NULL; touchjoy_variant_t (*joydriver_getTouchVariant)(void) = NULL; -void (*joydriver_setTouchAxisValues)(char up, char left, char right, char down) = NULL; +void (*joydriver_setTouchAxisTypes)(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]) = NULL; void (*joydriver_setScreenDivision)(float division) = NULL; void (*joydriver_setAxisOnLeft)(bool axisIsOnLeft) = NULL; void (*joydriver_beginCalibration)(void) = NULL; void (*joydriver_endCalibration)(void) = NULL; +bool (*joydriver_isCalibrating)(void) = NULL; #endif diff --git a/src/joystick.h b/src/joystick.h index cd2f2aaf..f57ecaff 100644 --- a/src/joystick.h +++ b/src/joystick.h @@ -44,8 +44,24 @@ typedef enum touchjoy_button_type_t { TOUCH_BUTTON0 = 0, TOUCH_BUTTON1, TOUCH_BOTH, + // --or-- an ASCII/fonttext value ... } touchjoy_button_type_t; +#define ROSETTE_ROWS 3 +#define ROSETTE_COLS 3 + +enum { + ROSETTE_NORTHWEST=0, + ROSETTE_NORTH, + ROSETTE_NORTHEAST, + ROSETTE_WEST, + ROSETTE_CENTER, + ROSETTE_EAST, + ROSETTE_SOUTHWEST, + ROSETTE_SOUTH, + ROSETTE_SOUTHEAST, +}; + // is the touch joystick available extern bool (*joydriver_isTouchJoystickAvailable)(void); @@ -58,16 +74,24 @@ extern void (*joydriver_setTouchJoystickOwnsScreen)(bool pwnd); // query touch screen ownership extern bool (*joydriver_ownsScreen)(void); -// set the joystick button visuals (these values are also fired for keyboard variant) -extern void (*joydriver_setTouchButtonValues)(char button0Val, char button1Val, char buttonBothVal); +/* + * set the joystick button types/visuals (scancodes are fired for EMULATED_KEYPAD variant) + * + * - for EMULATED_JOYSTICK, there is an implicit extra layer-of-indirection for the touchjoy_button_type_t, which maps + * to the open apple, closed apple, or "both" visual keys + * + * - for EMULATED_KEYPAD, the touchjoy_button_type_t is the displayed visual (as ASCII value and lookup into font + * table) + */ +extern void (*joydriver_setTouchButtonTypes)( + touchjoy_button_type_t touchDownChar, int downScancode, + touchjoy_button_type_t northChar, int northScancode, + touchjoy_button_type_t southChar, int southScancode); -// set the joystick button types (for joystick variant) -extern void (*joydriver_setTouchButtonTypes)(touchjoy_button_type_t down, touchjoy_button_type_t north, touchjoy_button_type_t south); - -// set the tap delay (to differentiate between single tap and north/south/etc swipe) +// set the button tap delay (to differentiate between single tap and north/south/etc swipe) extern void (*joydriver_setTapDelay)(float secs); -// set the touch axis sensitivity multiplier +// set the sensitivity multiplier extern void (*joydriver_setTouchAxisSensitivity)(float multiplier); // set the touch button switch threshold @@ -79,8 +103,8 @@ extern void (*joydriver_setTouchVariant)(touchjoy_variant_t variant); // get the joystick variant extern touchjoy_variant_t (*joydriver_getTouchVariant)(void); -// set the axis visuals (these key values are also fired for keyboard variant) -extern void (*joydriver_setTouchAxisValues)(char north, char west, char east, char south); +// set the axis visuals (scancodes are fired for EMULATED_KEYPAD variant) +extern void (*joydriver_setTouchAxisTypes)(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]); // set screen divide between axis and buttons extern void (*joydriver_setScreenDivision)(float division); @@ -94,6 +118,9 @@ extern void (*joydriver_beginCalibration)(void); // end calibration mode extern void (*joydriver_endCalibration)(void); +// end calibration mode +extern bool (*joydriver_isCalibrating)(void); + #endif // INTERFACE_TOUCH #endif // whole file diff --git a/src/keys.c b/src/keys.c index 0369fa0a..ff0a05ed 100644 --- a/src/keys.c +++ b/src/keys.c @@ -494,5 +494,6 @@ bool (*keydriver_isTouchKeyboardAvailable)(void) = NULL; void (*keydriver_setTouchKeyboardEnabled)(bool enabled) = NULL; void (*keydriver_setTouchKeyboardOwnsScreen)(bool pwnd) = NULL; bool (*keydriver_ownsScreen)(void) = NULL; +void (*keydriver_keyboardReadCallback)(void) = NULL; #endif diff --git a/src/keys.h b/src/keys.h index e8cbb43d..65108acb 100644 --- a/src/keys.h +++ b/src/keys.h @@ -160,6 +160,9 @@ extern void (*keydriver_setTouchKeyboardOwnsScreen)(bool pwnd); // query touch screen ownership extern bool (*keydriver_ownsScreen)(void); + +// keyboard read callback +extern void (*keydriver_keyboardReadCallback)(void); #endif #endif diff --git a/src/video/gltouchjoy.c b/src/video/gltouchjoy.c index 5edede29..c66a4cf7 100644 --- a/src/video/gltouchjoy.c +++ b/src/video/gltouchjoy.c @@ -9,10 +9,7 @@ * */ -#include "common.h" -#include "video/glvideo.h" -#include "video/glhudmodel.h" -#include "video/glnode.h" +#include "video/gltouchjoy.h" #if !INTERFACE_TOUCH #error this is a touch interface module, possibly you mean to not compile this at all? @@ -52,18 +49,15 @@ #define BUTTON_SWITCH_THRESHOLD_DEFAULT 22 #define BUTTON_TAP_DELAY_NANOS_DEFAULT 50000000 -// module globals +GLTouchJoyGlobals joyglobals = { 0 }; +GLTouchJoyAxes axes = { 0 }; +GLTouchJoyButtons buttons = { 0 }; + static struct { - bool isAvailable; // Were there any OpenGL/memory errors on gltouchjoy initialization? - bool isShuttingDown; - bool isCalibrating; // Are we running in calibration mode? - bool isEnabled; // Does player want touchjoy enabled? - bool ownsScreen; // Does the touchjoy currently own the screen? - float minAlphaWhenOwnsScreen; - float minAlpha; - float screenDivider; - bool axisIsOnLeft; -} joyglobals = { 0 }; + GLTouchJoyVariant *joys; + GLTouchJoyVariant *kpad; + GLTouchJoyVariant *curr; +} variant = { 0 }; // viewport touch static struct { @@ -85,60 +79,6 @@ static struct { // TODO FIXME : support 2-players! } touchport = { 0 }; -// touch axis variables - -static struct { - - GLModel *model; - uint8_t northChar; - uint8_t westChar; - uint8_t eastChar; - uint8_t southChar; - bool modelDirty; - - touchjoy_variant_t type; - - int centerX; - int centerY; - float multiplier; - int trackingIndex; - struct timespec timingBegin; - -} axes = { 0 }; - -// button object variables - -static struct { - GLModel *model; - uint8_t activeChar; - bool modelDirty; - - touchjoy_button_type_t touchDownButton; - touchjoy_button_type_t northButton; - touchjoy_button_type_t southButton; - - uint8_t char0; - uint8_t char1; - uint8_t charBoth; - - int switchThreshold; - - int centerX; - int centerY; - int trackingIndex; - struct timespec timingBegin; - - pthread_t tapDelayThreadId; - pthread_mutex_t tapDelayMutex; - pthread_cond_t tapDelayCond; - unsigned int tapDelayNanos; - - volatile uint8_t currButtonValue0; - volatile uint8_t currButtonValue1; - volatile uint8_t currButtonChar; - -} buttons = { 0 }; - // ---------------------------------------------------------------------------- #warning FIXME TODO ... this can become a common helper function ... @@ -192,10 +132,12 @@ static void _setup_axis_object(GLModel *parent) { } const unsigned int row = (AXIS_TEMPLATE_COLS+1); - ((hudElement->tpl)+(row*0))[2] = axes.northChar; - ((hudElement->tpl)+(row*2))[0] = axes.westChar; - ((hudElement->tpl)+(row*2))[4] = axes.eastChar; - ((hudElement->tpl)+(row*4))[2] = axes.southChar; + + for (unsigned int i=0; itpl)+(row*i*2))[j*2] = axes.rosetteChars[(i*ROSETTE_ROWS)+j]; + } + } glhud_setupDefault(parent); } @@ -276,17 +218,22 @@ static void *_button_tap_delayed_thread(void *dummyptr) { nanosleep(&ts, NULL); pthread_mutex_lock(&buttons.tapDelayMutex); - // set the emulator's joystick button values until touch up + // wait until touch up/cancel do { - joy_button0 = buttons.currButtonValue0; - joy_button1 = buttons.currButtonValue1; - _setup_button_object_with_char(buttons.currButtonChar); + + // 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 == TOUCH_NONE) || joyglobals.isShuttingDown) { break; } + pthread_cond_wait(&buttons.tapDelayCond, &buttons.tapDelayMutex); + if ( (buttons.trackingIndex == TOUCH_NONE) || joyglobals.isShuttingDown) { + break; + } TOUCH_JOY_LOG(">>> [DELAYEDTAP] looping ..."); } while (1); @@ -299,8 +246,8 @@ static void *_button_tap_delayed_thread(void *dummyptr) { nanosleep(&ts, NULL); pthread_mutex_lock(&buttons.tapDelayMutex); - joy_button0 = 0x0; - joy_button1 = 0x0; + variant.curr->buttonRelease(); + TOUCH_JOY_LOG(">>> [DELAYEDTAP] end ..."); } while (1); @@ -316,6 +263,9 @@ static void *_button_tap_delayed_thread(void *dummyptr) { static void gltouchjoy_setup(void) { LOG("gltouchjoy_setup ..."); + variant.kpad->resetState(); + variant.joys->resetState(); + mdlDestroyModel(&axes.model); mdlDestroyModel(&buttons.model); @@ -365,6 +315,9 @@ static void gltouchjoy_shutdown(void) { return; } + variant.kpad->resetState(); + variant.joys->resetState(); + joyglobals.isAvailable = false; joyglobals.isShuttingDown = true; @@ -387,6 +340,9 @@ static void gltouchjoy_render(void) { if (!joyglobals.isEnabled) { return; } + if (!joyglobals.ownsScreen) { + return; + } glViewport(0, 0, touchport.width, touchport.height); // NOTE : show these HUD elements beyond the A2 framebuffer dimensions @@ -475,11 +431,11 @@ static void gltouchjoy_resetJoystick(void) { // no-op } -static inline bool _is_point_on_axis_side(float x, float y) { +static inline bool _is_point_on_axis_side(int x, int y) { return (x >= touchport.axisX && x <= touchport.axisXMax && y >= touchport.axisY && y <= touchport.axisYMax); } -static inline bool _is_point_on_button_side(float x, float y) { +static inline bool _is_point_on_button_side(int x, int y) { return (x >= touchport.buttonX && x <= touchport.buttonXMax && y >= touchport.buttonY && y <= touchport.buttonYMax); } @@ -507,72 +463,91 @@ static inline void _reset_model_position(GLModel *model, float touchX, float tou quad[12+1] = centerY+objHalfH; } -static inline void _move_joystick_axis(int x, int y) { +static inline void _axis_touch_down(int x, int y) { + axes.centerX = x; + axes.centerY = y; - x = (x - axes.centerX); - y = (y - axes.centerY); + _reset_model_position(axes.model, x, y, AXIS_OBJ_HALF_W, AXIS_OBJ_HALF_H); + axes.modelDirty = true; - if (axes.multiplier != 1.f) { - x = (int) ((float)x * axes.multiplier); - y = (int) ((float)y * axes.multiplier); - } - - x += 0x80; - y += 0x80; - - if (x < 0) { - x = 0; - } - if (x > 0xff) { - x = 0xff; - } - if (y < 0) { - y = 0; - } - if (y > 0xff) { - y = 0xff; - } - - joy_x = x; - joy_y = y; + TOUCH_JOY_LOG("---TOUCH %sDOWN (axis index %d) center:(%d,%d) -> joy(0x%02X,0x%02X)", (action == TOUCH_DOWN ? "" : "POINTER "), axes.trackingIndex, axes.centerX, axes.centerY, joy_x, joy_y); + variant.curr->axisDown(); } -static inline void _set_current_joy_button_values(int theButton) { - if (theButton == TOUCH_BUTTON0) { - buttons.currButtonValue0 = 0x80; - buttons.currButtonValue1 = 0; - buttons.currButtonChar = buttons.char0; - } else if (theButton == TOUCH_BUTTON1) { - buttons.currButtonValue0 = 0; - buttons.currButtonValue1 = 0x80; - buttons.currButtonChar = buttons.char1; - } else if (theButton == TOUCH_BOTH) { - buttons.currButtonValue0 = 0x80; - buttons.currButtonValue1 = 0x80; - buttons.currButtonChar = buttons.charBoth; - } else { - buttons.currButtonValue0 = 0; - buttons.currButtonValue1 = 0; +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; + + _reset_model_position(buttons.model, x, y, BUTTON_OBJ_HALF_W, BUTTON_OBJ_HALF_H); + 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); +} + +static inline void _axis_move(int x, int y) { + x = (x - axes.centerX); + y = (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); +} + +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); } +} + +static inline void _axis_touch_up(int x, int y) { + x = (x - axes.centerX); + y = (y - axes.centerY); + if (buttons.trackingIndex > axes.trackingIndex) { + --buttons.trackingIndex; + } + variant.curr->axisUp(x, y); +#if DEBUG_TOUCH_JOY + bool resetIndex = false; + if (buttons.trackingIndex > axes.trackingIndex) { + // TODO FIXME : is resetting the pointer index just an Android-ism? + resetIndex = true; + } + TOUCH_JOY_LOG("---TOUCH %sUP (axis went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset buttons index!)" : "")); +#endif + axes.trackingIndex = TOUCH_NONE; +} + +static inline void _button_touch_up(void) { +#if DEBUG_TOUCH_JOY + bool resetIndex = false; + if (axes.trackingIndex > buttons.trackingIndex) { + // TODO FIXME : is resetting the pointer index just an Android-ism? + resetIndex = true; + } + TOUCH_JOY_LOG("---TOUCH %sUP (buttons went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset axis index!)" : "")); +#endif + if (axes.trackingIndex > buttons.trackingIndex) { + --axes.trackingIndex; + } + buttons.trackingIndex = TOUCH_NONE; _signal_tap_delay(); } -static inline void _move_button_axis(int x, int y) { - - x -= buttons.centerX; - y -= buttons.centerY; - - if ((y < -buttons.switchThreshold) || (y > buttons.switchThreshold)) { - int theButton = -1; - if (y < 0) { - theButton = buttons.northButton; - } else { - theButton = buttons.southButton; - } - - _set_current_joy_button_values(theButton); - } -} static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) { @@ -589,87 +564,63 @@ static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int point bool axisConsumed = false; bool buttonConsumed = false; - float x = x_coords[pointer_idx]; - float y = y_coords[pointer_idx]; - switch (action) { case TOUCH_DOWN: case TOUCH_POINTER_DOWN: - if (_is_point_on_axis_side(x, y)) { - axisConsumed = true; - axes.trackingIndex = pointer_idx; - axes.centerX = (int)x; - axes.centerY = (int)y; - _reset_model_position(axes.model, x, y, AXIS_OBJ_HALF_W, AXIS_OBJ_HALF_H); - axes.modelDirty = true; - TOUCH_JOY_LOG("---TOUCH %sDOWN (axis index %d) center:(%d,%d) -> joy(0x%02X,0x%02X)", (action == TOUCH_DOWN ? "" : "POINTER "), - axes.trackingIndex, axes.centerX, axes.centerY, joy_x, joy_y); - } else if (_is_point_on_button_side(x, y)) { - buttonConsumed = true; - buttons.trackingIndex = pointer_idx; - _set_current_joy_button_values(buttons.touchDownButton); - buttons.centerX = (int)x; - buttons.centerY = (int)y; - _reset_model_position(buttons.model, x, y, BUTTON_OBJ_HALF_W, BUTTON_OBJ_HALF_H); - 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); - } else { - // ... + { + int x = (int)x_coords[pointer_idx]; + int y = (int)y_coords[pointer_idx]; + if (_is_point_on_axis_side(x, y)) { + axisConsumed = true; + axes.trackingIndex = pointer_idx; + _axis_touch_down(x, y); + } else if (_is_point_on_button_side(x, y)) { + buttonConsumed = true; + buttons.trackingIndex = pointer_idx; + _button_touch_down(x, y); + } else { + assert(false && "should either be on axis or button side"); + } } break; case TOUCH_MOVE: if (axes.trackingIndex >= 0) { axisConsumed = true; - _move_joystick_axis((int)x_coords[axes.trackingIndex], (int)y_coords[axes.trackingIndex]); - TOUCH_JOY_LOG("---TOUCH MOVE ...tracking axis:%d (%d,%d) -> joy(0x%02X,0x%02X)", axes.trackingIndex, - (int)x_coords[axes.trackingIndex], (int)y_coords[axes.trackingIndex], joy_x, joy_y); + int x = (int)x_coords[axes.trackingIndex]; + int y = (int)y_coords[axes.trackingIndex]; + _axis_move(x, y); } if (buttons.trackingIndex >= 0) { buttonConsumed = true; - _move_button_axis((int)x_coords[buttons.trackingIndex], (int)y_coords[buttons.trackingIndex]); - TOUCH_JOY_LOG("+++TOUCH MOVE ...tracking button:%d (%d,%d) -> buttons(0x%02X,0x%02X)", buttons.trackingIndex, - (int)x_coords[buttons.trackingIndex], (int)y_coords[buttons.trackingIndex], joy_button0, joy_button1); + int x = (int)x_coords[buttons.trackingIndex]; + int y = (int)y_coords[buttons.trackingIndex]; + _button_move(x, y); } break; case TOUCH_UP: case TOUCH_POINTER_UP: if (pointer_idx == axes.trackingIndex) { - axisConsumed = true; - bool resetIndex = false; - if (buttons.trackingIndex > axes.trackingIndex) { - // TODO FIXME : is resetting the pointer index just an Android-ism? - resetIndex = true; - --buttons.trackingIndex; - } - axes.trackingIndex = TOUCH_NONE; - joy_x = HALF_JOY_RANGE; - joy_y = HALF_JOY_RANGE; - TOUCH_JOY_LOG("---TOUCH %sUP (axis went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset buttons index!)" : "")); + int x = (int)x_coords[axes.trackingIndex]; + int y = (int)y_coords[axes.trackingIndex]; + _axis_touch_up(x, y); } else if (pointer_idx == buttons.trackingIndex) { - buttonConsumed = true; - bool resetIndex = false; - if (axes.trackingIndex > buttons.trackingIndex) { - // TODO FIXME : is resetting the pointer index just an Android-ism? - resetIndex = true; - --axes.trackingIndex; - } - buttons.trackingIndex = TOUCH_NONE; - _signal_tap_delay(); - TOUCH_JOY_LOG("---TOUCH %sUP (buttons went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset axis index!)" : "")); + _button_touch_up(); } else { - // not tracking tap/gestures originating from control-gesture portion of screen + if (pointer_count == 1) { + // last pointer up completely resets state + LOG("!!!! ... RESETTING TOUCH JOYSTICK STATE MACHINE"); + variant.joys->resetState(); + variant.kpad->resetState(); + } } break; case TOUCH_CANCEL: LOG("---TOUCH CANCEL"); - axes.trackingIndex = TOUCH_NONE; - buttons.trackingIndex = TOUCH_NONE; - joy_x = HALF_JOY_RANGE; - joy_y = HALF_JOY_RANGE; + variant.joys->resetState(); + variant.kpad->resetState(); break; default: @@ -724,16 +675,19 @@ static void _animation_hideTouchJoystick(void) { buttons.timingBegin = (struct timespec){ 0 }; } -static void gltouchjoy_setTouchButtonValues(char char0, char char1, char charBoth) { - buttons.char0 = char0; - buttons.char1 = char1; - buttons.charBoth = charBoth; -} +static void gltouchjoy_setTouchButtonTypes( + touchjoy_button_type_t touchDownChar, int touchDownScancode, + touchjoy_button_type_t northChar, int northScancode, + touchjoy_button_type_t southChar, int southScancode) +{ + buttons.touchDownChar = touchDownChar; + buttons.touchDownScancode = touchDownScancode; -static void gltouchjoy_setTouchButtonTypes(touchjoy_button_type_t touchDownButton, touchjoy_button_type_t northButton, touchjoy_button_type_t southButton) { - buttons.touchDownButton = touchDownButton; - buttons.southButton = southButton; - buttons.northButton = northButton; + buttons.southChar = southChar; + buttons.southScancode = southScancode; + + buttons.northChar = northChar; + buttons.northScancode = northScancode; } static void gltouchjoy_setTapDelay(float secs) { @@ -751,23 +705,36 @@ static void gltouchjoy_setTouchAxisSensitivity(float multiplier) { } static void gltouchjoy_setButtonSwitchThreshold(int delta) { - buttons.switchThreshold = delta; + joyglobals.switchThreshold = delta; } -static void gltouchjoy_setTouchVariant(touchjoy_variant_t axisType) { - axes.type = axisType; - _setup_axis_object(axes.model); +static void gltouchjoy_setTouchVariant(touchjoy_variant_t variantType) { + variant.curr->resetState(); + + switch (variantType) { + case EMULATED_JOYSTICK: + variant.curr = variant.joys; + break; + + case EMULATED_KEYPAD: + variant.curr = variant.kpad; + break; + + default: + assert(false && "touch variant set to invalid"); + break; + } + + variant.curr->resetState(); } static touchjoy_variant_t gltouchjoy_getTouchVariant(void) { - return axes.type; + return variant.curr->variant(); } -static void gltouchjoy_setTouchAxisValues(char north, char west, char east, char south) { - axes.northChar = north; - axes.westChar = west; - axes.eastChar = east; - axes.southChar = south; +static void gltouchjoy_setTouchAxisTypes(uint8_t rosetteChars[(ROSETTE_ROWS * ROSETTE_COLS)], int rosetteScancodes[(ROSETTE_ROWS * ROSETTE_COLS)]) { + memcpy(axes.rosetteChars, rosetteChars, sizeof(uint8_t)*(ROSETTE_ROWS * ROSETTE_COLS)); + memcpy(axes.rosetteScancodes, rosetteScancodes, sizeof(int) *(ROSETTE_ROWS * ROSETTE_COLS)); _setup_axis_object(axes.model); } @@ -791,6 +758,10 @@ static void gltouchjoy_endCalibration(void) { joyglobals.isCalibrating = false; } +static bool gltouchjoy_isCalibrating(void) { + return joyglobals.isCalibrating; +} + __attribute__((constructor(CTOR_PRIORITY_LATE))) static void _init_gltouchjoy(void) { LOG("Registering OpenGL software touch joystick"); @@ -799,25 +770,42 @@ static void _init_gltouchjoy(void) { axes.centerY = 160; axes.multiplier = 1.f; axes.trackingIndex = TOUCH_NONE; - axes.type = EMULATED_JOYSTICK; - axes.northChar = MOUSETEXT_UP; - axes.westChar = MOUSETEXT_LEFT; - axes.eastChar = MOUSETEXT_RIGHT; - axes.southChar = MOUSETEXT_DOWN; + + axes.rosetteChars[0] = ' '; + axes.rosetteScancodes[0] = -1; + axes.rosetteChars[1] = MOUSETEXT_UP; + axes.rosetteScancodes[1] = -1; + axes.rosetteChars[2] = ' '; + axes.rosetteScancodes[2] = -1; + + axes.rosetteChars[3] = MOUSETEXT_LEFT; + axes.rosetteScancodes[3] = -1; + axes.rosetteChars[4] = '+'; + axes.rosetteScancodes[4] = -1; + axes.rosetteChars[5] = MOUSETEXT_RIGHT; + axes.rosetteScancodes[5] = -1; + + axes.rosetteChars[6] = ' '; + axes.rosetteScancodes[6] = -1; + axes.rosetteChars[7] = MOUSETEXT_DOWN; + axes.rosetteScancodes[7] = -1; + axes.rosetteChars[8] = ' '; + axes.rosetteScancodes[8] = -1; buttons.centerX = 240; buttons.centerY = 160; buttons.trackingIndex = TOUCH_NONE; - buttons.touchDownButton = TOUCH_BUTTON0; - buttons.southButton = TOUCH_BUTTON1; - buttons.northButton = TOUCH_BOTH; - buttons.char0 = MOUSETEXT_OPENAPPLE; - buttons.char1 = MOUSETEXT_CLOSEDAPPLE; - buttons.charBoth = '+'; + buttons.touchDownChar = TOUCH_BUTTON0; + buttons.touchDownScancode = -1; + + buttons.southChar = TOUCH_BUTTON1; + buttons.southScancode = -1; + + buttons.northChar = TOUCH_BOTH; + buttons.northScancode = -1; buttons.activeChar = MOUSETEXT_OPENAPPLE; - buttons.switchThreshold = BUTTON_SWITCH_THRESHOLD_DEFAULT; buttons.tapDelayThreadId = 0; buttons.tapDelayMutex = (pthread_mutex_t){ 0 }; @@ -828,6 +816,7 @@ static void _init_gltouchjoy(void) { joyglobals.ownsScreen = true; joyglobals.screenDivider = 0.5f; joyglobals.axisIsOnLeft = true; + joyglobals.switchThreshold = BUTTON_SWITCH_THRESHOLD_DEFAULT; video_backend->animation_showTouchJoystick = &_animation_showTouchJoystick; video_backend->animation_hideTouchJoystick = &_animation_hideTouchJoystick; @@ -836,18 +825,18 @@ static void _init_gltouchjoy(void) { joydriver_setTouchJoystickEnabled = &gltouchjoy_setTouchJoystickEnabled; joydriver_setTouchJoystickOwnsScreen = &gltouchjoy_setTouchJoystickOwnsScreen; joydriver_ownsScreen = &gltouchjoy_ownsScreen; - joydriver_setTouchButtonValues = &gltouchjoy_setTouchButtonValues; joydriver_setTouchButtonTypes = &gltouchjoy_setTouchButtonTypes; joydriver_setTapDelay = &gltouchjoy_setTapDelay; joydriver_setTouchAxisSensitivity = &gltouchjoy_setTouchAxisSensitivity; joydriver_setButtonSwitchThreshold = &gltouchjoy_setButtonSwitchThreshold; joydriver_setTouchVariant = &gltouchjoy_setTouchVariant; joydriver_getTouchVariant = &gltouchjoy_getTouchVariant; - joydriver_setTouchAxisValues = &gltouchjoy_setTouchAxisValues; + joydriver_setTouchAxisTypes = &gltouchjoy_setTouchAxisTypes; joydriver_setScreenDivision = &gltouchjoy_setScreenDivision; joydriver_setAxisOnLeft = &gltouchjoy_setAxisOnLeft; joydriver_beginCalibration = &gltouchjoy_beginCalibration; joydriver_endCalibration = &gltouchjoy_endCalibration; + joydriver_isCalibrating = &gltouchjoy_isCalibrating; glnode_registerNode(RENDER_LOW, (GLNode){ .setup = &gltouchjoy_setup, @@ -858,6 +847,25 @@ static void _init_gltouchjoy(void) { }); } +void gltouchjoy_registerVariant(touchjoy_variant_t variantType, GLTouchJoyVariant *touchJoyVariant) { + switch (variantType) { + case EMULATED_JOYSTICK: + variant.joys = touchJoyVariant; + variant.curr = variant.kpad; + break; + + case EMULATED_KEYPAD: + variant.kpad = touchJoyVariant; + variant.curr = variant.kpad; + break; + + default: + assert(false && "invalid touch variant registration"); + break; + } +} + + void gldriver_joystick_reset(void) { #warning FIXME TODO expunge this olde API ... } diff --git a/src/video/gltouchjoy.h b/src/video/gltouchjoy.h new file mode 100644 index 00000000..643b6005 --- /dev/null +++ b/src/video/gltouchjoy.h @@ -0,0 +1,99 @@ +/* + * Apple // emulator for *nix + * + * This software package is subject to the GNU General Public License + * version 2 or later (your choice) as published by the Free Software + * Foundation. + * + * THERE ARE NO WARRANTIES WHATSOEVER. + * + */ + +#ifndef _GLTOUCHJOY_H_ +#define _GLTOUCHJOY_H_ + +#include "common.h" +#include "video/glvideo.h" +#include "video/glhudmodel.h" +#include "video/glnode.h" + +// globals + +typedef struct GLTouchJoyGlobals { + + bool isAvailable; // Were there any OpenGL/memory errors on gltouchjoy initialization? + bool isShuttingDown; + bool isCalibrating; // Are we running in calibration mode? + bool isEnabled; // Does player want touchjoy enabled? + bool ownsScreen; // Does the touchjoy currently own the screen? + float minAlphaWhenOwnsScreen; + float minAlpha; + float screenDivider; + bool axisIsOnLeft; + + int switchThreshold; + +} GLTouchJoyGlobals; +extern GLTouchJoyGlobals joyglobals; + +// touch axis variables + +typedef struct GLTouchJoyAxes { + + GLModel *model; + bool modelDirty; + + uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS]; + int rosetteScancodes[ROSETTE_ROWS * ROSETTE_COLS]; + + int centerX; + int centerY; + float multiplier; + int trackingIndex; + struct timespec timingBegin; + +} GLTouchJoyAxes; +extern GLTouchJoyAxes axes; + +// button object variables + +typedef struct GLTouchJoyButtons { + GLModel *model; + uint8_t activeChar; + bool modelDirty; + + touchjoy_button_type_t touchDownChar; + int touchDownScancode; + touchjoy_button_type_t northChar; + int northScancode; + touchjoy_button_type_t southChar; + int southScancode; + + int centerX; + int centerY; + 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); +} GLTouchJoyVariant; + +// registers a touch joystick variant with manager +void gltouchjoy_registerVariant(touchjoy_variant_t variant, GLTouchJoyVariant *touchJoyVariant); + +#endif // whole file diff --git a/src/video/gltouchjoy_joy.c b/src/video/gltouchjoy_joy.c new file mode 100644 index 00000000..c60caccb --- /dev/null +++ b/src/video/gltouchjoy_joy.c @@ -0,0 +1,137 @@ +/* + * Apple // emulator for *nix + * + * This software package is subject to the GNU General Public License + * version 2 or later (your choice) as published by the Free Software + * Foundation. + * + * THERE ARE NO WARRANTIES WHATSOEVER. + * + */ + +#include "video/gltouchjoy.h" + +#if !INTERFACE_TOUCH +#error this is a touch interface module, possibly you mean to not compile this at all? +#endif + +static GLTouchJoyVariant happyHappyJoyJoy = { 0 }; + +static struct { + uint8_t currJoyButtonValue0; + uint8_t currJoyButtonValue1; + uint8_t currButtonDisplayChar; +} joys; + +static touchjoy_variant_t touchjoy_variant(void) { + return EMULATED_JOYSTICK; +} + +static inline void _reset_axis_state(void) { + joy_x = HALF_JOY_RANGE; + joy_y = HALF_JOY_RANGE; +} + +static inline void _reset_buttons_state(void) { + joy_button0 = 0x0; + joy_button1 = 0x0; + joys.currJoyButtonValue0 = 0; + joys.currJoyButtonValue1 = 0; + joys.currButtonDisplayChar = ' '; +} + +static void touchjoy_resetState(void) { + _reset_axis_state(); + _reset_buttons_state(); +} + +// ---------------------------------------------------------------------------- +// axis state + +static void touchjoy_axisDown(void) { + _reset_axis_state(); +} + +static void touchjoy_axisMove(int x, int y) { + if (axes.multiplier != 1.f) { + x = (int) ((float)x * axes.multiplier); + y = (int) ((float)y * axes.multiplier); + } + + x += 0x80; + y += 0x80; + + if (x < 0) { + x = 0; + } + if (x > 0xff) { + x = 0xff; + } + if (y < 0) { + y = 0; + } + if (y > 0xff) { + y = 0xff; + } + + joy_x = x; + joy_y = y; +} + +static void touchjoy_axisUp(int x, int y) { + _reset_axis_state(); +} + +// ---------------------------------------------------------------------------- +// button state + +static void touchjoy_setCurrButtonValue(touchjoy_button_type_t theButtonChar, int theButtonScancode) { + if (theButtonChar == TOUCH_BUTTON0) { + joys.currJoyButtonValue0 = 0x80; + joys.currJoyButtonValue1 = 0; + joys.currButtonDisplayChar = MOUSETEXT_OPENAPPLE; + } else if (theButtonChar == TOUCH_BUTTON1) { + joys.currJoyButtonValue0 = 0; + joys.currJoyButtonValue1 = 0x80; + joys.currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE; + } else if (theButtonChar == TOUCH_BOTH) { + joys.currJoyButtonValue0 = 0x80; + joys.currJoyButtonValue1 = 0x80; + joys.currButtonDisplayChar = '+'; + } else { + joys.currJoyButtonValue0 = 0; + joys.currJoyButtonValue1 = 0; + joys.currButtonDisplayChar = ' '; + } +} + +static uint8_t touchjoy_buttonPress(void) { + joy_button0 = joys.currJoyButtonValue0; + joy_button1 = joys.currJoyButtonValue1; + return joys.currButtonDisplayChar; +} + +static void touchjoy_buttonRelease(void) { + _reset_buttons_state(); +} + +// ---------------------------------------------------------------------------- + +__attribute__((constructor(CTOR_PRIORITY_EARLY))) +static void _init_gltouchjoy_joy(void) { + LOG("Registering OpenGL software touch joystick (joystick variant)"); + + happyHappyJoyJoy.variant = &touchjoy_variant, + happyHappyJoyJoy.resetState = &touchjoy_resetState, + + happyHappyJoyJoy.setCurrButtonValue = &touchjoy_setCurrButtonValue, + happyHappyJoyJoy.buttonPress = &touchjoy_buttonPress, + happyHappyJoyJoy.buttonRelease = &touchjoy_buttonRelease, + + happyHappyJoyJoy.axisDown = &touchjoy_axisDown, + happyHappyJoyJoy.axisMove = &touchjoy_axisMove, + happyHappyJoyJoy.axisUp = &touchjoy_axisUp, + + gltouchjoy_registerVariant(EMULATED_JOYSTICK, &happyHappyJoyJoy); +} + diff --git a/src/video/gltouchjoy_kpad.c b/src/video/gltouchjoy_kpad.c new file mode 100644 index 00000000..b6dae37a --- /dev/null +++ b/src/video/gltouchjoy_kpad.c @@ -0,0 +1,421 @@ +/* + * Apple // emulator for *nix + * + * This software package is subject to the GNU General Public License + * version 2 or later (your choice) as published by the Free Software + * Foundation. + * + * THERE ARE NO WARRANTIES WHATSOEVER. + * + */ + +#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) + +// 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) +#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; + + // scancodes to fire upon axisUp, buttonPress, or as key-repeat + keypad_fire_t fireIdx; + int scancodes[MAX_REPEATING]; + struct timespec timingBegins[MAX_REPEATING]; + + bool axisTiming; + bool buttonTiming; + +} kpad = { 0 }; + +static GLTouchJoyVariant kpadJoy = { 0 }; + +// ---------------------------------------------------------------------------- +// repeat key callback + +static void touchkpad_keyboardReadCallback(void) { + + assert(pthread_self() == cpu_thread_id); + + struct timespec now = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &now); + + for (unsigned int i=0; i= 0) { + struct timespec deltat = timespec_diff(kpad.timingBegins[kpad.fireIdx], now, NULL); + if (deltat.tv_sec || deltat.tv_nsec > KEY_REPEAT_THRESHOLD_NANOS) { + LOG("REPEAT #%d/%lu/%lu: %d", kpad.fireIdx, deltat.tv_sec, deltat.tv_nsec, scancode); + c_keys_handle_input(scancode, /*pressed:*/true, /*ASCII:*/false); + fired = true; + } + } + ++kpad.fireIdx; + if (kpad.fireIdx >= MAX_REPEATING) { + kpad.fireIdx = 0; + } + if (fired) { + break; + } + } + + if (!kpad.axisTiming && !kpad.buttonTiming) { + keydriver_keyboardReadCallback = NULL;// unschedule callback + } +} + +// ---------------------------------------------------------------------------- + +static touchjoy_variant_t touchkpad_variant(void) { + return EMULATED_KEYPAD; +} + +static void touchkpad_resetState(void) { + kpad.axisCurrentOctant = ORIGIN; + + for (unsigned int i=0; i= 0) { + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_CENTER]; + kpad.scancodes[REPEAT_AXIS_ALT] = -1; + clock_gettime(CLOCK_MONOTONIC, &kpad.timingBegins[REPEAT_AXIS]); + kpad.axisTiming = true; + keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback; + } +} + +static void touchkpad_axisMove(int dx, int dy) { + + if ((dx > -joyglobals.switchThreshold) && (dx < joyglobals.switchThreshold) && (dy > -joyglobals.switchThreshold) && (dy < joyglobals.switchThreshold)) { + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_CENTER]; + 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; + } + + LOG("radians:%f radnorm:%f octant:%f, currOctant:%d", radians, radnorm, octant, kpad.axisCurrentOctant); + + // handle a new octant + + struct timespec now = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &now); + kpad.timingBegins[REPEAT_AXIS] = now; + kpad.timingBegins[REPEAT_AXIS_ALT] = now; + keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback; + + kpad.scancodes[REPEAT_AXIS_ALT] = -1; + switch (kpad.axisCurrentOctant) { + case OCTANT_NORTHWEST: + if (axes.rosetteScancodes[ROSETTE_NORTHWEST] >= 0) { + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHWEST]; + LOG("XY : NORTHWEST, (%d)", axes.rosetteScancodes[ROSETTE_WEST]); + } else if (axes.rosetteScancodes[ROSETTE_NORTH] < 0) { + if (radians > RADIANS_NORTHWEST) { + LOG("IGNORING Y (NORTH) ..."); + kpad.scancodes[REPEAT_AXIS] = -1; + } else { + LOG("X : WEST (%d)", axes.rosetteScancodes[ROSETTE_WEST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST]; + } + } else if (axes.rosetteScancodes[ROSETTE_WEST] < 0) { + if (radians < RADIANS_NORTHWEST) { + LOG("IGNORING X (WEST) ..."); + kpad.scancodes[REPEAT_AXIS] = -1; + } else { + LOG("Y : NORTH (%d)", axes.rosetteScancodes[ROSETTE_NORTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH]; + } + } else { + if (radians > RADIANS_NORTHWEST) { + LOG("XY : NORTH (%d) & WEST (%d)", axes.rosetteScancodes[ROSETTE_NORTH], axes.rosetteScancodes[ROSETTE_WEST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH]; + kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_WEST]; + } else { + LOG("XY : WEST (%d) & NORTH (%d)", axes.rosetteScancodes[ROSETTE_WEST], axes.rosetteScancodes[ROSETTE_NORTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST]; + kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_NORTH]; + } + } + break; + + case OCTANT_NORTH: + LOG("Y : NORTH (%d)", axes.rosetteScancodes[ROSETTE_NORTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH]; + break; + + case OCTANT_NORTHEAST: + if (axes.rosetteScancodes[ROSETTE_NORTHEAST] >= 0) { + LOG("XY : NORTHEAST (%d)", axes.rosetteScancodes[ROSETTE_NORTHEAST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTHEAST]; + } else if (axes.rosetteScancodes[ROSETTE_NORTH] < 0) { + if (radians < RADIANS_NORTHEAST) { + LOG("IGNORING Y (NORTH) ..."); + kpad.scancodes[REPEAT_AXIS] = -1; + } else { + LOG("X : EAST (%d)", axes.rosetteScancodes[ROSETTE_EAST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST]; + } + } else if (axes.rosetteScancodes[ROSETTE_EAST] < 0) { + if (radians > RADIANS_NORTHEAST) { + LOG("IGNORING X (EAST) ..."); + kpad.scancodes[REPEAT_AXIS] = -1; + } else { + LOG("Y : NORTH (%d)", axes.rosetteScancodes[ROSETTE_NORTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH]; + } + } else { + if (radians < RADIANS_NORTHEAST) { + LOG("XY : NORTH (%d) & EAST (%d)", axes.rosetteScancodes[ROSETTE_NORTH], axes.rosetteScancodes[ROSETTE_EAST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_NORTH]; + kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_EAST]; + } else { + LOG("XY : EAST (%d) & NORTH (%d)", axes.rosetteScancodes[ROSETTE_EAST], axes.rosetteScancodes[ROSETTE_NORTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST]; + kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_NORTH]; + } + } + break; + + case OCTANT_WEST: + LOG("Y : WEST (%d)", axes.rosetteScancodes[ROSETTE_WEST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST]; + break; + + case OCTANT_EAST: + LOG("Y : EAST (%d)", axes.rosetteScancodes[ROSETTE_EAST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST]; + break; + + case OCTANT_SOUTHWEST: + if (axes.rosetteScancodes[ROSETTE_SOUTHWEST] >= 0) { + LOG("XY : SOUTHWEST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHWEST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHWEST]; + } else if (axes.rosetteScancodes[ROSETTE_SOUTH] < 0) { + if (radians < RADIANS_SOUTHWEST) { + kpad.scancodes[REPEAT_AXIS] = -1; + LOG("IGNORING Y (SOUTH) ..."); + } else { + LOG("X : WEST (%d)", axes.rosetteScancodes[ROSETTE_WEST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST]; + } + } else if (axes.rosetteScancodes[ROSETTE_WEST] < 0) { + if (radians > RADIANS_SOUTHWEST) { + kpad.scancodes[REPEAT_AXIS] = -1; + LOG("IGNORING X (WEST) ..."); + } else { + LOG("Y : SOUTH (%d)", axes.rosetteScancodes[ROSETTE_SOUTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH]; + } + } else { + if (radians < RADIANS_SOUTHWEST) { + LOG("XY : SOUTH (%d) & WEST (%d)", axes.rosetteScancodes[ROSETTE_SOUTH], axes.rosetteScancodes[ROSETTE_WEST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH]; + kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_WEST]; + } else { + LOG("XY : WEST (%d) & SOUTH (%d)", axes.rosetteScancodes[ROSETTE_WEST], axes.rosetteScancodes[ROSETTE_SOUTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_WEST]; + kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_SOUTH]; + } + } + break; + + case OCTANT_SOUTH: + LOG("Y : SOUTH (%d)", axes.rosetteScancodes[ROSETTE_SOUTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH]; + break; + + case OCTANT_SOUTHEAST: + if (axes.rosetteScancodes[ROSETTE_SOUTHEAST] >= 0) { + LOG("XY : SOUTHEAST (%d)", axes.rosetteScancodes[ROSETTE_SOUTHEAST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTHEAST]; + } else if (axes.rosetteScancodes[ROSETTE_SOUTH] < 0) { + if (radians > RADIANS_SOUTHEAST) { + LOG("IGNORING Y (SOUTH) ..."); + kpad.scancodes[REPEAT_AXIS] = -1; + } else { + LOG("X : EAST (%d)", axes.rosetteScancodes[ROSETTE_EAST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST]; + } + } else if (axes.rosetteScancodes[ROSETTE_EAST] < 0) { + if (radians < RADIANS_SOUTHEAST) { + LOG("IGNORING X (EAST) ..."); + kpad.scancodes[REPEAT_AXIS] = -1; + } else { + LOG("Y : SOUTH (%d)", axes.rosetteScancodes[ROSETTE_SOUTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH]; + } + } else { + if (radians > RADIANS_SOUTHEAST) { + LOG("XY : SOUTH (%d) & EAST (%d)", axes.rosetteScancodes[ROSETTE_SOUTH], axes.rosetteScancodes[ROSETTE_EAST]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_SOUTH]; + kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_EAST]; + } else { + LOG("XY : EAST (%d) & SOUTH (%d)", axes.rosetteScancodes[ROSETTE_EAST], axes.rosetteScancodes[ROSETTE_SOUTH]); + kpad.scancodes[REPEAT_AXIS] = axes.rosetteScancodes[ROSETTE_EAST]; + kpad.scancodes[REPEAT_AXIS_ALT] = axes.rosetteScancodes[ROSETTE_SOUTH]; + } + } + break; + + default: + assert(false && "should not happen"); + break; + } + + } while (0); + + kpad.axisTiming = true; +} + +static void touchkpad_axisUp(int dx, int dy) { + touchkpad_axisMove(dx, dy); + kpad.axisCurrentOctant = ORIGIN; + + int scancode = kpad.scancodes[REPEAT_AXIS]; + kpad.scancodes[REPEAT_AXIS] = -1; + if (scancode < 0) { + scancode = kpad.scancodes[REPEAT_AXIS_ALT]; + kpad.scancodes[REPEAT_AXIS_ALT] = -1; + } + + c_keys_handle_input(scancode, /*pressed:*/true, /*ASCII:*/false); + c_keys_handle_input(scancode, /*pressed:*/false, /*ASCII:*/false); + kpad.axisTiming = false; + + // if no other scancodes, REPEAT_AXIS_ALT (if non-negative) will be fired once remaining in callback +} + +// ---------------------------------------------------------------------------- +// button key state + +static void touchkpad_setCurrButtonValue(touchjoy_button_type_t theButtonChar, int theButtonScancode) { + if (theButtonChar >= 0) { + kpad.currButtonDisplayChar = theButtonChar; + kpad.scancodes[REPEAT_BUTTON] = theButtonScancode; + } else { + kpad.currButtonDisplayChar = ' '; + kpad.scancodes[REPEAT_BUTTON] = -1; + } +} + +static uint8_t touchkpad_buttonPress(void) { + if (kpad.scancodes[REPEAT_BUTTON] >= 0) { + LOG("->BUTT : %d/'%c'", kpad.scancodes[REPEAT_BUTTON], kpad.currButtonDisplayChar); + clock_gettime(CLOCK_MONOTONIC, &kpad.timingBegins[REPEAT_BUTTON]); + c_keys_handle_input(kpad.scancodes[REPEAT_BUTTON], /*pressed:*/true, /*ASCII:*/false); + kpad.buttonTiming = true; + keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback; + } + return kpad.currButtonDisplayChar; +} + +static void touchkpad_buttonRelease(void) { + kpad.scancodes[REPEAT_BUTTON] = -1; + kpad.buttonTiming = false; + c_keys_handle_input(kpad.scancodes[REPEAT_BUTTON], /*pressed:*/false, /*ASCII:*/false); +} + +// ---------------------------------------------------------------------------- + +__attribute__((constructor(CTOR_PRIORITY_EARLY))) +static void _init_gltouchjoy_kpad(void) { + LOG("Registering OpenGL software touch joystick (keypad variant)"); + + for (unsigned int i=0; i