android: initial port

This commit is contained in:
Alibek Omarov 2019-05-02 18:07:03 +03:00
parent 1a700fd06b
commit 3fef353291
18 changed files with 1360 additions and 13 deletions

View File

@ -147,11 +147,15 @@ Default build-depended cvar and constant values
#endif
#ifndef DEFAULT_RENDERER
#define DEFAULT_RENDERER "gl"
#ifdef __ANDROID__
#define DEFAULT_RENDERER "gles1"
#else
#define DEFAULT_RENDERER "gl"
#endif
#endif
#if TARGET_OS_IPHONE
#define DEFAULT_CON_MAXFRAC "0.5"
#define DEFAULT_CON_MAXFRAC "0.5"
#else
#define DEFAULT_CON_MAXFRAC "1"
#endif

View File

@ -137,7 +137,7 @@ typedef struct dll_info_s
void *link; // hinstance of loading library
} dll_info_t;
typedef void (*setpair_t)( const char *key, const char *value, void *buffer, void *numpairs );
typedef void (*setpair_t)( const char *key, const void *value, void *buffer, void *numpairs );
// config strings are a general means of communication from
// the server to all connected clients.

View File

@ -16,6 +16,7 @@ GNU General Public License for more details.
#include "common.h"
#include "sound.h"
#include "const.h"
#include <ctype.h>
sentence_t g_Sentences[MAX_SENTENCES];
static uint g_numSentences;

View File

@ -302,7 +302,6 @@ void DSP_ClearState( void );
qboolean S_Init( void );
void S_Shutdown( void );
void S_Activate( qboolean active );
void S_SoundList_f( void );
void S_SoundInfo_f( void );

View File

@ -39,6 +39,5 @@ void R_SaveVideoMode( int w, int h );
void VID_CheckChanges( void );
const char *VID_GetModeString( int vid_mode );
void VID_StartupGamma( void );
void GL_SwapBuffers();
#endif // VID_COMMON

View File

@ -492,7 +492,7 @@ int Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function );
int Cmd_AddRefCommand( const char *cmd_name, xcommand_t function, const char *description );
void Cmd_RemoveCommand( const char *cmd_name );
qboolean Cmd_Exists( const char *cmd_name );
void Cmd_LookupCmds( char *buffer, void *ptr, setpair_t callback );
void Cmd_LookupCmds( void *buffer, void *ptr, setpair_t callback );
qboolean Cmd_GetMapList( const char *s, char *completedname, int length );
qboolean Cmd_GetDemoList( const char *s, char *completedname, int length );
qboolean Cmd_GetMovieList( const char *s, char *completedname, int length );

View File

@ -93,7 +93,7 @@ write screenshot into clipboard
*/
void Sys_SetClipboardData( const byte *buffer, size_t size )
{
Platform_SetClipboardText( (char *)buffer, size );
Platform_SetClipboardText( buffer, size );
}
#endif // XASH_DEDICATED

View File

