15d31e62394f347e777ef0668d71c002d0ab24dc
[freeglut] / src / android / fg_main_android.c
1 /*
2  * fg_main_android.c
3  *
4  * The Android-specific windows message processing methods.
5  *
6  * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
7  * Written by Pawel W. Olszta, <olszta@sourceforge.net>
8  * Copied for Platform code by Evan Felix <karcaw at gmail.com>
9  * Copyright (C) 2012  Sylvain Beucler
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
24  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27  */
28
29 #include <GL/freeglut.h>
30 #include "fg_internal.h"
31 #include "egl/fg_window_egl.h"
32
33 #include <android/log.h>
34 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "FreeGLUT", __VA_ARGS__))
35 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "FreeGLUT", __VA_ARGS__))
36 #include <android/native_app_glue/android_native_app_glue.h>
37 #include <android/keycodes.h>
38
39 static struct touchscreen touchscreen;
40 static unsigned char key_a2fg[256];
41
42 /* Cf. http://developer.android.com/reference/android/view/KeyEvent.html */
43 /* These codes are missing in <android/keycodes.h> */
44 /* Don't convert to enum, since it may conflict with future version of
45    that <android/keycodes.h> */
46 #define AKEYCODE_FORWARD_DEL 112
47 #define AKEYCODE_CTRL_LEFT 113
48 #define AKEYCODE_CTRL_RIGHT 114
49 #define AKEYCODE_MOVE_HOME 122
50 #define AKEYCODE_MOVE_END 123
51 #define AKEYCODE_INSERT 124
52 #define AKEYCODE_ESCAPE 127
53 #define AKEYCODE_F1 131
54 #define AKEYCODE_F2 132
55 #define AKEYCODE_F3 133
56 #define AKEYCODE_F4 134
57 #define AKEYCODE_F5 135
58 #define AKEYCODE_F6 136
59 #define AKEYCODE_F7 137
60 #define AKEYCODE_F8 138
61 #define AKEYCODE_F9 139
62 #define AKEYCODE_F10 140
63 #define AKEYCODE_F11 141
64 #define AKEYCODE_F12 142
65
66 #define EVENT_HANDLED 1
67 #define EVENT_NOT_HANDLED 0
68
69 /**
70  * Initialize Android keycode to GLUT keycode mapping
71  */
72 static void key_init() {
73   memset(key_a2fg, 0, sizeof(key_a2fg));
74
75   key_a2fg[AKEYCODE_F1]  = GLUT_KEY_F1;
76   key_a2fg[AKEYCODE_F2]  = GLUT_KEY_F2;
77   key_a2fg[AKEYCODE_F3]  = GLUT_KEY_F3;
78   key_a2fg[AKEYCODE_F4]  = GLUT_KEY_F4;
79   key_a2fg[AKEYCODE_F5]  = GLUT_KEY_F5;
80   key_a2fg[AKEYCODE_F6]  = GLUT_KEY_F6;
81   key_a2fg[AKEYCODE_F7]  = GLUT_KEY_F7;
82   key_a2fg[AKEYCODE_F8]  = GLUT_KEY_F8;
83   key_a2fg[AKEYCODE_F9]  = GLUT_KEY_F9;
84   key_a2fg[AKEYCODE_F10] = GLUT_KEY_F10;
85   key_a2fg[AKEYCODE_F11] = GLUT_KEY_F11;
86   key_a2fg[AKEYCODE_F12] = GLUT_KEY_F12;
87
88   key_a2fg[AKEYCODE_PAGE_UP]   = GLUT_KEY_PAGE_UP;
89   key_a2fg[AKEYCODE_PAGE_DOWN] = GLUT_KEY_PAGE_DOWN;
90   key_a2fg[AKEYCODE_MOVE_HOME] = GLUT_KEY_HOME;
91   key_a2fg[AKEYCODE_MOVE_END]  = GLUT_KEY_END;
92   key_a2fg[AKEYCODE_INSERT]    = GLUT_KEY_INSERT;
93
94   key_a2fg[AKEYCODE_DPAD_UP]    = GLUT_KEY_UP;
95   key_a2fg[AKEYCODE_DPAD_DOWN]  = GLUT_KEY_DOWN;
96   key_a2fg[AKEYCODE_DPAD_LEFT]  = GLUT_KEY_LEFT;
97   key_a2fg[AKEYCODE_DPAD_RIGHT] = GLUT_KEY_RIGHT;
98
99   key_a2fg[AKEYCODE_ALT_LEFT]    = GLUT_KEY_ALT_L;
100   key_a2fg[AKEYCODE_ALT_RIGHT]   = GLUT_KEY_ALT_R;
101   key_a2fg[AKEYCODE_SHIFT_LEFT]  = GLUT_KEY_SHIFT_L;
102   key_a2fg[AKEYCODE_SHIFT_RIGHT] = GLUT_KEY_SHIFT_R;
103   key_a2fg[AKEYCODE_CTRL_LEFT]   = GLUT_KEY_CTRL_L;
104   key_a2fg[AKEYCODE_CTRL_RIGHT]  = GLUT_KEY_CTRL_R;
105 }
106
107 /**
108  * Convert an Android key event to ASCII.
109  */
110 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
111   int32_t code = AKeyEvent_getKeyCode(event);
112
113   /* Handle a few special cases: */
114   switch (code) {
115   case AKEYCODE_DEL:
116     return 8;
117   case AKEYCODE_FORWARD_DEL:
118     return 127;
119   case AKEYCODE_ESCAPE:
120     return 27;
121   }
122
123   /* Get usable JNI context */
124   JNIEnv* env = app->activity->env;
125   JavaVM* vm = app->activity->vm;
126   (*vm)->AttachCurrentThread(vm, &env, NULL);
127
128   jclass KeyEventClass = (*env)->FindClass(env, "android/view/KeyEvent");
129   jmethodID KeyEventConstructor = (*env)->GetMethodID(env, KeyEventClass, "<init>", "(II)V");
130   jobject keyEvent = (*env)->NewObject(env, KeyEventClass, KeyEventConstructor,
131                                        AKeyEvent_getAction(event), AKeyEvent_getKeyCode(event));
132   jmethodID KeyEvent_getUnicodeChar = (*env)->GetMethodID(env, KeyEventClass, "getUnicodeChar", "(I)I");
133   int ascii = (*env)->CallIntMethod(env, keyEvent, KeyEvent_getUnicodeChar, AKeyEvent_getMetaState(event));
134
135   /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */
136   (*vm)->DetachCurrentThread(vm);
137
138   return ascii;
139 }
140
141 unsigned long fgPlatformSystemTime ( void )
142 {
143   struct timeval now;
144   gettimeofday( &now, NULL );
145   return now.tv_usec/1000 + now.tv_sec*1000;
146 }
147
148 /*
149  * Does the magic required to relinquish the CPU until something interesting
150  * happens.
151  */
152 void fgPlatformSleepForEvents( long msec )
153 {
154     /* Android's NativeActivity relies on a Looper/ALooper object to
155        notify about events.  The Looper object is plugged on two
156        internal pipe(2)s to detect system and input events.  Sadly you
157        can only ask the Looper for an event, not just ask whether
158        there is a pending event (and process it later).  Consequently,
159        short of redesigning NativeActivity, we cannot
160        SleepForEvents. */
161 }
162
163 /**
164  * Process the next input event.
165  */
166 int32_t handle_input(struct android_app* app, AInputEvent* event) {
167   SFG_Window* window = fgWindowByHandle(app->window);
168   if (window == NULL)
169     return EVENT_NOT_HANDLED;
170
171   /* FIXME: in Android, when a key is repeated, down
172      and up events happen most often at the exact same time.  This
173      makes it impossible to animate based on key press time. */
174   /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */ 
175   /* This looks like a bug in the Android virtual keyboard system :/
176      Real buttons such as the Back button appear to work correctly
177      (series of down events with proper getRepeatCount value). */
178   
179   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
180     /* LOGI("action: %d", AKeyEvent_getAction(event)); */
181     /* LOGI("keycode: %d", code); */
182     int32_t code = AKeyEvent_getKeyCode(event);
183
184     if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
185       int32_t keypress = 0;
186       unsigned char ascii = 0;
187       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
188         INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
189         return EVENT_HANDLED;
190       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
191         INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
192         return EVENT_HANDLED;
193       }
194     }
195     else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
196       int32_t keypress = 0;
197       unsigned char ascii = 0;
198       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
199         INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
200         return EVENT_HANDLED;
201       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
202         INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
203         return EVENT_HANDLED;
204       }
205     }
206   }
207
208   int32_t source = AInputEvent_getSource(event);
209   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION
210       && source == AINPUT_SOURCE_TOUCHSCREEN) {
211     int32_t action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK;
212     /* Pointer ID for clicks */
213     int32_t pidx = AMotionEvent_getAction(event) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
214     /* TODO: Handle multi-touch; also handle multiple sources/devices */
215     /* cf. http://sourceforge.net/mailarchive/forum.php?thread_name=20120518071314.GA28061%40perso.beuc.net&forum_name=freeglut-developer */
216     if (0) {
217       LOGI("motion action=%d index=%d source=%d", action, pidx, source);
218       int count = AMotionEvent_getPointerCount(event);
219       int i;
220       for (i = 0; i < count; i++) {
221         LOGI("multi(%d): %.01f,%.01f",
222              AMotionEvent_getPointerId(event, i),
223              AMotionEvent_getX(event, i), AMotionEvent_getY(event, i));
224       }
225     }
226     float x = AMotionEvent_getX(event, 0);
227     float y = AMotionEvent_getY(event, 0);
228
229     /* Virtual arrows PAD */
230     /* Don't interfere with existing mouse move event */
231     if (!touchscreen.in_mmotion) {
232       struct vpad_state prev_vpad = touchscreen.vpad;
233       touchscreen.vpad.left = touchscreen.vpad.right
234         = touchscreen.vpad.up = touchscreen.vpad.down = false;
235
236       /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
237       int32_t height = ANativeWindow_getHeight(window->Window.Handle);
238       if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
239         if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
240           touchscreen.vpad.left = true;
241         if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
242           touchscreen.vpad.right = true;
243         if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
244           touchscreen.vpad.down = true;
245         if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
246           touchscreen.vpad.up = true;
247       }
248       if (action == AMOTION_EVENT_ACTION_DOWN && 
249           (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
250         touchscreen.vpad.on = true;
251       if (action == AMOTION_EVENT_ACTION_UP)
252         touchscreen.vpad.on = false;
253       if (prev_vpad.left != touchscreen.vpad.left
254           || prev_vpad.right != touchscreen.vpad.right
255           || prev_vpad.up != touchscreen.vpad.up
256           || prev_vpad.down != touchscreen.vpad.down
257           || prev_vpad.on != touchscreen.vpad.on) {
258         if (FETCH_WCB(*window, Special)) {
259           if (prev_vpad.left == false && touchscreen.vpad.left == true)
260             INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
261           else if (prev_vpad.right == false && touchscreen.vpad.right == true)
262             INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
263           else if (prev_vpad.up == false && touchscreen.vpad.up == true)
264             INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
265           else if (prev_vpad.down == false && touchscreen.vpad.down == true)
266             INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
267         }
268         if (FETCH_WCB(*window, SpecialUp)) {
269           if (prev_vpad.left == true && touchscreen.vpad.left == false)
270             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
271           if (prev_vpad.right == true && touchscreen.vpad.right == false)
272             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
273           if (prev_vpad.up == true && touchscreen.vpad.up == false)
274             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
275           if (prev_vpad.down == true && touchscreen.vpad.down == false)
276             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
277         }
278         return EVENT_HANDLED;
279       }
280     }
281     
282     /* Normal mouse events */
283     if (!touchscreen.vpad.on) {
284       window->State.MouseX = x;
285       window->State.MouseY = y;
286       if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
287         touchscreen.in_mmotion = true;
288         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
289       } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
290         touchscreen.in_mmotion = false;
291         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
292       } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
293         INVOKE_WCB(*window, Motion, (x, y));
294       }
295     }
296     
297     return EVENT_HANDLED;
298   }
299
300   /* Let Android handle other events (e.g. Back and Menu buttons) */
301   return EVENT_NOT_HANDLED;
302 }
303
304 /**
305  * Process the next main command.
306  */
307 void handle_cmd(struct android_app* app, int32_t cmd) {
308   SFG_Window* window = fgWindowByHandle(app->window);  /* may be NULL */
309   switch (cmd) {
310   /* App life cycle, in that order: */
311   case APP_CMD_START:
312     LOGI("handle_cmd: APP_CMD_START");
313     break;
314   case APP_CMD_RESUME:
315     LOGI("handle_cmd: APP_CMD_RESUME");
316     /* Cf. fgPlatformProcessSingleEvent */
317     break;
318   case APP_CMD_INIT_WINDOW: /* surfaceCreated */
319     /* The window is being shown, get it ready. */
320     LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window);
321     fgDisplay.pDisplay.single_native_window = app->window;
322     /* start|resume: glPlatformOpenWindow was waiting for Handle to be
323        defined and will now continue processing */
324     break;
325   case APP_CMD_GAINED_FOCUS:
326     LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
327     break;
328   case APP_CMD_WINDOW_RESIZED:
329     LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
330     if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
331       /* Make ProcessSingleEvent detect the new size, only available
332          after the next SwapBuffer */
333       glutPostRedisplay();
334     break;
335
336   case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
337     /* The system has asked us to save our current state, when it
338        pauses the application without destroying it right after. */
339     app->savedState = strdup("Detect me as non-NULL on next android_main");
340     app->savedStateSize = strlen(app->savedState) + 1;
341     LOGI("handle_cmd: APP_CMD_SAVE_STATE");
342     break;
343   case APP_CMD_PAUSE:
344     LOGI("handle_cmd: APP_CMD_PAUSE");
345     /* Cf. fgPlatformProcessSingleEvent */
346     break;
347   case APP_CMD_LOST_FOCUS:
348     LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
349     break;
350   case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
351     /* The application is being hidden, but may be restored */
352     LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
353     fghPlatformCloseWindowEGL(window);
354     window->State.NeedToInitContext = GL_TRUE;
355     fgDisplay.pDisplay.single_native_window = NULL;
356     break;
357   case APP_CMD_STOP:
358     LOGI("handle_cmd: APP_CMD_STOP");
359     break;
360   case APP_CMD_DESTROY: /* Activity.onDestroy */
361     LOGI("handle_cmd: APP_CMD_DESTROY");
362     /* User closed the application for good, let's kill the window */
363     {
364       /* Can't use fgWindowByHandle as app->window is NULL */
365       SFG_Window* window = fgStructure.CurrentWindow;
366       if (window != NULL) {
367         fgDestroyWindow(window);
368       } else {
369         LOGI("APP_CMD_DESTROY: No current window");
370       }
371     }
372     /* glue has already set android_app->destroyRequested=1 */
373     break;
374
375   case APP_CMD_CONFIG_CHANGED:
376     /* Handle rotation / orientation change */
377     LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
378     break;
379   case APP_CMD_LOW_MEMORY:
380     LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
381     break;
382   default:
383     LOGI("handle_cmd: unhandled cmd=%d", cmd);
384   }
385 }
386
387 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
388                            GLboolean positionUse, int x, int y,
389                            GLboolean sizeUse, int w, int h,
390                            GLboolean gameMode, GLboolean isSubWindow );
391
392 void fgPlatformProcessSingleEvent ( void )
393 {
394   /* When the screen is resized, the window handle still points to the
395      old window until the next SwapBuffer, while it's crucial to set
396      the size (onShape) correctly before the next onDisplay callback.
397      Plus we don't know if the next SwapBuffer already occurred at the
398      time we process the event (e.g. during onDisplay). */
399   /* So we do the check each time rather than on event. */
400   /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
401      2.3, that next SwapBuffer is fake (but still necessary to get the
402      new size). */
403   SFG_Window* window = fgStructure.CurrentWindow;
404   if (window != NULL && window->Window.Handle != NULL) {
405     int32_t width = ANativeWindow_getWidth(window->Window.Handle);
406     int32_t height = ANativeWindow_getHeight(window->Window.Handle);
407     if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
408       window->State.pWState.LastWidth = width;
409       window->State.pWState.LastHeight = height;
410       LOGI("width=%d, height=%d", width, height);
411       if( FETCH_WCB( *window, Reshape ) )
412         INVOKE_WCB( *window, Reshape, ( width, height ) );
413       else
414         glViewport( 0, 0, width, height );
415       glutPostRedisplay();
416     }
417   }
418
419   /* Read pending event. */
420   int ident;
421   int events;
422   struct android_poll_source* source;
423   /* This is called "ProcessSingleEvent" but this means we'd only
424      process ~60 (screen Hz) mouse events per second, plus other ports
425      are processing all events already.  So let's process all pending
426      events. */
427   /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
428   while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
429     /* Process this event. */
430     if (source != NULL) {
431       source->process(source->app, source);
432     }
433   }
434
435   /* If we're not in RESUME state, Android paused us, so wait */
436   struct android_app* app = fgDisplay.pDisplay.app;
437   if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
438       INVOKE_WCB(*window, Pause, ());
439
440     int FOREVER = -1;
441     while (app->destroyRequested != 1 && (app->activityState != APP_CMD_RESUME)) {
442       if ((ident=ALooper_pollOnce(FOREVER, NULL, &events, (void**)&source)) >= 0) {
443         /* Process this event. */
444         if (source != NULL) {
445           source->process(source->app, source);
446         }
447       }
448     }
449     /* Coming back from a pause: */
450     /* - Recreate window context and surface */
451     /* - Call user-defined hook to restore resources (textures...) */
452     /* - Exit pause looop */
453     if (app->destroyRequested != 1) {
454       /* Android is full-screen only, simplified call.. */
455       /* Ideally we'd have a fgPlatformReopenWindow() */
456       /* If we're hidden by a non-fullscreen or translucent activity,
457          we'll be paused but not stopped, and keep the current
458          surface; in which case fgPlatformOpenWindow will no-op. */
459       fgPlatformOpenWindow(window, "", GL_FALSE, 0, 0, GL_FALSE, 0, 0, GL_FALSE, GL_FALSE);
460
461       if (!FETCH_WCB(*window, InitContext))
462           fgWarning("Resuming application, but no callback to reload context resources (glutInitContextFunc)");
463     }
464
465     INVOKE_WCB(*window, Resume, ());
466   }
467 }
468
469 void fgPlatformMainLoopPreliminaryWork ( void )
470 {
471   LOGI("fgPlatformMainLoopPreliminaryWork\n");
472
473   key_init();
474
475   /* Make sure glue isn't stripped. */
476   /* JNI entry points need to be bundled even when linking statically */
477   app_dummy();
478 }