diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index ef095fa0f87a8..4b344b8ca9770 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -22,6 +22,16 @@ #ifdef SDL_PLATFORM_ANDROID +// Uncomment this so that gamepad events are first sent as commands to main SDLThread +// #define SDL_ANDROID_GAMEPAD_AS_RPC + +// Uncomment this to log commands sent between SDLActivity and main SDLThread +// #define DEBUG_RPC + +// Uncomment this to log messages entering and exiting methods in this file +// #define DEBUG_JNI + + #include "SDL_android.h" #include "../../events/SDL_events_c.h" @@ -31,6 +41,7 @@ #include "../../video/android/SDL_androidpen.h" #include "../../video/android/SDL_androidvideo.h" #include "../../video/android/SDL_androidwindow.h" +#include "../../video/android/SDL_androidevents.h" #include "../../joystick/android/SDL_sysjoystick_c.h" #include "../../haptic/android/SDL_syshaptic_c.h" #include "../../hidapi/android/hid.h" @@ -54,11 +65,6 @@ #define SDL_JAVA_CONTROLLER_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function) #define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function) CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function) -// Audio encoding definitions -#define ENCODING_PCM_8BIT 3 -#define ENCODING_PCM_16BIT 2 -#define ENCODING_PCM_FLOAT 4 - // Java class SDLActivity JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetVersion)( JNIEnv *env, jclass cls); @@ -347,9 +353,6 @@ static JNINativeMethod SDLControllerManager_tab[] = { { "nativeRemoveHaptic", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic) } }; -// Uncomment this to log messages entering and exiting methods in this file -// #define DEBUG_JNI - static void checkJNIReady(void); /******************************************************************************* @@ -434,11 +437,215 @@ static void Internal_Android_Destroy_AssetManager(void); static AAssetManager *asset_manager = NULL; static jobject javaAssetManagerRef = 0; -static SDL_Mutex *Android_ActivityMutex = NULL; -static SDL_Mutex *Android_LifecycleMutex = NULL; -static SDL_Semaphore *Android_LifecycleEventSem = NULL; -static SDL_AndroidLifecycleEvent Android_LifecycleEvents[SDL_NUM_ANDROID_LIFECYCLE_EVENTS]; -static int Android_NumLifecycleEvents; +static SDL_Semaphore *Android_PauseSem = NULL; +static SDL_Semaphore *Android_ResumeSem = NULL; +static SDL_Semaphore *Android_BlockOnPauseSem = NULL; + +// RPC commands. from SDLActivity thread to C Thread +typedef enum { + + // SDLActivity_tab + + // RPC_cmd_nativeGetVersion, + // RPC_cmd_nativeInitMainThread, + // RPC_cmd_nativeCleanupMainThread, + // RPC_cmd_nativeRunMain, + RPC_cmd_onNativeDropFile, + RPC_cmd_nativeSetScreenResolution, + RPC_cmd_onNativeResize, + RPC_cmd_onNativeSurfaceCreated, + RPC_cmd_onNativeSurfaceChanged, + RPC_cmd_onNativeSurfaceDestroyed, + RPC_cmd_onNativeScreenKeyboardShown, + RPC_cmd_onNativeScreenKeyboardHidden, + RPC_cmd_onNativeKeyDown, + RPC_cmd_onNativeKeyUp, + RPC_cmd_onNativeSoftReturnKey, + RPC_cmd_onNativeKeyboardFocusLost, + RPC_cmd_onNativeTouch, + RPC_cmd_onNativePinchStart, + RPC_cmd_onNativePinchUpdate, + RPC_cmd_onNativePinchEnd, + RPC_cmd_onNativeMouse, + RPC_cmd_onNativePen, + RPC_cmd_onNativeAccel, + RPC_cmd_onNativeClipboardChanged, + RPC_cmd_nativeLowMemory, + RPC_cmd_onNativeLocaleChanged, + RPC_cmd_onNativeDarkModeChanged, + RPC_cmd_nativeSendQuit, +// onDestroy / inverse of nativeSetupJNI +// RPC_cmd_nativeQuit, + RPC_cmd_nativePause, + RPC_cmd_nativePause_CancelSem, + RPC_cmd_nativeResume, + RPC_cmd_nativeResume_CancelSem, + RPC_cmd_WakeUp, + RPC_cmd_nativeFocusChanged, +// there are not returned void and so, they cannot really be defered to C Thread: +// RPC_cmd_nativeGetHint, +// RPC_cmd_nativeGetHintBoolean, + +// special case with recreate activity: +// RPC_cmd_nativeSetenv, +// + RPC_cmd_nativeSetNaturalOrientation, + RPC_cmd_onNativeRotationChanged, + RPC_cmd_onNativeInsetsChanged, + RPC_cmd_nativeAddTouch, + RPC_cmd_nativePermissionResult, + +// only used within the SDLActivity thread: +// RPC_cmd_nativeAllowRecreateActivity, +// RPC_cmd_nativeCheckSDLThreadCounter, + RPC_cmd_onNativeFileDialog, + + // SDLInputConnection_tab + RPC_cmd_nativeCommitText, + RPC_cmd_nativeGenerateScancodeForUnichar, + + // SDLControllerManager_tab +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_cmd_onNativePadDown, + RPC_cmd_onNativePadUp, + RPC_cmd_onNativeJoy, + RPC_cmd_onNativeHat, + RPC_cmd_onNativeJoySensor, + RPC_cmd_nativeAddJoystick, + RPC_cmd_nativeRemoveJoystick, + RPC_cmd_nativeAddHaptic, + RPC_cmd_nativeRemoveHaptic, +#endif + + // SDLAudioManager_tab + RPC_cmd_nativeAddAudioDevice, + RPC_cmd_nativeRemoveAudioDevice + + // RPC TODO HID ? see HIDDeviceManager_tab + +} RPC_cmd_t; + + +#ifdef DEBUG_RPC + +static const char *cmd2Str(RPC_cmd_t cmd) { + switch (cmd) { +#define CASE(x) case RPC_cmd_ ## x: return #x; + // SDLActivity_tab + CASE(onNativeDropFile); + CASE(nativeSetScreenResolution); + CASE(onNativeResize); + CASE(onNativeSurfaceCreated); + CASE(onNativeSurfaceChanged); + CASE(onNativeSurfaceDestroyed); + CASE(onNativeScreenKeyboardShown); + CASE(onNativeScreenKeyboardHidden); + CASE(onNativeKeyDown); + CASE(onNativeKeyUp); + CASE(onNativeSoftReturnKey); + CASE(onNativeKeyboardFocusLost); + CASE(onNativeTouch); + CASE(onNativePinchStart); + CASE(onNativePinchUpdate); + CASE(onNativePinchEnd); + CASE(onNativeMouse); + CASE(onNativePen); + CASE(onNativeAccel); + CASE(onNativeClipboardChanged); + CASE(nativeLowMemory); + CASE(onNativeLocaleChanged); + CASE(onNativeDarkModeChanged); + CASE(nativeSendQuit); + CASE(nativePause); + CASE(nativePause_CancelSem); + CASE(nativeResume); + CASE(nativeResume_CancelSem); + CASE(WakeUp); + CASE(nativeFocusChanged); + CASE(nativeSetNaturalOrientation); + CASE(onNativeRotationChanged); + CASE(onNativeInsetsChanged); + CASE(nativeAddTouch); + CASE(nativePermissionResult); + CASE(onNativeFileDialog); + + // SDLInputConnection_tab + CASE(nativeCommitText); + CASE(nativeGenerateScancodeForUnichar); + + // SDLAudioManager_tab + CASE(nativeAddAudioDevice); + CASE(nativeRemoveAudioDevice); + +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + // SDLControllerManager_tab + CASE(onNativePadDown); + CASE(onNativePadUp); + CASE(onNativeJoy); + CASE(onNativeHat); + CASE(onNativeJoySensor); + CASE(nativeAddJoystick); + CASE(nativeRemoveJoystick); + CASE(nativeAddHaptic); + CASE(nativeRemoveHaptic); +#endif + +#undef CASE + default: + return "unknown"; + } +} +#endif + + +// RPC TODO: enable timestamp ? +#define RPC_Send \ + /* data.timestamp = SDL_GetTicks(); */ \ + RPC_Send__(&data, data.cmd, sizeof(data), false); \ + +#if 0 + +#define RPC_SendWithPriority \ + /* data.timestamp = SDL_GetTicks(); */ \ + ret_send = RPC_Send__(&data, data.cmd, sizeof(data), true); \ + +#endif + +#define RPC_Add(foo) data.foo = foo; + +#define RPC_AddString(foo) \ + const char *utf##foo = (*env)->GetStringUTFChars(env, foo, NULL); \ + data.foo = SDL_strdup(utf##foo); \ + (*env)->ReleaseStringUTFChars(env, foo, utf##foo); \ + +#define RPC_Prepare(foo) \ + RPC_data_##foo##_t data; \ + data.cmd = RPC_cmd_##foo; \ + +#define RPC_SendWithoutData(foo) \ + RPC_data_t data; \ + data.cmd = RPC_cmd_##foo; \ + data.timestamp = SDL_GetTicks(); \ + RPC_Send__(&data, data.cmd, sizeof(data), false); \ + +// returning value +#define RPC_SendWithPriorityWithoutData(foo) \ + ({ \ + RPC_data_t data; \ + data.cmd = RPC_cmd_##foo; \ + data.timestamp = SDL_GetTicks(); \ + RPC_Send__(&data, data.cmd, sizeof(data), true); \ + }) \ + +static bool RPC_Send__(void *data, RPC_cmd_t cmd, int len, bool priority); +static void RPC_Init(); + +// header for command without data needed +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; +} RPC_data_t; + /******************************************************************************* Functions called by JNI @@ -623,6 +830,10 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl // Start with a clean slate SDL_ClearError(); + + RPC_Init(); + + /* * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this @@ -636,26 +847,17 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to found a JavaVM"); } - /* Use a mutex to prevent concurrency issues between Java Activity and Native thread code, when using 'Android_Window'. - * (Eg. Java sending Touch events, while native code is destroying the main SDL_Window. ) - */ - if (!Android_ActivityMutex) { - Android_ActivityMutex = SDL_CreateMutex(); // Could this be created twice if onCreate() is called a second time ? - } - - if (!Android_ActivityMutex) { - __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ActivityMutex mutex"); - } +#define ALLOC_SEM(foo) \ + foo = SDL_CreateSemaphore(0); \ + if (! foo) { \ + __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create " #foo "semaphore"); \ + } \ - Android_LifecycleMutex = SDL_CreateMutex(); - if (!Android_LifecycleMutex) { - __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleMutex mutex"); - } + ALLOC_SEM(Android_PauseSem); + ALLOC_SEM(Android_ResumeSem); + ALLOC_SEM(Android_BlockOnPauseSem); - Android_LifecycleEventSem = SDL_CreateSemaphore(0); - if (!Android_LifecycleEventSem) { - __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleEventSem semaphore"); - } +#undef ALLOC_SEM mActivityClass = (jclass)((*env)->NewGlobalRef(env, cls)); @@ -920,296 +1122,351 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, // Do not issue an exit or the whole application will terminate instead of just the SDL thread // exit(status); - return status; -} - -static int FindLifecycleEvent(SDL_AndroidLifecycleEvent event) -{ - for (int index = 0; index < Android_NumLifecycleEvents; ++index) { - if (Android_LifecycleEvents[index] == event) { - return index; - } - } - return -1; -} - -static void RemoveLifecycleEvent(int index) -{ - if (index < Android_NumLifecycleEvents - 1) { - SDL_memmove(&Android_LifecycleEvents[index], &Android_LifecycleEvents[index+1], (Android_NumLifecycleEvents - index - 1) * sizeof(Android_LifecycleEvents[index])); - } - --Android_NumLifecycleEvents; -} - -void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event) -{ - SDL_LockMutex(Android_LifecycleMutex); - { - int index; - bool add_event = true; - - switch (event) { - case SDL_ANDROID_LIFECYCLE_WAKE: - // We don't need more than one wake queued - index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_WAKE); - if (index >= 0) { - add_event = false; - } - break; - case SDL_ANDROID_LIFECYCLE_PAUSE: - // If we have a resume queued, just stay in the paused state - index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME); - if (index >= 0) { - RemoveLifecycleEvent(index); - add_event = false; - } - break; - case SDL_ANDROID_LIFECYCLE_RESUME: - // If we have a pause queued, just stay in the resumed state - index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE); - if (index >= 0) { - RemoveLifecycleEvent(index); - add_event = false; - } - break; - case SDL_ANDROID_LIFECYCLE_LOWMEMORY: - // We don't need more than one low memory event queued - index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY); - if (index >= 0) { - add_event = false; - } - break; - case SDL_ANDROID_LIFECYCLE_DESTROY: - // Remove all other events, we're done! - while (Android_NumLifecycleEvents > 0) { - RemoveLifecycleEvent(0); - } - break; - default: - SDL_assert(!"Sending unexpected lifecycle event"); - add_event = false; - break; - } + // Signal semaphores so that SDLActivity thread is not blocked + SDL_SignalSemaphore(Android_PauseSem); + SDL_SignalSemaphore(Android_ResumeSem); - if (add_event) { - SDL_assert(Android_NumLifecycleEvents < SDL_arraysize(Android_LifecycleEvents)); - Android_LifecycleEvents[Android_NumLifecycleEvents++] = event; - SDL_SignalSemaphore(Android_LifecycleEventSem); - } - } - SDL_UnlockMutex(Android_LifecycleMutex); + return status; } -bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS) -{ - bool got_event = false; - - while (!got_event && SDL_WaitSemaphoreTimeoutNS(Android_LifecycleEventSem, timeoutNS)) { - SDL_LockMutex(Android_LifecycleMutex); - { - if (Android_NumLifecycleEvents > 0) { - *event = Android_LifecycleEvents[0]; - RemoveLifecycleEvent(0); - got_event = true; - } - } - SDL_UnlockMutex(Android_LifecycleMutex); - } - return got_event; -} -void Android_LockActivityMutex(void) -{ - SDL_LockMutex(Android_ActivityMutex); -} +// Drop file -void Android_UnlockActivityMutex(void) -{ - SDL_UnlockMutex(Android_ActivityMutex); -} +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + char *filename; +} RPC_data_onNativeDropFile_t; -// Drop file JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)( JNIEnv *env, jclass jcls, jstring filename) { - const char *path = (*env)->GetStringUTFChars(env, filename, NULL); - SDL_SendDropFile(NULL, NULL, path); - (*env)->ReleaseStringUTFChars(env, filename, path); - SDL_SendDropComplete(NULL); + RPC_Prepare(onNativeDropFile); + RPC_AddString(filename); + RPC_Send; } // Set screen resolution +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int surfaceWidth; + int surfaceHeight; + int deviceWidth; + int deviceHeight; + float density; + float rate; +} RPC_data_nativeSetScreenResolution_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)( JNIEnv *env, jclass jcls, jint surfaceWidth, jint surfaceHeight, jint deviceWidth, jint deviceHeight, jfloat density, jfloat rate) { - SDL_LockMutex(Android_ActivityMutex); - - Android_SetScreenResolution(surfaceWidth, surfaceHeight, deviceWidth, deviceHeight, density, rate); - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(nativeSetScreenResolution); + RPC_Add(surfaceWidth); + RPC_Add(surfaceHeight); + RPC_Add(deviceWidth); + RPC_Add(deviceHeight); + RPC_Add(density); + RPC_Add(rate); + RPC_Send; } // Resize JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)( JNIEnv *env, jclass jcls) { - SDL_LockMutex(Android_ActivityMutex); - - if (Android_Window) { - Android_SendResize(Android_Window); - } - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_SendWithoutData(onNativeResize); } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int orientation; +} RPC_data_nativeSetNaturalOrientation_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetNaturalOrientation)( JNIEnv *env, jclass jcls, jint orientation) { - displayNaturalOrientation = (SDL_DisplayOrientation)orientation; + RPC_Prepare(nativeSetNaturalOrientation); + RPC_Add(orientation); + RPC_Send; } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int rotation; +} RPC_data_onNativeRotationChanged_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)( JNIEnv *env, jclass jcls, jint rotation) { - SDL_LockMutex(Android_ActivityMutex); - - if (displayNaturalOrientation == SDL_ORIENTATION_LANDSCAPE) { - rotation += 90; - } - - switch (rotation % 360) { - case 0: - displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT; - break; - case 90: - displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE; - break; - case 180: - displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT_FLIPPED; - break; - case 270: - displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED; - break; - default: - displayCurrentOrientation = SDL_ORIENTATION_UNKNOWN; - break; - } + RPC_Prepare(onNativeRotationChanged); + RPC_Add(rotation); + RPC_Send; +} - Android_SetOrientation(displayCurrentOrientation); - SDL_UnlockMutex(Android_ActivityMutex); -} +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int left; + int right; + int top; + int bottom; +} RPC_data_onNativeInsetsChanged_t; JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)( JNIEnv *env, jclass jcls, jint left, jint right, jint top, jint bottom) { - SDL_LockMutex(Android_ActivityMutex); - - Android_SetWindowSafeAreaInsets(left, right, top, bottom); - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(onNativeInsetsChanged); + RPC_Add(left); + RPC_Add(right); + RPC_Add(top); + RPC_Add(bottom); + RPC_Send; } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int touchId; + char *name; +} RPC_data_nativeAddTouch_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)( JNIEnv *env, jclass cls, jint touchId, jstring name) { - const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); + RPC_Prepare(nativeAddTouch); + RPC_Add(touchId); + RPC_AddString(name); + RPC_Send; +} - SDL_AddTouch(Android_ConvertJavaTouchID(touchId), - SDL_TOUCH_DEVICE_DIRECT, utfname); - (*env)->ReleaseStringUTFChars(env, name, utfname); -} +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + bool recording; + char *name; + int device_id; +} RPC_data_nativeAddAudioDevice_t; + JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, jint device_id) { #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES - if (SDL_GetCurrentAudioDriver() != NULL) { - void *handle = (void *)((size_t)device_id); - if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) { - const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL); - SDL_AddAudioDevice(recording, SDL_strdup(utf8name), NULL, handle); - (*env)->ReleaseStringUTFChars(env, name, utf8name); - } - } + RPC_Prepare(nativeAddAudioDevice); + RPC_Add(recording); + RPC_AddString(name); + RPC_Add(device_id); + RPC_Send; #endif } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + bool recording; + int device_id; +} RPC_data_nativeRemoveAudioDevice_t; + JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jint device_id) { #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES - if (SDL_GetCurrentAudioDriver() != NULL) { - SDL_Log("Removing device with handle %d, recording %d", device_id, recording); - SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id))); - } + RPC_Prepare(nativeRemoveAudioDevice); + RPC_Add(recording); + RPC_Add(device_id); + RPC_Send; #endif } // Paddown +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; + int keycode; + int scancode; +} RPC_data_onNativePadDown_t; + JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( JNIEnv *env, jclass jcls, jint device_id, jint keycode, jint scancode) { #ifdef SDL_JOYSTICK_ANDROID - return Android_OnPadDown(device_id, keycode, scancode); + int button = Android_keycode_to_SDL(keycode); + if (button < 0) { + button = Android_scancode_to_SDL(scancode); + } + if (button >= 0) { +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(onNativePadDown); + RPC_Add(device_id); + RPC_Add(keycode); + RPC_Send; +#else + Android_OnPadDown(device_id, keycode, scancode); +#endif + return true; + } + return false; #else return false; #endif // SDL_JOYSTICK_ANDROID } // Padup +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; + int keycode; + int scancode; +} RPC_data_onNativePadUp_t; + JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( JNIEnv *env, jclass jcls, jint device_id, jint keycode, jint scancode) { #ifdef SDL_JOYSTICK_ANDROID - return Android_OnPadUp(device_id, keycode, scancode); + int button = Android_keycode_to_SDL(keycode); + if (button < 0) { + button = Android_scancode_to_SDL(scancode); + } + if (button >= 0) { +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(onNativePadUp); + RPC_Add(device_id); + RPC_Add(keycode); + RPC_Send; +#else + Android_OnPadUp(device_id, keycode, scancode); +#endif + return true; + } + return false; #else return false; #endif // SDL_JOYSTICK_ANDROID } + // Joy +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; + int axis; + float value; +} RPC_data_onNativeJoy_t; + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)( JNIEnv *env, jclass jcls, jint device_id, jint axis, jfloat value) { #ifdef SDL_JOYSTICK_ANDROID +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(onNativeJoy); + RPC_Add(device_id); + RPC_Add(axis); + RPC_Add(value); + RPC_Send; +#else Android_OnJoy(device_id, axis, value); -#endif // SDL_JOYSTICK_ANDROID +#endif +#endif } // POV Hat +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; + int hat_id; + int x; + int y; +} RPC_data_onNativeHat_t; + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( JNIEnv *env, jclass jcls, jint device_id, jint hat_id, jint x, jint y) { #ifdef SDL_JOYSTICK_ANDROID +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(onNativeHat); + RPC_Add(device_id); + RPC_Add(hat_id); + RPC_Add(x); + RPC_Add(y); + RPC_Send; +#else Android_OnHat(device_id, hat_id, x, y); -#endif // SDL_JOYSTICK_ANDROID +#endif +#endif } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; + int sensor_type; + Uint64 sensor_timestamp; + float x; + float y; + float z; +} RPC_data_onNativeJoySensor_t; + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoySensor)( JNIEnv *env, jclass jcls, jint device_id, jint sensor_type, jlong sensor_timestamp, jfloat x, jfloat y, jfloat z) { #ifdef SDL_JOYSTICK_ANDROID +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(onNativeJoySensor); + RPC_Add(device_id); + RPC_Add(sensor_type); + RPC_Add(sensor_timestamp); + RPC_Add(x); + RPC_Add(y); + RPC_Add(z); + RPC_Send; +#else // In Java there's no Uint64 type, so pass Sint64 as if it was Uint64. Android_OnJoySensor(device_id, sensor_type, sensor_timestamp, x, y, z); +#endif #endif // SDL_JOYSTICK_ANDROID } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; + char *device_name; + char *device_desc; + int vendor_id; + int product_id; + int button_mask; + int naxes; + int axis_mask; + int nhats; + bool can_rumble; + bool has_rgb_led; + bool has_accelerometer; + bool has_gyroscope; +} RPC_data_nativeAddJoystick_t; + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name, jstring device_desc, @@ -1218,169 +1475,166 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( jboolean has_accelerometer, jboolean has_gyroscope) { #ifdef SDL_JOYSTICK_ANDROID +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(nativeAddJoystick); + RPC_Add(device_id); + RPC_AddString(device_name); + RPC_AddString(device_desc); + RPC_Add(vendor_id); + RPC_Add(product_id); + RPC_Add(button_mask); + RPC_Add(naxes); + RPC_Add(axis_mask); + RPC_Add(nhats); + RPC_Add(can_rumble); + RPC_Add(has_rgb_led); + RPC_Add(has_accelerometer); + RPC_Add(has_gyroscope); + RPC_Send; +#else const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); - Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, - can_rumble, has_rgb_led, has_accelerometer, has_gyroscope); + Android_AddJoystick(device_id, name, desc, + vendor_id, product_id, button_mask, naxes, + axis_mask, nhats, can_rumble, has_rgb_led, has_accelerometer, has_gyroscope); (*env)->ReleaseStringUTFChars(env, device_name, name); (*env)->ReleaseStringUTFChars(env, device_desc, desc); -#endif // SDL_JOYSTICK_ANDROID +#endif +#endif } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; +} RPC_data_nativeRemoveJoystick_t; + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( JNIEnv *env, jclass jcls, jint device_id) { #ifdef SDL_JOYSTICK_ANDROID +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(nativeRemoveJoystick); + RPC_Add(device_id); + RPC_Send; +#else Android_RemoveJoystick(device_id); -#endif // SDL_JOYSTICK_ANDROID +#endif +#endif } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; + char *device_name; +} RPC_data_nativeAddHaptic_t; + + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name) { #ifdef SDL_HAPTIC_ANDROID +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(nativeAddHaptic); + RPC_Add(device_name); + RPC_AddString(device_name); + RPC_Send; +#else const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); - Android_AddHaptic(device_id, name); - (*env)->ReleaseStringUTFChars(env, device_name, name); -#endif // SDL_HAPTIC_ANDROID +#endif +#endif } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int device_id; +} RPC_data_nativeRemoveHaptic_t; + JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( JNIEnv *env, jclass jcls, jint device_id) { #ifdef SDL_HAPTIC_ANDROID +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + RPC_Prepare(nativeRemoveHaptic); + RPC_Add(device_id); + RPC_Send; +#else Android_RemoveHaptic(device_id); #endif +#endif } // Called from surfaceCreated() JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(JNIEnv *env, jclass jcls) { - SDL_LockMutex(Android_ActivityMutex); - - if (Android_Window) { - SDL_WindowData *data = Android_Window->internal; + __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "onNativeSurfaceCreated"); - data->native_window = Android_JNI_GetNativeWindow(); - SDL_SetPointerProperty(SDL_GetWindowProperties(Android_Window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window); - if (data->native_window == NULL) { - SDL_SetError("Could not fetch native window from UI thread"); - } - } - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_SendWithoutData(onNativeSurfaceCreated); } // Called from surfaceChanged() JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv *env, jclass jcls) { - SDL_LockMutex(Android_ActivityMutex); - -#ifdef SDL_VIDEO_OPENGL_EGL - if (Android_Window && (Android_Window->flags & SDL_WINDOW_OPENGL)) { - SDL_VideoDevice *_this = SDL_GetVideoDevice(); - SDL_WindowData *data = Android_Window->internal; - - // If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here - if (data->egl_surface == EGL_NO_SURFACE) { - data->egl_surface = SDL_EGL_CreateSurface(_this, Android_Window, (NativeWindowType)data->native_window); - SDL_SetPointerProperty(SDL_GetWindowProperties(Android_Window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface); - } - - // GL Context handling is done in the event loop because this function is run from the Java thread - } -#endif - - if (Android_Window) { - Android_RestoreScreenKeyboard(SDL_GetVideoDevice(), Android_Window); - } + __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "onNativeSurfaceChanged"); - SDL_UnlockMutex(Android_ActivityMutex); + RPC_SendWithoutData(onNativeSurfaceChanged); } // Called from surfaceDestroyed() JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv *env, jclass jcls) { - int nb_attempt = 50; - -retry: - - SDL_LockMutex(Android_ActivityMutex); - - if (Android_Window) { - SDL_WindowData *data = Android_Window->internal; - - // Wait for Main thread being paused and context un-activated to release 'egl_surface' - if ((Android_Window->flags & SDL_WINDOW_OPENGL) && !data->backup_done) { - nb_attempt -= 1; - if (nb_attempt == 0) { - SDL_SetError("Try to release egl_surface with context probably still active"); - } else { - SDL_UnlockMutex(Android_ActivityMutex); - SDL_Delay(10); - goto retry; - } - } - -#ifdef SDL_VIDEO_OPENGL_EGL - if (data->egl_surface != EGL_NO_SURFACE) { - SDL_EGL_DestroySurface(SDL_GetVideoDevice(), data->egl_surface); - data->egl_surface = EGL_NO_SURFACE; - } -#endif - - if (data->native_window) { - ANativeWindow_release(data->native_window); - data->native_window = NULL; - } - - // GL Context handling is done in the event loop because this function is run from the Java thread - } + __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "onNativeSurfaceDestroyed"); - SDL_UnlockMutex(Android_ActivityMutex); + RPC_SendWithoutData(onNativeSurfaceDestroyed); } JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardShown)(JNIEnv *env, jclass jcls) { - SDL_SendScreenKeyboardShown(); + RPC_SendWithoutData(onNativeScreenKeyboardShown); } JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardHidden)(JNIEnv *env, jclass jcls) { - SDL_SendScreenKeyboardHidden(); + RPC_SendWithoutData(onNativeScreenKeyboardHidden); } // Keydown +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int keycode; +} RPC_data_onNativeKeyDown_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( JNIEnv *env, jclass jcls, jint keycode) { - SDL_LockMutex(Android_ActivityMutex); - - if (Android_Window) { - Android_OnKeyDown(keycode); - } - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(onNativeKeyDown); + RPC_Add(keycode); + RPC_Send; } // Keyup +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int keycode; +} RPC_data_onNativeKeyUp_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)( JNIEnv *env, jclass jcls, jint keycode) { - SDL_LockMutex(Android_ActivityMutex); - - if (Android_Window) { - Android_OnKeyUp(keycode); - } - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(onNativeKeyUp); + RPC_Add(keycode); + RPC_Send; } // Virtual keyboard return key might stop text input @@ -1388,7 +1642,9 @@ JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)( JNIEnv *env, jclass jcls) { if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) { - SDL_StopTextInput(Android_Window); + + RPC_SendWithoutData(onNativeSoftReturnKey); + return JNI_TRUE; } return JNI_FALSE; @@ -1398,108 +1654,147 @@ JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)( JNIEnv *env, jclass jcls) { - // Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget - SDL_StopTextInput(Android_Window); + RPC_SendWithoutData(onNativeKeyboardFocusLost); } // Touch +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int touch_device_id_in; + int pointer_finger_id_in; + int action; + float x; + float y; + float p; +} RPC_data_onNativeTouch_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( JNIEnv *env, jclass jcls, jint touch_device_id_in, jint pointer_finger_id_in, jint action, jfloat x, jfloat y, jfloat p) { - SDL_LockMutex(Android_ActivityMutex); - - Android_OnTouch(Android_Window, touch_device_id_in, pointer_finger_id_in, action, x, y, p); - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(onNativeTouch); + RPC_Add(touch_device_id_in); + RPC_Add(pointer_finger_id_in); + RPC_Add(action); + RPC_Add(x); + RPC_Add(y); + RPC_Add(p); + RPC_Send; } // Pinch JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)( JNIEnv *env, jclass jcls) { - SDL_LockMutex(Android_ActivityMutex); - - if (Android_Window) { - SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, Android_Window, 0); - } - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_SendWithoutData(onNativePinchStart); } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + float scale; +} RPC_data_onNativePinchUpdate_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)( JNIEnv *env, jclass jcls, jfloat scale) { - SDL_LockMutex(Android_ActivityMutex); - - if (Android_Window) { - SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, Android_Window, scale); - } - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(onNativePinchUpdate); + RPC_Add(scale); + RPC_Send; } JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)( JNIEnv *env, jclass jcls) { - SDL_LockMutex(Android_ActivityMutex); - - if (Android_Window) { - SDL_SendPinch(SDL_EVENT_PINCH_END, 0, Android_Window, 0); - } - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_SendWithoutData(onNativePinchEnd); } // Mouse +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int button; + int action; + float x; + float y; + bool relative; +} RPC_data_onNativeMouse_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( JNIEnv *env, jclass jcls, jint button, jint action, jfloat x, jfloat y, jboolean relative) { - SDL_LockMutex(Android_ActivityMutex); - - Android_OnMouse(Android_Window, button, action, x, y, relative); - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(onNativeMouse); + RPC_Add(button); + RPC_Add(action); + RPC_Add(x); + RPC_Add(y); + RPC_Add(relative); + RPC_Send; } // Pen +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int pen_id_in; + int device_type; + int button; + int action; + float x; + float y; + float p; +} RPC_data_onNativePen_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)( JNIEnv *env, jclass jcls, jint pen_id_in, jint device_type, jint button, jint action, jfloat x, jfloat y, jfloat p) { - SDL_LockMutex(Android_ActivityMutex); - - Android_OnPen(Android_Window, pen_id_in, device_type, button, action, x, y, p); - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(onNativePen); + RPC_Add(pen_id_in); + RPC_Add(device_type); + RPC_Add(button); + RPC_Add(action); + RPC_Add(x); + RPC_Add(y); + RPC_Add(p); + RPC_Send; } // Accelerometer +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + float x; + float y; + float z; +} RPC_data_onNativeAccel_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)( JNIEnv *env, jclass jcls, jfloat x, jfloat y, jfloat z) { - fLastAccelerometer[0] = x; - fLastAccelerometer[1] = y; - fLastAccelerometer[2] = z; - bHasNewData = true; + RPC_Prepare(onNativeAccel); + RPC_Add(x); + RPC_Add(y); + RPC_Add(z); + RPC_Send; } // Clipboard JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)( JNIEnv *env, jclass jcls) { - // TODO: compute new mime types - SDL_SendClipboardUpdate(false, NULL, 0); + RPC_SendWithoutData(onNativeClipboardChanged); } // Low memory JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)( JNIEnv *env, jclass cls) { - Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY); + RPC_SendWithoutData(nativeLowMemory); } /* Locale @@ -1507,21 +1802,32 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)( JNIEnv *env, jclass cls) { - SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED); + RPC_SendWithoutData(onNativeLocaleChanged); } // Dark mode +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + bool enabled; +} RPC_data_onNativeDarkModeChanged_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)( JNIEnv *env, jclass cls, jboolean enabled) { - Android_SetDarkMode(enabled); + RPC_Prepare(onNativeDarkModeChanged); + RPC_Add(enabled); + RPC_Send; } // Send Quit event to "SDLThread" thread JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)( JNIEnv *env, jclass cls) { - Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_DESTROY); + RPC_SendWithoutData(nativeSendQuit); + + // Un-block main C thread. Can be needed + SDL_SignalSemaphore(Android_BlockOnPauseSem); } // Activity ends @@ -1530,22 +1836,17 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)( { const char *str; - if (Android_ActivityMutex) { - SDL_DestroyMutex(Android_ActivityMutex); - Android_ActivityMutex = NULL; - } - - if (Android_LifecycleMutex) { - SDL_DestroyMutex(Android_LifecycleMutex); - Android_LifecycleMutex = NULL; - } +#define DESTROY_SEM(foo) \ + if (foo) { \ + SDL_DestroySemaphore(foo); \ + foo = NULL; \ + } \ - if (Android_LifecycleEventSem) { - SDL_DestroySemaphore(Android_LifecycleEventSem); - Android_LifecycleEventSem = NULL; - } + DESTROY_SEM(Android_PauseSem); + DESTROY_SEM(Android_ResumeSem); + DESTROY_SEM(Android_BlockOnPauseSem); - Android_NumLifecycleEvents = 0; +#undef DESTROY_SEM Internal_Android_Destroy_AssetManager(); @@ -1563,7 +1864,15 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)( { __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()"); - Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE); + bool ret = RPC_SendWithPriorityWithoutData(nativePause); + + // Wait for completion + if (ret) { + if (!SDL_WaitSemaphoreTimeoutNS(Android_PauseSem, SDL_MS_TO_NS(500))) { + SDL_Log("nativePause timeout expired"); + RPC_SendWithPriorityWithoutData(nativePause_CancelSem); + } + } } // Resume @@ -1572,38 +1881,82 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)( { __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()"); - Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME); + // First: send the resume CMD. + + bool ret = RPC_SendWithPriorityWithoutData(nativeResume); + + // Un-block main C thread. + SDL_SignalSemaphore(Android_BlockOnPauseSem); + + // It will consume the Resume CMD and get out of the PAUSE + + // Wait for completion + if (ret) { + if (!SDL_WaitSemaphoreTimeoutNS(Android_ResumeSem, SDL_MS_TO_NS(500))) { + SDL_Log("nativeResume timeout expired"); + RPC_SendWithPriorityWithoutData(nativeResume_CancelSem); + } + } + __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume() done"); +} + +void Android_WaitForResume() +{ + SDL_WaitSemaphoreTimeoutNS(Android_BlockOnPauseSem, -1); +} + +void Android_WakeUp() +{ + // TODO: Android doesn't need to wake up event + // RPC_SendWithoutData(WakeUp); } +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + bool hasFocus; +} RPC_data_nativeFocusChanged_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)( JNIEnv *env, jclass cls, jboolean hasFocus) { - SDL_LockMutex(Android_ActivityMutex); + RPC_Prepare(nativeFocusChanged); + RPC_Add(hasFocus); + RPC_Send; +} - if (Android_Window) { - __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeFocusChanged()"); - SDL_SendWindowEvent(Android_Window, (hasFocus ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST), 0, 0); - } - SDL_UnlockMutex(Android_ActivityMutex); -} +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + char *text; + int newCursorPosition; +} RPC_data_nativeCommitText_t; JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)( JNIEnv *env, jclass cls, jstring text, jint newCursorPosition) { - const char *utftext = (*env)->GetStringUTFChars(env, text, NULL); + RPC_Prepare(nativeCommitText); + RPC_AddString(text); + RPC_Add(newCursorPosition); + RPC_Send; +} - SDL_SendKeyboardText(utftext); - (*env)->ReleaseStringUTFChars(env, text, utftext); -} +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + char chUnicode; +} RPC_data_nativeGenerateScancodeForUnichar_t; JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)( JNIEnv *env, jclass cls, jchar chUnicode) { - SDL_SendKeyboardUnicodeKey(0, chUnicode); + RPC_Prepare(nativeGenerateScancodeForUnichar); + RPC_Add(chUnicode); + RPC_Send; } JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)( @@ -3156,25 +3509,21 @@ typedef struct NativePermissionRequestInfo static NativePermissionRequestInfo pending_permissions; +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + int requestCode; + bool result; +} RPC_data_nativePermissionResult_t; + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)( JNIEnv *env, jclass cls, jint requestCode, jboolean result) { - SDL_LockMutex(Android_ActivityMutex); - NativePermissionRequestInfo *prev = &pending_permissions; - for (NativePermissionRequestInfo *info = prev->next; info != NULL; info = info->next) { - if (info->request_code == (int) requestCode) { - prev->next = info->next; - SDL_UnlockMutex(Android_ActivityMutex); - info->callback(info->userdata, info->permission, result ? true : false); - SDL_free(info->permission); - SDL_free(info); - return; - } - prev = info; - } - - SDL_UnlockMutex(Android_ActivityMutex); + RPC_Prepare(nativePermissionResult); + RPC_Add(requestCode); + RPC_Add(result); + RPC_Send; } bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata) @@ -3202,10 +3551,8 @@ bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPerm info->callback = cb; info->userdata = userdata; - SDL_LockMutex(Android_ActivityMutex); info->next = pending_permissions.next; pending_permissions.next = info; - SDL_UnlockMutex(Android_ActivityMutex); JNIEnv *env = Android_JNI_GetEnv(); jstring jpermission = (*env)->NewStringUTF(env, permission); @@ -3317,73 +3664,60 @@ static struct AndroidFileDialog void *userdata; } mAndroidFileDialogData; -JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( +typedef struct { + RPC_cmd_t cmd; + Uint64 timestamp; + + int requestCode; + int filter; + + char **charFileList; + size_t count; + +} RPC_data_onNativeFileDialog_t; + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( JNIEnv *env, jclass jcls, jint requestCode, jobjectArray fileList, jint filter) { - if (mAndroidFileDialogData.callback != NULL && mAndroidFileDialogData.request_code == requestCode) { - if (fileList == NULL) { - SDL_SetError("Unspecified error in JNI"); - mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); - mAndroidFileDialogData.callback = NULL; - return; - } - - // Convert fileList to string - size_t count = (*env)->GetArrayLength(env, fileList); - char **charFileList = SDL_calloc(count + 1, sizeof(char *)); + size_t count = 0; + char **charFileList = NULL; - if (charFileList == NULL) { - mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); - mAndroidFileDialogData.callback = NULL; - return; - } + if (fileList) { + count = (*env)->GetArrayLength(env, fileList); + charFileList = SDL_calloc(count + 1, sizeof(char *)); - // Convert to UTF-8 - // TODO: Fix modified UTF-8 to classic UTF-8 - for (int i = 0; i < count; i++) { - jstring string = (*env)->GetObjectArrayElement(env, fileList, i); - if (!string) { - continue; - } + if (charFileList) { + // Convert to UTF-8 + // TODO: Fix modified UTF-8 to classic UTF-8 + for (int i = 0; i < count; i++) { + jstring string = (*env)->GetObjectArrayElement(env, fileList, i); + if (!string) { + continue; + } - const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL); - if (!utf8string) { - (*env)->DeleteLocalRef(env, string); - continue; - } + const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL); + if (!utf8string) { + (*env)->DeleteLocalRef(env, string); + continue; + } - char *newFile = SDL_strdup(utf8string); - if (!newFile) { + char *newFile = SDL_strdup(utf8string); + charFileList[i] = newFile; (*env)->ReleaseStringUTFChars(env, string, utf8string); (*env)->DeleteLocalRef(env, string); - mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); - mAndroidFileDialogData.callback = NULL; - - // Cleanup memory - for (int j = 0; j < i; j++) { - SDL_free(charFileList[j]); - } - SDL_free(charFileList); - return; } - - charFileList[i] = newFile; - (*env)->ReleaseStringUTFChars(env, string, utf8string); - (*env)->DeleteLocalRef(env, string); } - - // Call user-provided callback - SDL_ClearError(); - mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, (const char *const *) charFileList, filter); - mAndroidFileDialogData.callback = NULL; - - // Cleanup memory - for (int i = 0; i < count; i++) { - SDL_free(charFileList[i]); - } - SDL_free(charFileList); + } else { + SDL_SetError("Unspecified error in JNI"); } + + RPC_Prepare(onNativeFileDialog); + RPC_Add(requestCode); + RPC_Add(filter); + RPC_Add(count); + RPC_Add(charFileList); + RPC_Send; } bool Android_JNI_ShowFileDialog( @@ -3462,4 +3796,651 @@ bool Android_JNI_ShowFileDialog( return true; } + +#define RPC_PAGE_SIZE (1024 * 10) +// more size reserved for commands that should not be dropped +#define RPC_PAGE_SIZE_RESERVED (1024 * 1) +static char RPC_cmd_buffer_0[RPC_PAGE_SIZE + RPC_PAGE_SIZE_RESERVED]; +static char RPC_cmd_buffer_1[RPC_PAGE_SIZE + RPC_PAGE_SIZE_RESERVED]; + +static char *RPC_cmd_buffer = NULL; // RPC_cmd_buffer_0 or RPC_cmd_buffer_1 +static int RPC_cur_cmd_buffer = 0; // 0 or 1 +static int RPC_cur_size = 0; // used size +static int RPC_cur_nb_cmd = 0; // nb of cmds + +static SDL_Mutex *RPC_Mutex = NULL; + + +static void RPC_Init() +{ + RPC_cmd_buffer = RPC_cmd_buffer_0; + RPC_cur_cmd_buffer = 0; + RPC_cur_size = 0; + RPC_cur_nb_cmd = 0; + + if (!RPC_Mutex) { + RPC_Mutex = SDL_CreateMutex(); + } + + if (!RPC_Mutex) { + __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create RPC_Mutex mutex"); + } + +} + +static bool RPC_Send__(void *data, RPC_cmd_t cmd, int len, bool priority) +{ + bool ret = true; + SDL_LockMutex(RPC_Mutex); + + int max_size = (priority ? RPC_PAGE_SIZE + RPC_PAGE_SIZE_RESERVED : RPC_PAGE_SIZE); + + if (RPC_cur_size + len < max_size) { + SDL_memcpy(RPC_cmd_buffer + RPC_cur_size, data, len); + RPC_cur_size += len; + RPC_cur_nb_cmd += 1; +#ifdef DEBUG_RPC + __android_log_print(ANDROID_LOG_ERROR, "SDL", "send RPC of len %d, cmd=%s", len, cmd2Str(cmd)); +#endif + } else { +#ifdef DEBUG_RPC + __android_log_print(ANDROID_LOG_ERROR, "SDL", "cannot add RPC of len %d, cmd=%s", len, cmd2Str(cmd)); +#else + __android_log_print(ANDROID_LOG_ERROR, "SDL", "cannot add RPC of len %d, cmd=%u", len, cmd); +#endif + ret = false; + } + + SDL_UnlockMutex(RPC_Mutex); + return ret; +} + +void Android_LockActivityState(void) +{ + SDL_LockMutex(RPC_Mutex); +} + +void Android_UnlockActivityState(void) +{ + SDL_UnlockMutex(RPC_Mutex); +} + +void Android_PumpRPC(SDL_Window *window) +{ + int nb_cmd = 0; + char *cmd_buffer = NULL; + + SDL_LockMutex(RPC_Mutex); + + if (RPC_cur_nb_cmd) { + cmd_buffer = RPC_cmd_buffer; + nb_cmd = RPC_cur_nb_cmd; + + // Toggle cmd buffer + RPC_cur_size = 0; + RPC_cur_nb_cmd = 0; + + if (RPC_cur_cmd_buffer == 0) { + RPC_cur_cmd_buffer = 1; + RPC_cmd_buffer = RPC_cmd_buffer_1; + } else { + RPC_cur_cmd_buffer = 0; + RPC_cmd_buffer = RPC_cmd_buffer_0; + } + } + + SDL_UnlockMutex(RPC_Mutex); + + +#define RPC_Get(foo) \ + RPC_data_##foo##_t data; \ + len = sizeof (data); \ + SDL_memcpy(&data, cmd_buffer, len); \ + + +#define RPC_GetNoData \ + len = sizeof (RPC_data_t); \ + +#ifdef DEBUG_RPC + if (nb_cmd) { + SDL_Log("------------New set of RPC cmds, nb: %d", nb_cmd); + } +#endif + + while (nb_cmd--) { + RPC_cmd_t cmd; + int len; // don't initialize so that it can trigger some warning with `-Wsometimes-uninitialized` if RPC_Get*() has be forgotten + SDL_memcpy(&cmd, cmd_buffer, sizeof (cmd)); + +#ifdef DEBUG_RPC + SDL_Log("RPC cmd: %s", cmd2Str(cmd)); +#endif + + switch (cmd) { + + // ------------------------------ + // SDLActivity_tab + // ------------------------------ + case RPC_cmd_onNativeDropFile: + { + RPC_Get(onNativeDropFile); + SDL_SendDropFile(NULL, NULL, data.filename); + SDL_SendDropComplete(NULL); + SDL_free(data.filename); + } + break; + + case RPC_cmd_nativeSetScreenResolution: + { + RPC_Get(nativeSetScreenResolution); + Android_SetScreenResolution(data.surfaceWidth, data.surfaceHeight, data.deviceWidth, data.deviceHeight, data.density, data.rate); + } + break; + + case RPC_cmd_onNativeResize: + { + RPC_GetNoData; + Android_SendResize(window); + } + break; + + case RPC_cmd_onNativeSurfaceCreated: + { + RPC_GetNoData; + Android_NativeSurfaceCreated(window); + } + break; + + case RPC_cmd_onNativeSurfaceChanged: + { + RPC_GetNoData; + Android_NativeSurfaceChanged(window); + + Android_RestoreScreenKeyboard(SDL_GetVideoDevice(), window); + } + break; + + case RPC_cmd_onNativeSurfaceDestroyed: + { + RPC_GetNoData; + + // onNativeSurfaceDestroyed comes always after nativePause + // (see SDLActivity.java), so Paused has already been notified + + Android_NativeSurfaceDestroyed(window); + } + break; + + case RPC_cmd_nativeSendQuit: + { + RPC_GetNoData; + Android_OnDestroy(); + } + break; + + case RPC_cmd_nativePause: + { + RPC_GetNoData; + Android_OnPause(window); + + SDL_SignalSemaphore(Android_PauseSem); + } + break; + + // Cancel semaphore if timeout expired + case RPC_cmd_nativePause_CancelSem: + { + RPC_GetNoData; + + SDL_TryWaitSemaphore(Android_PauseSem); + } + break; + + case RPC_cmd_nativeResume: + { + RPC_GetNoData; + Android_OnResume(window); + + SDL_SignalSemaphore(Android_ResumeSem); + } + break; + + // Cancel semaphore if timeout expired + case RPC_cmd_nativeResume_CancelSem: + { + RPC_GetNoData; + + SDL_TryWaitSemaphore(Android_ResumeSem); + } + break; + + + case RPC_cmd_WakeUp: + { + RPC_GetNoData; + + // TODO: something here ? what's the use case ? clear blocOnPause ? + // + // this is not sent by SDLActivity. + + // Finish handling events quickly if we're not paused + } + break; + + case RPC_cmd_onNativeScreenKeyboardShown: + { + RPC_GetNoData; + SDL_SendScreenKeyboardShown(); + } + break; + + case RPC_cmd_onNativeScreenKeyboardHidden: + { + RPC_GetNoData; + SDL_SendScreenKeyboardHidden(); + } + break; + + case RPC_cmd_onNativeKeyDown: + { + RPC_Get(onNativeKeyDown); + Android_OnKeyDown(data.keycode); + } + break; + + case RPC_cmd_onNativeKeyUp: + { + RPC_Get(onNativeKeyUp); + Android_OnKeyUp(data.keycode); + } + break; + + case RPC_cmd_onNativeSoftReturnKey: + { + RPC_GetNoData; + SDL_StopTextInput(window); + } + break; + + case RPC_cmd_onNativeKeyboardFocusLost: + { + RPC_GetNoData; + + // Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget + SDL_StopTextInput(window); + } + break; + + case RPC_cmd_onNativeTouch: + { + RPC_Get(onNativeTouch); + Android_OnTouch(window, data.touch_device_id_in, data.pointer_finger_id_in, data.action, data.x, data.y, data.p); + } + break; + + case RPC_cmd_onNativePinchStart: + { + RPC_GetNoData; + SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, window, 0); + } + break; + + case RPC_cmd_onNativePinchUpdate: + { + RPC_Get(onNativePinchUpdate); + SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, window, data.scale); + } + break; + + case RPC_cmd_onNativePinchEnd: + { + RPC_GetNoData; + SDL_SendPinch(SDL_EVENT_PINCH_END, 0, window, 0); + } + break; + + case RPC_cmd_onNativeMouse: + { + RPC_Get(onNativeMouse); + Android_OnMouse(window, data.button, data.action, data.x, data.y, data.relative); + } + break; + + case RPC_cmd_onNativePen: + { + RPC_Get(onNativePen); + Android_OnPen(window, data.pen_id_in, data.device_type, data.button, data.action, data.x, data.y, data.p); + } + break; + + case RPC_cmd_onNativeAccel: + { + RPC_Get(onNativeAccel); + fLastAccelerometer[0] = data.x; + fLastAccelerometer[1] = data.y; + fLastAccelerometer[2] = data.z; + bHasNewData = true; + } + break; + + case RPC_cmd_onNativeClipboardChanged: + { + RPC_GetNoData; + + // TODO: compute new mime types + SDL_SendClipboardUpdate(false, NULL, 0); + } + break; + + case RPC_cmd_nativeLowMemory: + { + RPC_GetNoData; + + // SDLActivity always sent this event when going to pause. + // Prevent user to do anything if rendering is not available. + if (Android_RenderingAvailable()) { + SDL_SendAppEvent(SDL_EVENT_LOW_MEMORY); + } + } + break; + + case RPC_cmd_onNativeLocaleChanged: + { + RPC_GetNoData; + SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED); + } + break; + + case RPC_cmd_onNativeDarkModeChanged: + { + RPC_Get(onNativeDarkModeChanged); + Android_SetDarkMode(data.enabled); + } + break; + + case RPC_cmd_nativeFocusChanged: + { + RPC_Get(nativeFocusChanged); + __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeFocusChanged()"); + SDL_SendWindowEvent(window, (data.hasFocus ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST), 0, 0); + } + break; + + case RPC_cmd_nativeSetNaturalOrientation: + { + RPC_Get(nativeSetNaturalOrientation); + displayNaturalOrientation = (SDL_DisplayOrientation)data.orientation; + } + break; + + case RPC_cmd_onNativeRotationChanged: + { + RPC_Get(onNativeRotationChanged); + + if (displayNaturalOrientation == SDL_ORIENTATION_LANDSCAPE) { + data.rotation += 90; + } + + switch (data.rotation % 360) { + case 0: + displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT; + break; + case 90: + displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE; + break; + case 180: + displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT_FLIPPED; + break; + case 270: + displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED; + break; + default: + displayCurrentOrientation = SDL_ORIENTATION_UNKNOWN; + break; + } + + Android_SetOrientation(displayCurrentOrientation); + + } + break; + + case RPC_cmd_onNativeInsetsChanged: + { + RPC_Get(onNativeInsetsChanged); + Android_SetWindowSafeAreaInsets(window, data.left, data.right, data.top, data.bottom); + } + break; + + // This command is send both from SDLActivity + // and from SDLThread, through Android_InitTouch() -> Android_JNI_InitTouch() -> initTouch() (java) + case RPC_cmd_nativeAddTouch: + { + RPC_Get(nativeAddTouch); + SDL_AddTouch(Android_ConvertJavaTouchID(data.touchId), SDL_TOUCH_DEVICE_DIRECT, data.name); + SDL_free(data.name); + } + break; + + case RPC_cmd_nativePermissionResult: + { + RPC_Get(nativePermissionResult); + + NativePermissionRequestInfo *prev = &pending_permissions; + for (NativePermissionRequestInfo *info = prev->next; info != NULL; info = info->next) { + if (info->request_code == (int) data.requestCode) { + prev->next = info->next; + info->callback(info->userdata, info->permission, data.result ? true : false); + SDL_free(info->permission); + SDL_free(info); + + /* done */ + break; + } + prev = info; + } + + } + break; + + case RPC_cmd_onNativeFileDialog: + { + RPC_Get(onNativeFileDialog); + + if (mAndroidFileDialogData.callback != NULL && mAndroidFileDialogData.request_code == data.requestCode) { + + // Handle some error ... + if (!data.charFileList) { + mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); + mAndroidFileDialogData.callback = NULL; + goto onNativeFileDialog_end; + } else { + for (int i = 0; i < data.count; i++) { + if (!data.charFileList[i]) { + mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); + mAndroidFileDialogData.callback = NULL; + goto onNativeFileDialog_end; + } + } + } + + // Call user-provided callback + SDL_ClearError(); + mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, (const char *const *) data.charFileList, data.filter); + mAndroidFileDialogData.callback = NULL; + } + +onNativeFileDialog_end: + + // Cleanup memory + for (int i = 0; i < data.count; i++) { + SDL_free(data.charFileList[i]); + } + SDL_free(data.charFileList); + } + break; + + // ------------------------------ + // SDLInputConnection_tab + // ------------------------------ + case RPC_cmd_nativeCommitText: + { + RPC_Get(nativeCommitText); + SDL_SendKeyboardText(data.text); + SDL_free(data.text); + } + break; + + case RPC_cmd_nativeGenerateScancodeForUnichar: + { + RPC_Get(nativeGenerateScancodeForUnichar); + SDL_SendKeyboardUnicodeKey(0, data.chUnicode); + } + break; + + + // ------------------------------ + // SDLAudioManager_tab + // ------------------------------ + case RPC_cmd_nativeAddAudioDevice: + { + RPC_Get(nativeAddAudioDevice); +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES + if (SDL_GetCurrentAudioDriver() != NULL) { + void *handle = (void *)((size_t)data.device_id); + if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) { + SDL_AddAudioDevice(data.recording, SDL_strdup(data.name), NULL, handle); + } + } +#endif + SDL_free(data.name); + } + + break; + + case RPC_cmd_nativeRemoveAudioDevice: + { + RPC_Get(nativeRemoveAudioDevice); +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES + if (SDL_GetCurrentAudioDriver() != NULL) { + SDL_Log("Removing device with handle %d, recording %d", data.device_id, data.recording); + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)data.device_id))); + } +#endif + } + break; + +#ifdef SDL_ANDROID_GAMEPAD_AS_RPC + // ------------------------------ + // SDLControllerManager_tab + // ------------------------------ + case RPC_cmd_onNativePadDown: + { + RPC_Get(onNativePadDown); +#ifdef SDL_JOYSTICK_ANDROID + Android_OnPadDown(data.device_id, data.keycode, data.scancode); +#endif + } + break; + case RPC_cmd_onNativePadUp: + { + RPC_Get(onNativePadUp); +#ifdef SDL_JOYSTICK_ANDROID + Android_OnPadUp(data.device_id, data.keycode, data.scancode); +#endif + } + break; + + case RPC_cmd_onNativeJoy: + { + RPC_Get(onNativeJoy); +#ifdef SDL_JOYSTICK_ANDROID + Android_OnJoy(data.device_id, data.axis, data.value); +#endif + } + break; + + case RPC_cmd_onNativeHat: + { + RPC_Get(onNativeHat); +#ifdef SDL_JOYSTICK_ANDROID + Android_OnHat(data.device_id, data.hat_id, data.x, data.y); +#endif + } + break; + + case RPC_cmd_onNativeJoySensor: + { + RPC_Get(onNativeJoySensor); +#ifdef SDL_JOYSTICK_ANDROID + // In Java there's no Uint64 type, so pass Sint64 as if it was Uint64. + Android_OnJoySensor(data.device_id, data.sensor_type, data.sensor_timestamp, data.x, data.y, data.z); +#endif + } + break; + + case RPC_cmd_nativeAddJoystick: + { + RPC_Get(nativeAddJoystick); +#ifdef SDL_JOYSTICK_ANDROID + Android_AddJoystick(data.device_id, data.device_name, data.device_desc, + data.vendor_id, data.product_id, data.button_mask, data.naxes, + data.axis_mask, data.nhats, data.can_rumble, data.has_rgb_led, + data.has_accelerometer, data.has_gyroscope); +#endif + SDL_free(data.device_name); + SDL_free(data.device_desc); + + } + break; + + case RPC_cmd_nativeRemoveJoystick: + { + RPC_Get(nativeRemoveJoystick); + +#ifdef SDL_JOYSTICK_ANDROID + Android_RemoveJoystick(data.device_id); +#endif + + } + break; + + case RPC_cmd_nativeAddHaptic: + { + RPC_Get(nativeAddHaptic); + +#ifdef SDL_HAPTIC_ANDROID + Android_AddHaptic(data.device_id, data.device_name); +#endif + SDL_free(data.device_name); + + } + break; + + case RPC_cmd_nativeRemoveHaptic: + { + RPC_Get(nativeRemoveHaptic); +#ifdef SDL_HAPTIC_ANDROID + Android_RemoveHaptic(data.device_id); +#endif + + } + break; +#endif + + + // ------------------------------ + // HIDDeviceManager_tab + // ------------------------------ + + default: + len = 0; + SDL_Log("Error unknown RPC cmd %u", cmd); + + } + + cmd_buffer += len; + } + +} + #endif // SDL_PLATFORM_ANDROID diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index ec9d1dc863d3e..b9c9fd1c5a7d9 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -38,22 +38,8 @@ extern "C" { // this appears to be broken right now (on Android, not SDL, I think...?). #define ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 0 -// Life cycle -typedef enum -{ - SDL_ANDROID_LIFECYCLE_WAKE, - SDL_ANDROID_LIFECYCLE_PAUSE, - SDL_ANDROID_LIFECYCLE_RESUME, - SDL_ANDROID_LIFECYCLE_LOWMEMORY, - SDL_ANDROID_LIFECYCLE_DESTROY, - SDL_NUM_ANDROID_LIFECYCLE_EVENTS -} SDL_AndroidLifecycleEvent; - -void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event); -bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS); - -void Android_LockActivityMutex(void); -void Android_UnlockActivityMutex(void); +void Android_LockActivityState(void); +void Android_UnlockActivityState(void); void Android_SetAllowRecreateActivity(bool enabled); @@ -158,6 +144,11 @@ bool Android_JNI_ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, const SDL_DialogFileFilter *filters, int nfilters, SDL_FileDialogType type, bool multiple, const char *initialPath); +// Pump RPC commands +void Android_PumpRPC(SDL_Window *window); +void Android_WaitForResume(void); + + // Ends C function definitions when using C++ #ifdef __cplusplus /* *INDENT-OFF* */ diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 315a8b6d905a9..970dc97b30b7f 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -42,7 +42,6 @@ #include "../video/SDL_sysvideo.h" #ifdef SDL_PLATFORM_ANDROID -#include "../core/android/SDL_android.h" #include "../video/android/SDL_androidevents.h" #endif @@ -1130,7 +1129,7 @@ static void SDL_CutEvent(SDL_EventEntry *entry) static void SDL_SendWakeupEvent(void) { #ifdef SDL_PLATFORM_ANDROID - Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_WAKE); + Android_WakeUp(); #else SDL_VideoDevice *_this = SDL_GetVideoDevice(); if (_this == NULL || !_this->SendWakeupEvent) { @@ -1516,7 +1515,7 @@ static void SDL_PumpEventsInternal(bool push_sentinel) #ifdef SDL_PLATFORM_ANDROID // Android event processing is independent of the video subsystem - Android_PumpEvents(0); + Android_PumpEvents(); #else // Get events from the video subsystem SDL_VideoDevice *_this = SDL_GetVideoDevice(); @@ -1550,7 +1549,12 @@ void SDL_PumpEvents(void) bool SDL_PollEvent(SDL_Event *event) { - return SDL_WaitEventTimeoutNS(event, 0); + bool ret = SDL_WaitEventTimeoutNS(event, 0); +#ifdef SDL_PLATFORM_ANDROID + Android_BlockEventLoop(); +#endif + return ret; + } #ifndef SDL_PLATFORM_ANDROID @@ -1740,16 +1744,16 @@ bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS) return true; } - Uint64 delay = -1; + Uint64 delay = SDL_MS_TO_NS(10); if (timeoutNS > 0) { Uint64 now = SDL_GetTicksNS(); if (now >= expiration) { // Timeout expired and no events return false; } - delay = (expiration - now); + delay = SDL_min((expiration - now), delay); } - Android_PumpEvents(delay); + SDL_DelayNS(delay); } #else SDL_VideoDevice *_this = SDL_GetVideoDevice(); diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c index 265c32e391568..d3a4efd4a6cb7 100644 --- a/src/joystick/android/SDL_sysjoystick.c +++ b/src/joystick/android/SDL_sysjoystick.c @@ -66,7 +66,7 @@ static int numjoysticks = 0; * This code manipulation is done to get a sequential list of codes. * FIXME: This is only suited for the case where we use a fixed number of buttons determined by ANDROID_MAX_NBUTTONS */ -static int keycode_to_SDL(int keycode) +int Android_keycode_to_SDL(int keycode) { // FIXME: If this function gets too unwieldy in the future, replace with a lookup table int button = 0; @@ -172,7 +172,7 @@ static int keycode_to_SDL(int keycode) return button; } -static int scancode_to_SDL(int scancode) +int Android_scancode_to_SDL(int scancode) { int button = 0; switch (scancode) { @@ -226,9 +226,9 @@ bool Android_OnPadDown(int device_id, int keycode, int scancode) { Uint64 timestamp = SDL_GetTicksNS(); SDL_joylist_item *item; - int button = keycode_to_SDL(keycode); + int button = Android_keycode_to_SDL(keycode); if (button < 0) { - button = scancode_to_SDL(scancode); + button = Android_scancode_to_SDL(scancode); } if (button >= 0) { SDL_LockJoysticks(); @@ -249,9 +249,9 @@ bool Android_OnPadUp(int device_id, int keycode, int scancode) { Uint64 timestamp = SDL_GetTicksNS(); SDL_joylist_item *item; - int button = keycode_to_SDL(keycode); + int button = Android_keycode_to_SDL(keycode); if (button < 0) { - button = scancode_to_SDL(scancode); + button = Android_scancode_to_SDL(scancode); } if (button >= 0) { SDL_LockJoysticks(); diff --git a/src/joystick/android/SDL_sysjoystick_c.h b/src/joystick/android/SDL_sysjoystick_c.h index f99abb5e61bf0..e3fbf4635336d 100644 --- a/src/joystick/android/SDL_sysjoystick_c.h +++ b/src/joystick/android/SDL_sysjoystick_c.h @@ -28,6 +28,8 @@ #include "../SDL_sysjoystick.h" +extern int Android_keycode_to_SDL(int keycode); +extern int Android_scancode_to_SDL(int scancode); extern bool Android_OnPadDown(int device_id, int keycode, int scancode); extern bool Android_OnPadUp(int device_id, int keycode, int scancode); extern bool Android_OnJoy(int device_id, int axisnum, float value); diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c index 757baef0cacd4..7d2f4a8dcfd83 100644 --- a/src/main/SDL_main_callbacks.c +++ b/src/main/SDL_main_callbacks.c @@ -22,6 +22,10 @@ #include "SDL_internal.h" #include "SDL_main_callbacks.h" +#ifdef SDL_PLATFORM_ANDROID +#include "../video/android/SDL_androidevents.h" +#endif + static SDL_AppEvent_func SDL_main_event_callback; static SDL_AppIterate_func SDL_main_iteration_callback; static SDL_AppQuit_func SDL_main_quit_callback; @@ -123,6 +127,11 @@ SDL_AppResult SDL_IterateMainCallbacks(bool pump_events) if (pump_events) { SDL_PumpEvents(); } + +#ifdef SDL_PLATFORM_ANDROID + Android_BlockEventLoop(); +#endif + SDL_DispatchMainCallbackEvents(); SDL_AppResult rc = (SDL_AppResult)SDL_GetAtomicInt(&apprc); diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 9894d44678c9c..b12a117bc70c6 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -1080,7 +1080,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) } #ifdef SDL_PLATFORM_ANDROID - if (!Android_WaitActiveAndLockActivity()) { + if (!Android_WaitActiveAndLockActivity(window)) { return NULL; } #endif @@ -1260,7 +1260,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) SDL_renderers = renderer; #ifdef SDL_PLATFORM_ANDROID - Android_UnlockActivityMutex(); + Android_UnlockActivityState(); #endif SDL_ClearError(); @@ -1269,7 +1269,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) error: #ifdef SDL_PLATFORM_ANDROID - Android_UnlockActivityMutex(); + Android_UnlockActivityState(); #endif if (renderer) { diff --git a/src/video/android/SDL_androidevents.c b/src/video/android/SDL_androidevents.c index 4bdd717e121fe..40222bd146da6 100644 --- a/src/video/android/SDL_androidevents.c +++ b/src/video/android/SDL_androidevents.c @@ -33,10 +33,14 @@ #ifdef SDL_VIDEO_OPENGL_EGL -static void android_egl_context_restore(SDL_Window *window) +// Restore EGL context when we have both: +// nativeSurfaceCreated() +// and SDLActivity hasFocus(); +void Android_egl_context_restore(SDL_Window *window) { - if (window) { - SDL_WindowData *data = window->internal; + SDL_WindowData *data = window->internal; + + if (data->backup_done && data->egl_surface != EGL_NO_SURFACE) { SDL_GL_MakeCurrent(window, NULL); if (!SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context)) { // The context is no longer valid, create a new one @@ -54,24 +58,22 @@ static void android_egl_context_restore(SDL_Window *window) } } -static void android_egl_context_backup(SDL_Window *window) +static void Android_egl_context_backup(SDL_Window *window) { - if (window) { - int interval = 0; - // Keep a copy of the EGL Context so we can try to restore it when we resume - SDL_WindowData *data = window->internal; - data->egl_context = SDL_GL_GetCurrentContext(); - - // Save/Restore the swap interval / vsync - if (SDL_GL_GetSwapInterval(&interval)) { - data->has_swap_interval = 1; - data->swap_interval = interval; - } - - // We need to do this so the EGLSurface can be freed - SDL_GL_MakeCurrent(window, NULL); - data->backup_done = true; + int interval = 0; + // Keep a copy of the EGL Context so we can try to restore it when we resume + SDL_WindowData *data = window->internal; + data->egl_context = SDL_GL_GetCurrentContext(); + + // Save/Restore the swap interval / vsync + if (SDL_GL_GetSwapInterval(&interval)) { + data->has_swap_interval = 1; + data->swap_interval = interval; } + + // We need to do this so the EGLSurface can be freed + SDL_GL_MakeCurrent(window, NULL); + data->backup_done = true; } #endif @@ -103,6 +105,7 @@ static void Android_PauseAudio(void) static void Android_ResumeAudio(void) { + if (Android_PausedAudio) { OPENSLES_ResumeDevices(); AAUDIO_ResumeDevices(); @@ -110,7 +113,12 @@ static void Android_ResumeAudio(void) } } -static void Android_OnPause(void) +bool Android_RenderingAvailable() +{ + return !Android_Paused; +} + +void Android_OnPause(SDL_Window *window) { SDL_OnApplicationWillEnterBackground(); SDL_OnApplicationDidEnterBackground(); @@ -121,10 +129,8 @@ static void Android_OnPause(void) * was being queued. */ #ifdef SDL_VIDEO_OPENGL_EGL - if (Android_Window && !Android_Window->external_graphics_context) { - Android_LockActivityMutex(); - android_egl_context_backup(Android_Window); - Android_UnlockActivityMutex(); + if (window && !window->external_graphics_context) { + Android_egl_context_backup(window); } #endif @@ -136,7 +142,7 @@ static void Android_OnPause(void) Android_Paused = true; } -static void Android_OnResume(void) +void Android_OnResume(SDL_Window *window) { Android_Paused = false; @@ -146,22 +152,15 @@ static void Android_OnResume(void) #ifdef SDL_VIDEO_OPENGL_EGL // Restore the GL Context from here, as this operation is thread dependent - if (Android_Window && !Android_Window->external_graphics_context && !SDL_HasEvent(SDL_EVENT_QUIT)) { - Android_LockActivityMutex(); - android_egl_context_restore(Android_Window); - Android_UnlockActivityMutex(); + if (window && !window->external_graphics_context && !SDL_HasEvent(SDL_EVENT_QUIT)) { + Android_egl_context_restore(window); } #endif SDL_OnApplicationDidEnterForeground(); } -static void Android_OnLowMemory(void) -{ - SDL_SendAppEvent(SDL_EVENT_LOW_MEMORY); -} - -static void Android_OnDestroy(void) +void Android_OnDestroy(void) { // Make sure we unblock any audio processing before we quit Android_ResumeAudio(); @@ -176,86 +175,77 @@ static void Android_OnDestroy(void) Android_Destroyed = true; } -static void Android_HandleLifecycleEvent(SDL_AndroidLifecycleEvent event) +void Android_PumpEvents() { - switch (event) { - case SDL_ANDROID_LIFECYCLE_WAKE: - // Nothing to do, just return - break; - case SDL_ANDROID_LIFECYCLE_PAUSE: - Android_OnPause(); - break; - case SDL_ANDROID_LIFECYCLE_RESUME: - Android_OnResume(); - break; - case SDL_ANDROID_LIFECYCLE_LOWMEMORY: - Android_OnLowMemory(); - break; - case SDL_ANDROID_LIFECYCLE_DESTROY: - Android_OnDestroy(); - break; - default: - break; + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + SDL_Window *window = _this ? _this->windows : NULL; + + if (Android_Destroyed) { + return; } + + Android_PumpRPC(window); } -static Sint64 GetLifecycleEventTimeout(bool paused, Sint64 timeoutNS) + +// return 'true' if app has been blocked +bool Android_BlockEventLoop() { + if (Android_Destroyed) { + return false; + } + if (Android_Paused) { if (Android_BlockOnPause) { - timeoutNS = -1; - } else if (timeoutNS == 0) { - timeoutNS = SDL_MS_TO_NS(100); - } - } - return timeoutNS; -} + SDL_Log("BlockOnPause"); + Android_WaitForResume(); + SDL_Log("BlockOnPause...resume"); -void Android_PumpEvents(Sint64 timeoutNS) -{ - SDL_AndroidLifecycleEvent event; - bool paused = Android_Paused; - - while (!Android_Destroyed && - Android_WaitLifecycleEvent(&event, GetLifecycleEventTimeout(paused, timeoutNS))) { - Android_HandleLifecycleEvent(event); - - switch (event) { - case SDL_ANDROID_LIFECYCLE_WAKE: - // Finish handling events quickly if we're not paused - timeoutNS = 0; - break; - case SDL_ANDROID_LIFECYCLE_PAUSE: - // Finish handling events at the current timeout and return to process events one more time before blocking. - break; - case SDL_ANDROID_LIFECYCLE_RESUME: - // Finish handling events at the resume state timeout - paused = false; - break; - default: - break; + // app has been blocked. now we're unblocked. + // pump to get 'resume' command (eg RPC_cmd_nativeResume). + + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + SDL_Window *window = _this ? _this->windows : NULL; + Android_PumpRPC(window); + + return true; } } + + return false; } -bool Android_WaitActiveAndLockActivity(void) + +bool Android_WaitActiveAndLockActivity(SDL_Window *window) { - /* Make sure we have pumped all events so that Android_Paused state is correct */ - SDL_AndroidLifecycleEvent event; - while (!Android_Destroyed && Android_WaitLifecycleEvent(&event, 0)) { - Android_HandleLifecycleEvent(event); - } +retry: - while (Android_Paused && !Android_Destroyed) { - Android_PumpEvents(-1); - } + // Lock first. + // So no new lifecycle event comes in the meantimes from the SDLActivity, + // Hence SDLActivity won't change state. + Android_LockActivityState(); + + // Pump all events so that Android_Paused state is correct + Android_PumpRPC(window); if (Android_Destroyed) { SDL_SetError("Android activity has been destroyed"); + Android_UnlockActivityState(); return false; } - Android_LockActivityMutex(); + if (!Android_Paused) { + // SDLActivity is Active. return lock'ed + return true; + } else { + // Still Paused + // Unlock and wait for the SDLActivity to send new cycle events + // or for the user to move the app to foreground. + Android_UnlockActivityState(); + SDL_Delay(10); + goto retry; + } + return true; } diff --git a/src/video/android/SDL_androidevents.h b/src/video/android/SDL_androidevents.h index 7328f2c49ad11..c260907db1272 100644 --- a/src/video/android/SDL_androidevents.h +++ b/src/video/android/SDL_androidevents.h @@ -21,6 +21,16 @@ #include "SDL_internal.h" extern void Android_InitEvents(void); -extern void Android_PumpEvents(Sint64 timeoutNS); -extern bool Android_WaitActiveAndLockActivity(void); +extern void Android_PumpEvents(void); +extern bool Android_BlockEventLoop(void); +extern bool Android_WaitActiveAndLockActivity(SDL_Window *window); extern void Android_QuitEvents(void); +#ifdef SDL_VIDEO_OPENGL_EGL +void Android_egl_context_restore(SDL_Window *window); +#endif + +void Android_OnPause(SDL_Window *window); +void Android_OnResume(SDL_Window *window); +void Android_OnDestroy(void); +void Android_WakeUp(void); +bool Android_RenderingAvailable(void); diff --git a/src/video/android/SDL_androidgl.c b/src/video/android/SDL_androidgl.c index e9de25fce62a9..345fd5088dc80 100644 --- a/src/video/android/SDL_androidgl.c +++ b/src/video/android/SDL_androidgl.c @@ -49,13 +49,13 @@ SDL_GLContext Android_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *win { SDL_GLContext result; - if (!Android_WaitActiveAndLockActivity()) { + if (!Android_WaitActiveAndLockActivity(window)) { return NULL; } result = SDL_EGL_CreateContext(_this, window->internal->egl_surface); - Android_UnlockActivityMutex(); + Android_UnlockActivityState(); return result; } @@ -64,8 +64,6 @@ bool Android_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) { bool result; - Android_LockActivityMutex(); - /* The following two calls existed in the original Java code * If you happen to have a device that's affected by their removal, * please report to our bug tracker. -- Gabriel @@ -75,8 +73,6 @@ bool Android_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) _this->egl_data->eglWaitGL();*/ result = SDL_EGL_SwapBuffers(_this, window->internal->egl_surface); - Android_UnlockActivityMutex(); - return result; } diff --git a/src/video/android/SDL_androidkeyboard.c b/src/video/android/SDL_androidkeyboard.c index 82c87a7305163..cc18032946f10 100644 --- a/src/video/android/SDL_androidkeyboard.c +++ b/src/video/android/SDL_androidkeyboard.c @@ -451,7 +451,7 @@ void Android_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) void Android_RestoreScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) { - if (_this->screen_keyboard_shown) { + if (_this && _this->screen_keyboard_shown && window) { Android_ShowScreenKeyboard(_this, window, window->text_input_props); } } diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c index d868e6960e264..be9219c9532be 100644 --- a/src/video/android/SDL_androidvideo.c +++ b/src/video/android/SDL_androidvideo.c @@ -298,17 +298,22 @@ void Android_SendResize(SDL_Window *window) if (window) { SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, Android_SurfaceWidth, Android_SurfaceHeight); } + + // onNativeResize() should probably merged in to onNativeSurfaceChanged() so that + // both RPC commands are executed atomically without user app trying to render inbetween + // (and so Android_NativeSurfaceResized is called from Android_NativeSurfaceChanged()) + Android_NativeSurfaceResized(window); } -void Android_SetWindowSafeAreaInsets(int left, int right, int top, int bottom) +void Android_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom) { Android_SafeInsetLeft = left; Android_SafeInsetRight = right; Android_SafeInsetTop = top; Android_SafeInsetBottom = bottom; - if (Android_Window) { - SDL_SetWindowSafeAreaInsets(Android_Window, left, right, top, bottom); + if (window) { + SDL_SetWindowSafeAreaInsets(window, left, right, top, bottom); } } diff --git a/src/video/android/SDL_androidvideo.h b/src/video/android/SDL_androidvideo.h index 03bfb0165e281..c695265445fbb 100644 --- a/src/video/android/SDL_androidvideo.h +++ b/src/video/android/SDL_androidvideo.h @@ -30,7 +30,7 @@ extern void Android_SetScreenResolution(int surfaceWidth, int surfaceHeight, int extern void Android_SetFormat(int format_wanted, int format_got); extern void Android_SetOrientation(SDL_DisplayOrientation orientation); extern void Android_SendResize(SDL_Window *window); -extern void Android_SetWindowSafeAreaInsets(int left, int right, int top, int bottom); +extern void Android_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom); extern void Android_SetDarkMode(bool enabled); // Private display data diff --git a/src/video/android/SDL_androidwindow.c b/src/video/android/SDL_androidwindow.c index 4652b5f6ba005..fd32017c52df0 100644 --- a/src/video/android/SDL_androidwindow.c +++ b/src/video/android/SDL_androidwindow.c @@ -34,18 +34,32 @@ // Currently only one window -SDL_Window *Android_Window = NULL; +static bool window_created = false; bool Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *data; bool result = true; - if (!Android_WaitActiveAndLockActivity()) { + // Android_WaitActiveAndLockActivity() process some RPC: + // "onSurfaceCreated" that needs "window->internal" + // and also "nativeSetScreenResolution" + + data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + return false; + } + +#ifdef SDL_VIDEO_OPENGL_EGL + data->egl_surface = EGL_NO_SURFACE; +#endif + window->internal = data; + + if (!Android_WaitActiveAndLockActivity(window)) { return false; } - if (Android_Window) { + if (window_created) { result = SDL_SetError("Android only supports one window"); goto endfunction; } @@ -63,44 +77,23 @@ bool Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper SDL_SetMouseFocus(window); SDL_SetKeyboardFocus(window); - data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data)); - if (!data) { + if (!Android_NativeSurfaceCreated(window)) { result = false; goto endfunction; } - data->native_window = Android_JNI_GetNativeWindow(); - if (!data->native_window) { - SDL_free(data); - result = SDL_SetError("Could not fetch native window"); + if (!Android_NativeSurfaceChanged(window)) { + result = false; goto endfunction; } - SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window); - - /* Do not create EGLSurface for Vulkan window since it will then make the window - incompatible with vkCreateAndroidSurfaceKHR */ -#ifdef SDL_VIDEO_OPENGL_EGL - if (window->flags & SDL_WINDOW_OPENGL) { - data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->native_window); - - if (data->egl_surface == EGL_NO_SURFACE) { - ANativeWindow_release(data->native_window); - SDL_free(data); - result = false; - goto endfunction; - } - } - SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface); -#endif SDL_SetWindowSafeAreaInsets(window, Android_SafeInsetLeft, Android_SafeInsetRight, Android_SafeInsetTop, Android_SafeInsetBottom); - window->internal = data; - Android_Window = window; + window_created = true; endfunction: - Android_UnlockActivityMutex(); + Android_UnlockActivityState(); return result; } @@ -112,54 +105,48 @@ void Android_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) SDL_FullscreenResult Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen) { - Android_LockActivityMutex(); - - if (window == Android_Window) { - SDL_WindowData *data; - int old_w, old_h, new_w, new_h; + SDL_WindowData *data; + int old_w, old_h, new_w, new_h; - // If the window is being destroyed don't change visible state - if (!window->is_destroying) { - Android_JNI_SetWindowStyle(fullscreen); - } + // If the window is being destroyed don't change visible state + if (!window->is_destroying) { + Android_JNI_SetWindowStyle(fullscreen); + } - /* Ensure our size matches reality after we've executed the window style change. - * - * It is possible that we've set width and height to the full-size display, but on - * Samsung DeX or Chromebooks or other windowed Android environments, our window may - * still not be the full display size. - */ - if (!SDL_IsDeXMode() && !SDL_IsChromebook()) { - goto endfunction; - } + /* Ensure our size matches reality after we've executed the window style change. + * + * It is possible that we've set width and height to the full-size display, but on + * Samsung DeX or Chromebooks or other windowed Android environments, our window may + * still not be the full display size. + */ + if (!SDL_IsDeXMode() && !SDL_IsChromebook()) { + goto endfunction; + } - data = window->internal; - if (!data || !data->native_window) { - if (data && !data->native_window) { - SDL_SetError("Missing native window"); - } - goto endfunction; + data = window->internal; + if (!data || !data->native_window) { + if (data && !data->native_window) { + SDL_SetError("Missing native window"); } + goto endfunction; + } - old_w = window->w; - old_h = window->h; + old_w = window->w; + old_h = window->h; - new_w = ANativeWindow_getWidth(data->native_window); - new_h = ANativeWindow_getHeight(data->native_window); + new_w = ANativeWindow_getWidth(data->native_window); + new_h = ANativeWindow_getHeight(data->native_window); - if (new_w < 0 || new_h < 0) { - SDL_SetError("ANativeWindow_getWidth/Height() fails"); - } + if (new_w < 0 || new_h < 0) { + SDL_SetError("ANativeWindow_getWidth/Height() fails"); + } - if (old_w != new_w || old_h != new_h) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, new_w, new_h); - } + if (old_w != new_w || old_h != new_h) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, new_w, new_h); } endfunction: - Android_UnlockActivityMutex(); - return SDL_FULLSCREEN_SUCCEEDED; } @@ -176,29 +163,108 @@ void Android_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool void Android_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) { - Android_LockActivityMutex(); + window_created = false; - if (window == Android_Window) { - Android_Window = NULL; - if (window->internal) { - SDL_WindowData *data = window->internal; + if (window->internal) { + Android_NativeSurfaceDestroyed(window); + SDL_free(window->internal); + window->internal = NULL; + } +} + + +// Those functions called from RPC onNativeSurface{Created,Changed,Destroyed}() +// and SDL_{Create,Destroy}Window(); + +bool Android_NativeSurfaceCreated(SDL_Window *window) +{ + if (window) { + SDL_WindowData *data = window->internal; + + data->native_window = Android_JNI_GetNativeWindow(); + SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window); + if (data->native_window == NULL) { + SDL_SetError("Could not fetch native window"); + return false; + } + return true; + } + return false; +} + + +// Resize surface +void Android_NativeSurfaceResized(SDL_Window *window) +{ + if (window) { + SDL_WindowData *data = window->internal; + if (data->native_window) { + int format = 0; + int new_w = ANativeWindow_getWidth(data->native_window); + int new_h = ANativeWindow_getHeight(data->native_window); + ANativeWindow_setBuffersGeometry(data->native_window, new_w, new_h, format); + } + } +} + +bool Android_NativeSurfaceChanged(SDL_Window *window) +{ + if (window) { #ifdef SDL_VIDEO_OPENGL_EGL - if (data->egl_surface != EGL_NO_SURFACE) { - SDL_EGL_DestroySurface(_this, data->egl_surface); + /* Do not create EGLSurface for Vulkan window since it will then make the window + incompatible with vkCreateAndroidSurfaceKHR */ + if (window->flags & SDL_WINDOW_OPENGL) { + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + SDL_WindowData *data = window->internal; + + // Make sure native_window is valid + if (!data->native_window) { + if (!Android_NativeSurfaceCreated(window)) { + return false; + } + } + + // If the surface has been previously destroyed by onNativeSurfaceDestroyed + // or if it is the first time, recreate it. + if (data->egl_surface == EGL_NO_SURFACE) { + data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->native_window); + SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface); } -#endif - if (data->native_window) { - ANativeWindow_release(data->native_window); + if (data->egl_surface == EGL_NO_SURFACE) { + if (data->native_window) { + ANativeWindow_release(data->native_window); + data->native_window = 0; + } + return false; } - SDL_free(window->internal); - window->internal = NULL; } +#endif + return true; } - Android_UnlockActivityMutex(); + return false; +} + +void Android_NativeSurfaceDestroyed(SDL_Window *window) +{ + if (window) { + SDL_WindowData *data = window->internal; + +#ifdef SDL_VIDEO_OPENGL_EGL + if (data->egl_surface != EGL_NO_SURFACE) { + SDL_EGL_DestroySurface(SDL_GetVideoDevice(), data->egl_surface); + data->egl_surface = EGL_NO_SURFACE; + } +#endif + + if (data->native_window) { + ANativeWindow_release(data->native_window); + data->native_window = NULL; + } + } } #endif // SDL_VIDEO_DRIVER_ANDROID diff --git a/src/video/android/SDL_androidwindow.h b/src/video/android/SDL_androidwindow.h index cc24da96ebe16..1ef50082e9dfa 100644 --- a/src/video/android/SDL_androidwindow.h +++ b/src/video/android/SDL_androidwindow.h @@ -33,7 +33,6 @@ extern void Android_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Android_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable); extern void Android_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern SDL_Window *Android_Window; struct SDL_WindowData { @@ -48,4 +47,9 @@ struct SDL_WindowData }; +bool Android_NativeSurfaceCreated(SDL_Window *window); +bool Android_NativeSurfaceChanged(SDL_Window *window); +void Android_NativeSurfaceDestroyed(SDL_Window *window); +void Android_NativeSurfaceResized(SDL_Window *window); + #endif // SDL_androidwindow_h_