@ -0,0 +1,956 @@
/*
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 __ANDROID__ || 1
#include "input.h"
#include "client.h"
#include "sound.h"
#include "platform/android/android_priv.h"
#include "errno.h"
#include <pthread.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,
event_touch_up,
event_touch_move,
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 nativeegl_s negl;
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()
{
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_in_celest_xash3d_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[1 + len + 1];
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, "in/celest/xash3d/XashActivity");
jni.swapBuffers = (*env)->GetStaticMethodID(env, jni.actcls, "swapBuffers", "()V");
jni.toggleEGL = (*env)->GetStaticMethodID(env, jni.actcls, "toggleEGL", "(I)V");
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.createGLContext = (*env)->GetStaticMethodID(env, jni.actcls, "createGLContext", "(I)Z");
jni.getGLAttribute = (*env)->GetStaticMethodID(env, jni.actcls, "getGLAttribute", "(I)I");
jni.deleteGLContext = (*env)->GetStaticMethodID(env, jni.actcls, "deleteGLContext", "()Z");
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");
/* 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]);
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] ) ) )
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 )
{
static byte engineKeys;
if( !key )
engineKeys = 0; // centered;
if( down )
engineKeys |= key;
else
engineKeys &= ~key;
event_t *event = Android_AllocEvent();
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;
}
/*
========================
Android_Init
Initialize android-related cvars
========================
*/
void Android_Init()
{
android_sleep = Cvar_Get( "android_sleep", "1", FCVAR_ARCHIVE, "Enable sleep in background" );
}
/*
========================
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
========================
*/
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 ) );
}
/*
========================
Android_GetAndroidID
========================
*/
const char *Android_GetAndroidID( void )
{
static char id[65];
if( id[0] )
return id;
jstring resultJNIStr = (jstring)(*jni.env)->CallStaticObjectMethod( jni.env, jni.actcls, jni.getAndroidId );
const char *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 Android_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 Platform_GetMousePos( int *x, int *y )
{
// stub
}
void 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
}
void Platform_GetClipboardText( char *buffer, size_t size )
{
// stub
if( size ) buffer[0] = 0;
}
void Platform_SetClipboardText( const char *buffer, size_t size )
{
// stub
}
/*
========================
Android_RunEvents
Execute all events from queue
========================
*/
void Platform_RunEvents()
{
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:
#if 0 // TOUCHTODO
IN_TouchEvent( 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 );
#endif
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 )
{
host.status = HOST_FRAME;
SNDDMA_Activate( true );
(*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 );
Android_UpdateSurface();
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 )
{
host.status = HOST_NOFOCUS;
SNDDMA_Activate( false );
(*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 );
negl.valid = false;
}
break;
case event_resize:
// reinitialize EGL and change engine screen size
if( host.status == HOST_NORMAL && ( 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();
SetBits( gl_vsync->flags, FCVAR_CHANGED ); // set swap interval
VID_SetMode();
}
break;
case event_joyadd:
Joy_AddEvent( events.queue[i].arg );
break;
case event_joyremove:
Joy_RemoveEvent( events.queue[i].arg );
break;
case event_joyball:
if( !Joy_IsActive() )
Joy_AddEvent( 0 );
Joy_BallMotionEvent( events.queue[i].arg, events.queue[i].ball.ball,
events.queue[i].ball.xrel, events.queue[i].ball.yrel );
break;
case event_joyhat:
if( !Joy_IsActive() )
Joy_AddEvent( 0 );
Joy_HatMotionEvent( events.queue[i].arg, events.queue[i].hat.hat, events.queue[i].hat.key );
break;
case event_joyaxis:
if( !Joy_IsActive() )
Joy_AddEvent( 0 );
Joy_AxisMotionEvent( events.queue[i].arg, events.queue[i].axis.axis, events.queue[i].axis.val );
break;
case event_joybutton:
if( !Joy_IsActive() )
Joy_AddEvent( 0 );
Joy_ButtonEvent( events.queue[i].arg, 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 // __ANDROID__

View File

@ -0,0 +1,51 @@
#pragma once
#ifndef ANDROID_PRIV_H
#define ANDROID_PRIV_H
#include <EGL/egl.h>
#include <android/log.h>
#include <jni.h>
#include <sys/prctl.h>
extern struct jnimethods_s
{
jclass actcls;
JavaVM *vm;
JNIEnv *env;
jmethodID swapBuffers;
jmethodID toggleEGL;
jmethodID enableTextInput;
jmethodID vibrate;
jmethodID messageBox;
jmethodID createGLContext;
jmethodID getGLAttribute;
jmethodID deleteGLContext;
jmethodID notify;
jmethodID setTitle;
jmethodID setIcon;
jmethodID getAndroidId;
jmethodID saveID;
jmethodID loadID;
jmethodID showMouse;
jmethodID shellExecute;
int width, height;
} jni;
extern struct nativeegl_s
{
qboolean valid;
EGLDisplay dpy;
EGLSurface surface;
} negl;
extern struct jnimouse_s
{
float x, y;
} jnimouse;
//
// vid_android.c
//
void Android_UpdateSurface( void );
#endif // ANDROID_PRIV_H

View File

@ -19,7 +19,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "common.h"
#include "platform.h"
#include "platform/platform.h"
#if XASH_SOUND == SOUND_OPENSLES
#include <SLES/OpenSLES.h>
#include "pthread.h"
@ -194,7 +194,7 @@ static const char *SNDDMA_Android_Init( void )
snddma_android_pos = 0;
dma.initialized = true;
S_Activate( true );
SNDDMA_Activate( true );
return NULL;
}

View File

@ -0,0 +1,308 @@
#include "platform/platform.h"
#if defined XASH_VIDEO == VIDEO_ANDROID || 1
#include "input.h"
#include "client.h"
#include "filesystem.h"
#include "platform/android/android_priv.h"
#include "vid_common.h"
/*
========================
Android_SwapInterval
========================
*/
static void Android_SwapInterval( int interval )
{
// there is no eglSwapInterval in EGL10/EGL11 classes,
// so only native backend supported
if( negl.valid )
eglSwapInterval( negl.dpy, interval );
}
/*
========================
Android_SetTitle
========================
*/
static void Android_SetTitle( const char *title )
{
(*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.setTitle, (*jni.env)->NewStringUTF( jni.env, title ) );
}
/*
========================
Android_SetIcon
========================
*/
static void Android_SetIcon( const char *path )
{
(*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.setIcon, (*jni.env)->NewStringUTF( jni.env, path ) );
}
/*
========================
Android_GetScreenRes
Resolution got from last resize event
========================
*/
static void Android_GetScreenRes( int *width, int *height )
{
*width=jni.width, *height=jni.height;
}
/*
========================
Android_UpdateSurface
Check if we may use native EGL without jni calls
========================
*/
void Android_UpdateSurface( void )
{
negl.valid = false;
if( Sys_CheckParm("-nonativeegl") )
return; //disabled by user
negl.dpy = eglGetCurrentDisplay();
if( negl.dpy == EGL_NO_DISPLAY )
return;
negl.surface = eglGetCurrentSurface(EGL_DRAW);
if( negl.surface == EGL_NO_SURFACE )
return;
// now check if swapBuffers does not give error
if( eglSwapBuffers( negl.dpy, negl.surface ) == EGL_FALSE )
return;
// double check
if( eglGetError() != EGL_SUCCESS )
return;
__android_log_print( ANDROID_LOG_VERBOSE, "Xash", "native EGL enabled" );
negl.valid = true;
}
/*
========================
Android_GetGLAttribute
========================
*/
static int Android_GetGLAttribute( int eglAttr )
{
int ret = (*jni.env)->CallStaticIntMethod( jni.env, jni.actcls, jni.getGLAttribute, eglAttr );
// Con_Reportf( "Android_GetGLAttribute( %i ) => %i\n", eglAttr, ret );
return ret;
}
/*
========================
Android_InitGL
========================
*/
qboolean Android_InitGL()
{
int colorBits[3];
qboolean result;
// result = (*jni.env)->CallStaticBooleanMethod( jni.env, jni.actcls, jni.createGLContext, (int)gl_stencilbits->value );
/*colorBits[0] = Android_GetGLAttribute( EGL_RED_SIZE );
colorBits[1] = Android_GetGLAttribute( EGL_GREEN_SIZE );
colorBits[2] = Android_GetGLAttribute( EGL_BLUE_SIZE );
glConfig.color_bits = colorBits[0] + colorBits[1] + colorBits[2];
glConfig.alpha_bits = Android_GetGLAttribute( EGL_ALPHA_SIZE );
glConfig.depth_bits = Android_GetGLAttribute( EGL_DEPTH_SIZE );
glConfig.stencil_bits = Android_GetGLAttribute( EGL_STENCIL_SIZE );
glState.stencilEnabled = glConfig.stencil_bits ? true : false;*/
Android_UpdateSurface();
return result;
}
/*
========================
Android_ShutdownGL
========================
*/
void Android_ShutdownGL()
{
(*jni.env)->CallStaticBooleanMethod( jni.env, jni.actcls, jni.deleteGLContext );
}
/*
========================
Android_SwapBuffers
Update screen. Use native EGL if possible
========================
*/
void GL_SwapBuffers()
{
if( negl.valid )
{
eglSwapBuffers( negl.dpy, negl.surface );
}
else
{
// nanoGL_Flush();
(*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.swapBuffers );
}
}
qboolean R_Init_Video( const int type )
{
string safe;
qboolean retval;
if( FS_FileExists( GI->iconpath, true ) )
{
if( host.rodir[0] )
{
Android_SetIcon( va( "%s/%s/%s", host.rodir, GI->gamefolder, GI->iconpath ) );
}
else
{
Android_SetIcon( va( "%s/%s/%s", host.rootdir, GI->gamefolder, GI->iconpath ) );
}
}
Android_SetTitle( GI->title );
VID_StartupGamma();
switch( type )
{
case REF_SOFTWARE:
glw_state.software = true;
Host_Error( "software mode isn't supported on Android yet! :(\n", type );
break;
case REF_GL:
if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", safe ) )
glw_state.safe = bound( SAFE_NO, Q_atoi( safe ), SAFE_DONTCARE );
// refdll can request some attributes
ref.dllFuncs.GL_SetupAttributes( glw_state.safe );
break;
default:
Host_Error( "Can't initialize unknown context type %d!\n", type );
break;
}
if( !(retval = VID_SetMode()) )
{
return retval;
}
switch( type )
{
case REF_GL:
// refdll also can check extensions
ref.dllFuncs.GL_InitExtensions();
break;
case REF_SOFTWARE:
default:
break;
}
host.renderinfo_changed = false;
return true;
}
void R_Free_Video( void )
{
// GL_DeleteContext ();
// VID_DestroyWindow ();
// R_FreeVideoModes();
ref.dllFuncs.GL_ClearExtensions();
}
qboolean VID_SetMode( void )
{
R_ChangeDisplaySettings( 0, 0, false ); // width and height are ignored anyway
return true;
}
rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen )
{
Android_GetScreenRes(&width, &height);
Con_Reportf( "R_ChangeDisplaySettings: forced resolution to %dx%d)\n", width, height);
R_SaveVideoMode( width, height );
host.window_center_x = width / 2;
host.window_center_y = height / 2;
refState.wideScreen = true; // V_AdjustFov will check for widescreen
return rserr_ok;
}
int GL_SetAttribute( int attr, int val )
{
}
int GL_GetAttribute( int attr, int *val )
{
}
int R_MaxVideoModes()
{
return 0;
}
vidmode_t* R_GetVideoMode( int num )
{
return NULL;
}
void* GL_GetProcAddress( const char *name ) // RenderAPI requirement
{
return NULL; // not implemented, only static for now
}
void GL_UpdateSwapInterval( void )
{
// disable VSync while level is loading
if( cls.state < ca_active )
{
Android_SwapInterval( 0 );
SetBits( gl_vsync->flags, FCVAR_CHANGED );
}
else if( FBitSet( gl_vsync->flags, FCVAR_CHANGED ))
{
ClearBits( gl_vsync->flags, FCVAR_CHANGED );
Android_SwapInterval( gl_vsync->value );
}
}
void *SW_LockBuffer()
{
return NULL;
}
void SW_UnlockBuffer()
{
}
qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b )
{
return false;
}
#endif

