/* android_nosdl.c - android backend Copyright (C) 2016-2019 mittorn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "platform/platform.h" #if !defined(XASH_DEDICATED) #include "input.h" #include "client.h" #include "sound.h" #include "platform/android/android_priv.h" #include "errno.h" #include <pthread.h> #include <sys/prctl.h> #ifndef JNICALL #define JNICALL // a1ba: workaround for my IDE, where Java files are not included #define JNIEXPORT #endif convar_t *android_sleep; static const int s_android_scantokey[] = { 0, K_LEFTARROW, K_RIGHTARROW, K_AUX26, K_ESCAPE, // 0 K_AUX26, K_AUX25, '0', '1', '2', // 5 '3', '4', '5', '6', '7', // 10 '8', '9', '*', '#', K_UPARROW, // 15 K_DOWNARROW, K_LEFTARROW, K_RIGHTARROW, K_ENTER, K_AUX32, // 20 K_AUX31, K_AUX29, K_AUX28, K_AUX27, 'a', // 25 'b', 'c', 'd', 'e', 'f', // 30 'g', 'h', 'i', 'j', 'k', // 35 'l', 'm', 'n', 'o', 'p', // 40 'q', 'r', 's', 't', 'u', // 45 'v', 'w', 'x', 'y', 'z', // 50 ',', '.', K_ALT, K_ALT, K_SHIFT, // 55 K_SHIFT, K_TAB, K_SPACE, 0, 0, // 60 0, K_ENTER, K_BACKSPACE, '`', '-', // 65 '=', '[', ']', '\\', ';', // 70 '\'', '/', '@', K_KP_NUMLOCK, 0, // 75 0, '+', '`', 0, 0, // 80 0, 0, 0, 0, 0, // 85 0, 0, K_PGUP, K_PGDN, 0, // 90 0, K_AUX1, K_AUX2, K_AUX14, K_AUX3, // 95 K_AUX4, K_AUX15, K_AUX6, K_AUX7, K_JOY1, // 100 K_JOY2, K_AUX10, K_AUX11, K_ESCAPE, K_ESCAPE, // 105 0, K_ESCAPE, K_DEL, K_CTRL, K_CTRL, // 110 K_CAPSLOCK, 0, 0, 0, 0, // 115 0, K_PAUSE, K_HOME, K_END, K_INS, // 120 0, 0, 0, 0, 0, // 125 0, K_F1, K_F2, K_F3, K_F4, // 130 K_F5, K_F6, K_F7, K_F8, K_F9, // 135 K_F10, K_F11, K_F12, K_KP_NUMLOCK, K_KP_INS, // 140 K_KP_END, K_KP_DOWNARROW, K_KP_PGDN, K_KP_LEFTARROW, K_KP_5, // 145 K_KP_RIGHTARROW,K_KP_HOME, K_KP_UPARROW, K_KP_PGUP, K_KP_SLASH, // 150 0, K_KP_MINUS, K_KP_PLUS, K_KP_DEL, ',', // 155 K_KP_ENTER, '=', '(', ')' }; #define ANDROID_MAX_EVENTS 64 #define MAX_FINGERS 10 typedef enum event_type { event_touch_down = 0, event_touch_up, event_touch_move, // compatible with touchEventType event_key_down, event_key_up, event_set_pause, event_resize, event_joyhat, event_joyball, event_joybutton, event_joyaxis, event_joyadd, event_joyremove, event_onpause, event_ondestroy, event_onresume, event_onfocuschange, } eventtype_t; typedef struct touchevent_s { float x; float y; float dx; float dy; } touchevent_t; typedef struct joyball_s { short xrel; short yrel; byte ball; } joyball_t; typedef struct joyhat_s { byte hat; byte key; } joyhat_t; typedef struct joyaxis_s { short val; byte axis; } joyaxis_t; typedef struct joybutton_s { int down; byte button; } joybutton_t; typedef struct keyevent_s { int code; } keyevent_t; typedef struct event_s { eventtype_t type; int arg; union { touchevent_t touch; joyhat_t hat; joyball_t ball; joyaxis_t axis; joybutton_t button; keyevent_t key; }; } event_t; typedef struct finger_s { float x, y; qboolean down; } finger_t; static struct { pthread_mutex_t mutex; // this mutex is locked while not running frame, used for events synchronization pthread_mutex_t framemutex; // this mutex is locked while engine is running and unlocked while it reading events, used for pause in background. event_t queue[ANDROID_MAX_EVENTS]; volatile int count; finger_t fingers[MAX_FINGERS]; char inputtext[256]; float mousex, mousey; } events = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }; struct jnimethods_s jni; struct jnimouse_s jnimouse; #define Android_Lock() pthread_mutex_lock(&events.mutex); #define Android_Unlock() pthread_mutex_unlock(&events.mutex); #define Android_PushEvent() Android_Unlock() typedef void (*pfnChangeGame)( const char *progname ); int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ); /* ======================== Android_AllocEvent Lock event queue and return pointer to next event. Caller must do Android_PushEvent() to unlock queue after setting parameters. ======================== */ event_t *Android_AllocEvent( void ) { Android_Lock(); if( events.count == ANDROID_MAX_EVENTS ) { events.count--; //override last event __android_log_print( ANDROID_LOG_ERROR, "Xash", "Too many events!!!" ); } return &events.queue[ events.count++ ]; } /* ===================================================== JNI callbacks On application start, setenv and onNativeResize called from ui thread to set up engine configuration nativeInit called directly from engine thread and will not return until exit. These functions may be called from other threads at any time: nativeKey nativeTouch onNativeResize nativeString nativeSetPause ===================================================== */ #define VA_ARGS(...) , ##__VA_ARGS__ // GCC extension #define DECLARE_JNI_INTERFACE( ret, name, ... ) \ JNIEXPORT ret JNICALL Java_su_xash_engine_XashActivity_##name( JNIEnv *env, jclass clazz VA_ARGS(__VA_ARGS__) ) DECLARE_JNI_INTERFACE( int, nativeInit, jobject array ) { int i; int argc; int status; /* Prepare the arguments. */ int len = (*env)->GetArrayLength(env, array); char** argv = calloc( 1 + len + 1, sizeof( char ** )); argc = 0; argv[argc++] = strdup("app_process"); for (i = 0; i < len; ++i) { const char* utf; char* arg = NULL; jstring string = (*env)->GetObjectArrayElement(env, array, i); if (string) { utf = (*env)->GetStringUTFChars(env, string, 0); if (utf) { arg = strdup(utf); (*env)->ReleaseStringUTFChars(env, string, utf); } (*env)->DeleteLocalRef(env, string); } if (!arg) { arg = strdup(""); } argv[argc++] = arg; } argv[argc] = NULL; prctl(PR_SET_DUMPABLE, 1); /* Init callbacks. */ jni.env = env; jni.actcls = (*env)->FindClass(env, "su/xash/engine/XashActivity"); jni.enableTextInput = (*env)->GetStaticMethodID(env, jni.actcls, "showKeyboard", "(I)V"); jni.vibrate = (*env)->GetStaticMethodID(env, jni.actcls, "vibrate", "(I)V" ); jni.messageBox = (*env)->GetStaticMethodID(env, jni.actcls, "messageBox", "(Ljava/lang/String;Ljava/lang/String;)V"); jni.notify = (*env)->GetStaticMethodID(env, jni.actcls, "engineThreadNotify", "()V"); jni.setTitle = (*env)->GetStaticMethodID(env, jni.actcls, "setTitle", "(Ljava/lang/String;)V"); jni.setIcon = (*env)->GetStaticMethodID(env, jni.actcls, "setIcon", "(Ljava/lang/String;)V"); jni.getAndroidId = (*env)->GetStaticMethodID(env, jni.actcls, "getAndroidID", "()Ljava/lang/String;"); jni.saveID = (*env)->GetStaticMethodID(env, jni.actcls, "saveID", "(Ljava/lang/String;)V"); jni.loadID = (*env)->GetStaticMethodID(env, jni.actcls, "loadID", "()Ljava/lang/String;"); jni.showMouse = (*env)->GetStaticMethodID(env, jni.actcls, "showMouse", "(I)V"); jni.shellExecute = (*env)->GetStaticMethodID(env, jni.actcls, "shellExecute", "(Ljava/lang/String;)V"); jni.swapBuffers = (*env)->GetStaticMethodID(env, jni.actcls, "swapBuffers", "()V"); jni.toggleEGL = (*env)->GetStaticMethodID(env, jni.actcls, "toggleEGL", "(I)V"); jni.createGLContext = (*env)->GetStaticMethodID(env, jni.actcls, "createGLContext", "([I[I)Z"); jni.getGLAttribute = (*env)->GetStaticMethodID(env, jni.actcls, "getGLAttribute", "(I)I"); jni.deleteGLContext = (*env)->GetStaticMethodID(env, jni.actcls, "deleteGLContext", "()Z"); jni.getSelectedPixelFormat = (*env)->GetStaticMethodID(env, jni.actcls, "getSelectedPixelFormat", "()I"); jni.getSurface = (*env)->GetStaticMethodID(env, jni.actcls, "getNativeSurface", "()Landroid/view/Surface;"); /* Run the application. */ status = Host_Main( argc, argv, getenv("XASH3D_GAMEDIR"), false, NULL ); /* Release the arguments. */ for (i = 0; i < argc; ++i) free(argv[i]); free(argv); return status; } DECLARE_JNI_INTERFACE( void, onNativeResize, jint width, jint height ) { event_t *event; if( !width || !height ) return; jni.width=width, jni.height=height; // alloc update event to change screen size event = Android_AllocEvent(); event->type = event_resize; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeQuit ) { } DECLARE_JNI_INTERFACE( void, nativeSetPause, jint pause ) { event_t *event = Android_AllocEvent(); event->type = event_set_pause; event->arg = pause; Android_PushEvent(); // if pause enabled, hold engine by locking frame mutex. // Engine will stop after event reading and will not continue untill unlock if( android_sleep && android_sleep->value ) { if( pause ) pthread_mutex_lock( &events.framemutex ); else pthread_mutex_unlock( &events.framemutex ); } } DECLARE_JNI_INTERFACE( void, nativeUnPause ) { // UnPause engine before sending critical events if( android_sleep && android_sleep->value ) pthread_mutex_unlock( &events.framemutex ); } DECLARE_JNI_INTERFACE( void, nativeKey, jint down, jint code ) { event_t *event; if( code < 0 ) { event = Android_AllocEvent(); event->arg = (-code) & 255; event->type = down?event_key_down:event_key_up; Android_PushEvent(); } else { if( code >= ( sizeof( s_android_scantokey ) / sizeof( s_android_scantokey[0] ) ) ) { Con_DPrintf( "nativeKey: unknown Android key %d\n", code ); return; } if( !s_android_scantokey[code] ) { Con_DPrintf( "nativeKey: unmapped Android key %d\n", code ); return; } event = Android_AllocEvent(); event->type = down?event_key_down:event_key_up; event->arg = s_android_scantokey[code]; Android_PushEvent(); } } DECLARE_JNI_INTERFACE( void, nativeString, jobject string ) { char* str = (char *) (*env)->GetStringUTFChars(env, string, NULL); Android_Lock(); strncat( events.inputtext, str, 256 ); Android_Unlock(); (*env)->ReleaseStringUTFChars(env, string, str); } #ifdef SOFTFP_LINK DECLARE_JNI_INTERFACE( void, nativeTouch, jint finger, jint action, jfloat x, jfloat y ) __attribute__((pcs("aapcs"))); #endif DECLARE_JNI_INTERFACE( void, nativeTouch, jint finger, jint action, jfloat x, jfloat y ) { float dx, dy; event_t *event; // if something wrong with android event if( finger > MAX_FINGERS ) return; // not touch action? if( !( action >=0 && action <= 2 ) ) return; // 0.0f .. 1.0f x /= jni.width; y /= jni.height; if( action ) dx = x - events.fingers[finger].x, dy = y - events.fingers[finger].y; else dx = dy = 0.0f; events.fingers[finger].x = x, events.fingers[finger].y = y; // check if we should skip some events if( ( action == 2 ) && ( !dx && !dy ) ) return; if( ( action == 0 ) && events.fingers[finger].down ) return; if( ( action == 1 ) && !events.fingers[finger].down ) return; if( action == 2 && !events.fingers[finger].down ) action = 0; if( action == 0 ) events.fingers[finger].down = true; else if( action == 1 ) events.fingers[finger].down = false; event = Android_AllocEvent(); event->arg = finger; event->type = action; event->touch.x = x; event->touch.y = y; event->touch.dx = dx; event->touch.dy = dy; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeBall, jint id, jbyte ball, jshort xrel, jshort yrel ) { event_t *event = Android_AllocEvent(); event->type = event_joyball; event->arg = id; event->ball.ball = ball; event->ball.xrel = xrel; event->ball.yrel = yrel; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeHat, jint id, jbyte hat, jbyte key, jboolean down ) { event_t *event = Android_AllocEvent(); static byte engineKeys; if( !key ) engineKeys = 0; // centered; if( down ) engineKeys |= key; else engineKeys &= ~key; event->type = event_joyhat; event->arg = id; event->hat.hat = hat; event->hat.key = engineKeys; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeAxis, jint id, jbyte axis, jshort val ) { event_t *event = Android_AllocEvent(); event->type = event_joyaxis; event->arg = id; event->axis.axis = axis; event->axis.val = val; __android_log_print(ANDROID_LOG_VERBOSE, "Xash", "axis %i %i", axis, val ); Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeJoyButton, jint id, jbyte button, jboolean down ) { event_t *event = Android_AllocEvent(); event->type = event_joybutton; event->arg = id; event->button.button = button; event->button.down = down; __android_log_print(ANDROID_LOG_VERBOSE, "Xash", "button %i", button ); Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeJoyAdd, jint id ) { event_t *event = Android_AllocEvent(); event->type = event_joyadd; event->arg = id; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeJoyDel, jint id ) { event_t *event = Android_AllocEvent(); event->type = event_joyremove; event->arg = id; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeOnResume ) { event_t *event = Android_AllocEvent(); event->type = event_onresume; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeOnFocusChange ) { event_t *event = Android_AllocEvent(); event->type = event_onfocuschange; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeOnPause ) { event_t *event = Android_AllocEvent(); event->type = event_onpause; Android_PushEvent(); } DECLARE_JNI_INTERFACE( void, nativeOnDestroy ) { event_t *event = Android_AllocEvent(); event->type = event_ondestroy; Android_PushEvent(); } DECLARE_JNI_INTERFACE( int, setenv, jstring key, jstring value, jboolean overwrite ) { char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL); char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL); int err = setenv(k, v, overwrite); (*env)->ReleaseStringUTFChars(env, key, k); (*env)->ReleaseStringUTFChars(env, value, v); return err; } DECLARE_JNI_INTERFACE( void, nativeMouseMove, jfloat x, jfloat y ) { Android_Lock(); events.mousex += x; events.mousey += y; Android_Unlock(); } DECLARE_JNI_INTERFACE( int, nativeTestWritePermission, jstring jPath ) { char *path = (char *)(*env)->GetStringUTFChars(env, jPath, NULL); FILE *fd; char testFile[PATH_MAX]; int ret = 0; // maybe generate new file everytime? Q_snprintf( testFile, PATH_MAX, "%s/.testfile", path ); __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: file=%s", testFile ); fd = fopen( testFile, "w+" ); if( fd ) { __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: passed" ); ret = 1; fclose( fd ); remove( testFile ); } else { __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: error=%s", strerror( errno ) ); } (*env)->ReleaseStringUTFChars( env, jPath, path ); return ret; } JNIEXPORT jint JNICALL JNI_OnLoad( JavaVM *vm, void *reserved ) { return JNI_VERSION_1_6; } /* ======================== Platform_Init Initialize android-related cvars ======================== */ void Platform_Init( void ) { android_sleep = Cvar_Get( "android_sleep", "1", FCVAR_ARCHIVE, "Enable sleep in background" ); } void Platform_Shutdown( void ) { } /* ======================== Android_EnableTextInput Show virtual keyboard ======================== */ void Platform_EnableTextInput( qboolean enable ) { (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.enableTextInput, enable ); } /* ======================== Android_Vibrate ======================== */ void Platform_Vibrate( float life, char flags ) { if( life ) (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.vibrate, (int)life ); } /* ======================== Android_GetNativeObject ======================== */ void *Platform_GetNativeObject( const char *objName ) { static const char *availObjects[] = { "JNIEnv", "ActivityClass", NULL }; void *object = NULL; if( !objName ) { object = (void*)availObjects; } else if( !strcasecmp( objName, "JNIEnv" ) ) { object = (void*)jni.env; } else if( !strcasecmp( objName, "ActivityClass" ) ) { object = (void*)jni.actcls; } return object; } /* ======================== Android_MessageBox Show messagebox and wait for OK button press ======================== */ #if XASH_MESSAGEBOX == MSGBOX_ANDROID void Platform_MessageBox( const char *title, const char *text, qboolean parentMainWindow ) { (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.messageBox, (*jni.env)->NewStringUTF( jni.env, title ), (*jni.env)->NewStringUTF( jni.env ,text ) ); } #endif // XASH_MESSAGEBOX == MSGBOX_ANDROID /* ======================== Android_GetAndroidID ======================== */ const char *Android_GetAndroidID( void ) { static char id[65]; const char *resultCStr; jstring resultJNIStr; if( id[0] ) return id; resultJNIStr = (jstring)(*jni.env)->CallStaticObjectMethod( jni.env, jni.actcls, jni.getAndroidId ); resultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL ); Q_strncpy( id, resultCStr, 64 ); (*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr ); if( !id[0] ) return NULL; return id; } /* ======================== Android_LoadID ======================== */ const char *Android_LoadID( void ) { static char id[65]; jstring resultJNIStr = (jstring)(*jni.env)->CallStaticObjectMethod( jni.env, jni.actcls, jni.loadID ); const char *resultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL ); Q_strncpy( id, resultCStr, 64 ); (*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr ); return id; } /* ======================== Android_SaveID ======================== */ void Android_SaveID( const char *id ) { (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.saveID, (*jni.env)->NewStringUTF( jni.env, id ) ); } /* ======================== Android_MouseMove ======================== */ void Platform_MouseMove( float *x, float *y ) { *x = jnimouse.x; *y = jnimouse.y; jnimouse.x = 0; jnimouse.y = 0; // Con_Reportf( "Android_MouseMove: %f %f\n", *x, *y ); } /* ======================== Android_AddMove ======================== */ void Android_AddMove( float x, float y ) { jnimouse.x += x; jnimouse.y += y; } void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) { // stub } void GAME_EXPORT Platform_SetMousePos( int x, int y ) { // stub } int Platform_JoyInit( int numjoy ) { // stub return 0; } /* ======================== Android_ShowMouse ======================== */ void Android_ShowMouse( qboolean show ) { if( m_ignore->value ) show = true; (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.showMouse, show ); } /* ======================== Android_ShellExecute ======================== */ void Platform_ShellExecute( const char *path, const char *parms ) { jstring jstr; if( !path ) return; // useless // get java.lang.String jstr = (*jni.env)->NewStringUTF( jni.env, path ); // open browser (*jni.env)->CallStaticVoidMethod(jni.env, jni.actcls, jni.shellExecute, jstr); // no need to free jstr } int Platform_GetClipboardText( char *buffer, size_t size ) { // stub if( size ) buffer[0] = 0; return 0; } void Platform_SetClipboardText( const char *buffer ) { // stub } void Platform_SetCursorType( VGUI_DefaultCursor cursor ) { // stub } key_modifier_t Platform_GetKeyModifiers( void ) { // stub return KeyModifier_None; } void Platform_PreCreateMove( void ) { // stub } /* ======================== Android_RunEvents Execute all events from queue ======================== */ void Platform_RunEvents( void ) { int i; // enter events read Android_Lock(); pthread_mutex_unlock( &events.framemutex ); for( i = 0; i < events.count; i++ ) { switch( events.queue[i].type ) { case event_touch_down: case event_touch_up: case event_touch_move: IN_TouchEvent( (touchEventType)events.queue[i].type, events.queue[i].arg, events.queue[i].touch.x, events.queue[i].touch.y, events.queue[i].touch.dx, events.queue[i].touch.dy ); break; case event_key_down: Key_Event( events.queue[i].arg, true ); if( events.queue[i].arg == K_AUX31 || events.queue[i].arg == K_AUX29 ) { host.force_draw_version = true; host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; } break; case event_key_up: Key_Event( events.queue[i].arg, false ); if( events.queue[i].arg == K_AUX31 || events.queue[i].arg == K_AUX29 ) { host.force_draw_version = true; host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; } break; case event_set_pause: // destroy EGL surface when hiding application if( !events.queue[i].arg ) { SNDDMA_Activate( true ); // (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); Android_UpdateSurface( true ); host.status = HOST_FRAME; SetBits( gl_vsync->flags, FCVAR_CHANGED ); // set swap interval host.force_draw_version = true; host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; } if( events.queue[i].arg ) { SNDDMA_Activate( false ); Android_UpdateSurface( false ); host.status = HOST_NOFOCUS; // (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); } break; case event_resize: // reinitialize EGL and change engine screen size if( host.status == HOST_FRAME &&( refState.width != jni.width || refState.height != jni.height ) ) { // (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); // (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); Android_UpdateSurface( true ); SetBits( gl_vsync->flags, FCVAR_CHANGED ); // set swap interval VID_SetMode(); } break; case event_joyadd: Joy_AddEvent(); break; case event_joyremove: Joy_RemoveEvent(); break; case event_joyball: if( !Joy_IsActive() ) Joy_AddEvent(); Joy_BallMotionEvent( events.queue[i].ball.ball, events.queue[i].ball.xrel, events.queue[i].ball.yrel ); break; case event_joyhat: if( !Joy_IsActive() ) Joy_AddEvent(); Joy_HatMotionEvent( events.queue[i].hat.hat, events.queue[i].hat.key ); break; case event_joyaxis: if( !Joy_IsActive() ) Joy_AddEvent(); Joy_AxisMotionEvent( events.queue[i].axis.axis, events.queue[i].axis.val ); break; case event_joybutton: if( !Joy_IsActive() ) Joy_AddEvent(); Joy_ButtonEvent( events.queue[i].button.button, (byte)events.queue[i].button.down ); break; case event_ondestroy: //host.skip_configs = true; // skip config save, because engine may be killed during config save Sys_Quit(); (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.notify ); break; case event_onpause: #ifdef PARANOID_CONFIG_SAVE switch( host.status ) { case HOST_INIT: case HOST_CRASHED: case HOST_ERR_FATAL: Con_Reportf( S_WARN "Abnormal host state during onPause (%d), skipping config save!\n", host.status ); break; default: // restore all latched cheat cvars Cvar_SetCheatState( true ); Host_WriteConfig(); } #endif // disable sound during call/screen-off SNDDMA_Activate( false ); // host.status = HOST_NOFOCUS; // stop blocking UI thread (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.notify ); break; case event_onresume: // re-enable sound after onPause // host.status = HOST_FRAME; SNDDMA_Activate( true ); host.force_draw_version = true; host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; break; case event_onfocuschange: host.force_draw_version = true; host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; break; } } events.count = 0; // no more events // text input handled separately to allow unicode symbols for( i = 0; events.inputtext[i]; i++ ) { int ch; // if engine does not use utf-8, we need to convert it to preferred encoding if( !Q_stricmp( cl_charset->string, "utf-8" ) ) ch = (unsigned char)events.inputtext[i]; else ch = Con_UtfProcessCharForce( (unsigned char)events.inputtext[i] ); if( !ch ) // utf-8 continue; // some keyboards may send enter as text if( ch == '\n' ) { Key_Event( K_ENTER, true ); Key_Event( K_ENTER, false ); continue; } // otherwise just push it by char, text render will decode unicode strings CL_CharEvent( ch ); } events.inputtext[0] = 0; // no more text jnimouse.x += events.mousex; events.mousex = 0; jnimouse.y += events.mousey; events.mousey = 0; //end events read Android_Unlock(); pthread_mutex_lock( &events.framemutex ); } #endif // XASH_DEDICATED