View File

@ -36,6 +36,12 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren
// see system.c
// qboolean Sys_DebuggerPresent( void );
#ifdef __ANDROID__
const char *Android_GetAndroidID( void );
const char *Android_LoadID( void );
void Android_SaveID( const char *id );
#endif
/*
==============================================================================
@ -64,7 +70,12 @@ void Platform_GetMousePos( int *x, int *y );
void Platform_SetMousePos( int x, int y );
// Clipboard
void Platform_GetClipboardText( char *buffer, size_t size );
void Platform_SetClipboardText( char *buffer, size_t size );
void Platform_SetClipboardText( const char *buffer, size_t size );
#ifdef __ANDROID__
void Android_ShowMouse( qboolean show );
void Android_MouseMove( float *x, float *y );
#endif
/*
==============================================================================
@ -94,6 +105,7 @@ void* GL_GetProcAddress( const char *name ); // RenderAPI requirement
void GL_UpdateSwapInterval( void );
int GL_SetAttribute( int attr, int val );
int GL_GetAttribute( int attr, int *val );
void GL_SwapBuffers();
void *SW_LockBuffer();
void SW_UnlockBuffer();
qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b );

View File

@ -18,6 +18,7 @@ GNU General Public License for more details.
#include <sys/stat.h>
#include <fcntl.h>
#include "platform/platform.h"
#include "menu_int.h"
static qboolean Sys_FindExecutable( const char *baseName, char *buf, size_t size )
{
@ -65,6 +66,7 @@ static qboolean Sys_FindExecutable( const char *baseName, char *buf, size_t size
return false;
}
#ifndef __ANDROID__
void Platform_ShellExecute( const char *path, const char *parms )
{
char xdgOpen[128];
@ -88,6 +90,7 @@ void Platform_ShellExecute( const char *path, const char *parms )
Con_Reportf( S_WARN "Could not find "OPEN_COMMAND" utility\n" );
}
}
#endif // __ANDROID__
#ifdef XASH_DEDICATED
void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow )

View File

@ -72,7 +72,7 @@ Platform_SetClipobardText
=============
*/
void Platform_SetClipboardText( char *buffer, size_t size )
void Platform_SetClipboardText( const char *buffer, size_t size )
{
SDL_SetClipboardText( buffer );
}

View File

@ -15,6 +15,8 @@ GNU General Public License for more details.
#include <windows.h>
#include "platform/platform.h"
#include "menu_int.h"
#ifdef _WIN32
BOOL WINAPI IsDebuggerPresent(VOID);
#endif // _WIN32

View File

@ -27,6 +27,7 @@ def configure(conf):
conf.env.append_unique('DEFINES', 'XASH_DEDICATED')
elif conf.env.DEST_OS2 == 'android': # Android doesn't need SDL2
conf.check_cc(lib='log')
conf.check_cc(lib='EGL')
else:
conf.load('sdl2')
if not conf.env.HAVE_SDL2:
@ -73,7 +74,7 @@ def build(bld):
source += bld.path.ant_glob(['platform/sdl/*.c'])
if bld.env.DEST_OS2 == 'android':
libs.append('LOG')
libs += ['LOG', 'EGL']
source += bld.path.ant_glob(['platform/android/*.c*'])
# add client files

View File

@ -47,6 +47,8 @@ def configure(conf):
conf.check( lib='GL' )
conf.env.append_unique('DEFINES', 'REF_DLL')
if conf.env.DEST_OS2 == 'android':
conf.check_cc(lib='log')
def build(bld):
libs = [ 'public', 'M' ]

View File

@ -106,6 +106,10 @@ class Android:
def cxx(self):
return os.path.abspath(os.path.join(self.ndk_home, self.toolchain_path + 'g++'))
def system_stl(self):
# TODO: proper STL support
return os.path.abspath(os.path.join(self.ndk_home, 'sources', 'cxx-stl', 'system', 'include'))
def sysroot(self):
arch = self.arch
if self.is_arm():
@ -118,6 +122,7 @@ class Android:
def cflags(self):
cflags = ['--sysroot={0}'.format(self.sysroot()), '-DANDROID', '-D__ANDROID__']
cflags += ['-I{0}'.format(self.system_stl())]
if self.is_arm():
if self.arch.startswith('armeabi-v7a'):
# ARMv7 support
@ -178,15 +183,19 @@ def configure(conf):
android = Android(android_ndk_path, values[0], values[1], values[2])
conf.options.ALLOW64 = True # skip pointer length check
conf.options.NO_VGUI = True # skip vgui
conf.options.NANOGL = True
conf.options.GLWES = True
conf.options.GL = False
conf.environ['CC'] = android.cc()
conf.environ['CXX'] = android.cxx()
conf.env.CFLAGS += android.cflags()
conf.env.CXXFLAGS += android.cflags()
conf.env.LINKFLAGS += android.ldflags()
conf.env.HAVE_M = True
if android.is_hardfp():
conf.env.HAVE_M = True
conf.env.LIB_M = ['m_hard']
else: conf.env.LIB_M = ['m']
conf.msg('Selected Android NDK', android_ndk_path)
# no need to print C/C++ compiler, as it would be printed by compiler_c/cxx