typo
[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 "fg_main.h"
32 #include "egl/fg_window_egl.h"
33
34 #include <android/log.h>
35 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "FreeGLUT", __VA_ARGS__))
36 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "FreeGLUT", __VA_ARGS__))
37 #include <android/native_app_glue/android_native_app_glue.h>
38 #include <android/keycodes.h>
39
40 static struct touchscreen touchscreen;
41 static unsigned char key_a2fg[256];
42
43 /* Cf. http://developer.android.com/reference/android/view/KeyEvent.html */
44 /* These codes are missing in <android/keycodes.h> */
45 /* Don't convert to enum, since it may conflict with future version of
46    that <android/keycodes.h> */
47 #define AKEYCODE_FORWARD_DEL 112
48 #define AKEYCODE_CTRL_LEFT 113
49 #define AKEYCODE_CTRL_RIGHT 114
50 #define AKEYCODE_MOVE_HOME 122
51 #define AKEYCODE_MOVE_END 123
52 #define AKEYCODE_INSERT 124
53 #define AKEYCODE_ESCAPE 127
54 #define AKEYCODE_F1 131
55 #define AKEYCODE_F2 132
56 #define AKEYCODE_F3 133
57 #define AKEYCODE_F4 134
58 #define AKEYCODE_F5 135
59 #define AKEYCODE_F6 136
60 #define AKEYCODE_F7 137
61 #define AKEYCODE_F8 138
62 #define AKEYCODE_F9 139
63 #define AKEYCODE_F10 140
64 #define AKEYCODE_F11 141
65 #define AKEYCODE_F12 142
66
67 #define EVENT_HANDLED 1
68 #define EVENT_NOT_HANDLED 0
69
70 /**
71  * Initialize Android keycode to GLUT keycode mapping
72  */
73 static void key_init() {
74   memset(key_a2fg, 0, sizeof(key_a2fg));
75
76   key_a2fg[AKEYCODE_F1]  = GLUT_KEY_F1;
77   key_a2fg[AKEYCODE_F2]  = GLUT_KEY_F2;
78   key_a2fg[AKEYCODE_F3]  = GLUT_KEY_F3;
79   key_a2fg[AKEYCODE_F4]  = GLUT_KEY_F4;
80   key_a2fg[AKEYCODE_F5]  = GLUT_KEY_F5;
81   key_a2fg[AKEYCODE_F6]  = GLUT_KEY_F6;
82   key_a2fg[AKEYCODE_F7]  = GLUT_KEY_F7;
83   key_a2fg[AKEYCODE_F8]  = GLUT_KEY_F8;
84   key_a2fg[AKEYCODE_F9]  = GLUT_KEY_F9;
85   key_a2fg[AKEYCODE_F10] = GLUT_KEY_F10;
86   key_a2fg[AKEYCODE_F11] = GLUT_KEY_F11;
87   key_a2fg[AKEYCODE_F12] = GLUT_KEY_F12;
88
89   key_a2fg[AKEYCODE_PAGE_UP]   = GLUT_KEY_PAGE_UP;
90   key_a2fg[AKEYCODE_PAGE_DOWN] = GLUT_KEY_PAGE_DOWN;
91   key_a2fg[AKEYCODE_MOVE_HOME] = GLUT_KEY_HOME;
92   key_a2fg[AKEYCODE_MOVE_END]  = GLUT_KEY_END;
93   key_a2fg[AKEYCODE_INSERT]    = GLUT_KEY_INSERT;
94
95   key_a2fg[AKEYCODE_DPAD_UP]    = GLUT_KEY_UP;
96   key_a2fg[AKEYCODE_DPAD_DOWN]  = GLUT_KEY_DOWN;
97   key_a2fg[AKEYCODE_DPAD_LEFT]  = GLUT_KEY_LEFT;
98   key_a2fg[AKEYCODE_DPAD_RIGHT] = GLUT_KEY_RIGHT;
99
100   key_a2fg[AKEYCODE_ALT_LEFT]    = GLUT_KEY_ALT_L;
101   key_a2fg[AKEYCODE_ALT_RIGHT]   = GLUT_KEY_ALT_R;
102   key_a2fg[AKEYCODE_SHIFT_LEFT]  = GLUT_KEY_SHIFT_L;
103   key_a2fg[AKEYCODE_SHIFT_RIGHT] = GLUT_KEY_SHIFT_R;
104   key_a2fg[AKEYCODE_CTRL_LEFT]   = GLUT_KEY_CTRL_L;
105   key_a2fg[AKEYCODE_CTRL_RIGHT]  = GLUT_KEY_CTRL_R;
106 }
107
108 /**
109  * Convert an Android key event to ASCII.
110  */
111 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
112   int32_t code = AKeyEvent_getKeyCode(event);
113
114   /* Handle a few special cases: */
115   switch (code) {
116   case AKEYCODE_DEL:
117     return 8;
118   case AKEYCODE_FORWARD_DEL:
119     return 127;
120   case AKEYCODE_ESCAPE:
121     return 27;
122   }
123
124   /* Get usable JNI context */
125   JNIEnv* env = app->activity->env;
126   JavaVM* vm = app->activity->vm;
127   (*vm)->AttachCurrentThread(vm, &env, NULL);
128
129   jclass KeyEventClass = (*env)->FindClass(env, "android/view/KeyEvent");
130   jmethodID KeyEventConstructor = (*env)->GetMethodID(env, KeyEventClass, "<init>", "(II)V");
131   jobject keyEvent = (*env)->NewObject(env, KeyEventClass, KeyEventConstructor,
132                                        AKeyEvent_getAction(event), AKeyEvent_getKeyCode(event));
133   jmethodID KeyEvent_getUnicodeChar = (*env)->GetMethodID(env, KeyEventClass, "getUnicodeChar", "(I)I");
134   int ascii = (*env)->CallIntMethod(env, keyEvent, KeyEvent_getUnicodeChar, AKeyEvent_getMetaState(event));
135
136   /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */
137   (*vm)->DetachCurrentThread(vm);
138
139   return ascii;
140 }
141
142 /*
143  * Request a window resize
144  */
145 void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
146 {
147   fprintf(stderr, "fgPlatformReshapeWindow: STUB\n");
148 }
149
150 /*
151  * A static helper function to execute display callback for a window
152  */
153 void fgPlatformDisplayWindow ( SFG_Window *window )
154 {
155   fghRedrawWindow ( window ) ;
156 }
157
158 unsigned long fgPlatformSystemTime ( void )
159 {
160   struct timeval now;
161   gettimeofday( &now, NULL );
162   return now.tv_usec/1000 + now.tv_sec*1000;
163 }
164
165 /*
166  * Does the magic required to relinquish the CPU until something interesting
167  * happens.
168  */
169 void fgPlatformSleepForEvents( long msec )
170 {
171     /* Android's NativeActivity relies on a Looper/ALooper object to
172        notify about events.  The Looper object is plugged on two
173        internal pipe(2)s to detect system and input events.  Sadly you
174        can only ask the Looper for an event, not just ask whether
175        there is a pending event (and process it later).  Consequently,
176        short of redesigning NativeActivity, we cannot
177        SleepForEvents. */
178 }
179
180 /**
181  * Process the next input event.
182  */
183 int32_t handle_input(struct android_app* app, AInputEvent* event) {
184   SFG_Window* window = fgWindowByHandle(app->window);
185   if (window == NULL)
186     return EVENT_NOT_HANDLED;
187
188   /* FIXME: in Android, when a key is repeated, down
189      and up events happen most often at the exact same time.  This
190      makes it impossible to animate based on key press time. */
191   /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */ 
192   /* This looks like a bug in the Android virtual keyboard system :/
193      Real buttons such as the Back button appear to work correctly
194      (series of down events with proper getRepeatCount value). */
195   
196   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
197     /* LOGI("action: %d", AKeyEvent_getAction(event)); */
198     /* LOGI("keycode: %d", code); */
199     int32_t code = AKeyEvent_getKeyCode(event);
200
201     if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
202       int32_t keypress = 0;
203       unsigned char ascii = 0;
204       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
205         INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
206         return EVENT_HANDLED;
207       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
208         INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
209         return EVENT_HANDLED;
210       }
211     }
212     else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
213       int32_t keypress = 0;
214       unsigned char ascii = 0;
215       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
216         INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
217         return EVENT_HANDLED;
218       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
219         INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
220         return EVENT_HANDLED;
221       }
222     }
223   }
224
225   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
226     int32_t action = AMotionEvent_getAction(event);
227     float x = AMotionEvent_getX(event, 0);
228     float y = AMotionEvent_getY(event, 0);
229     LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
230     
231     /* Virtual arrows PAD */
232     /* Don't interfere with existing mouse move event */
233     if (!touchscreen.in_mmotion) {
234       struct vpad_state prev_vpad = touchscreen.vpad;
235       touchscreen.vpad.left = touchscreen.vpad.right
236         = touchscreen.vpad.up = touchscreen.vpad.down = false;
237
238       /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
239       int32_t height = ANativeWindow_getHeight(window->Window.Handle);
240       if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
241         if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
242           touchscreen.vpad.left = true;
243         if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
244           touchscreen.vpad.right = true;
245         if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
246           touchscreen.vpad.down = true;
247         if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
248           touchscreen.vpad.up = true;
249       }
250       if (action == AMOTION_EVENT_ACTION_DOWN && 
251           (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
252         touchscreen.vpad.on = true;
253       if (action == AMOTION_EVENT_ACTION_UP)
254         touchscreen.vpad.on = false;
255       if (prev_vpad.left != touchscreen.vpad.left
256           || prev_vpad.right != touchscreen.vpad.right
257           || prev_vpad.up != touchscreen.vpad.up
258           || prev_vpad.down != touchscreen.vpad.down
259           || prev_vpad.on != touchscreen.vpad.on) {
260         if (FETCH_WCB(*window, Special)) {
261           if (prev_vpad.left == false && touchscreen.vpad.left == true)
262             INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
263           else if (prev_vpad.right == false && touchscreen.vpad.right == true)
264             INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
265           else if (prev_vpad.up == false && touchscreen.vpad.up == true)
266             INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
267           else if (prev_vpad.down == false && touchscreen.vpad.down == true)
268             INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
269         }
270         if (FETCH_WCB(*window, SpecialUp)) {
271           if (prev_vpad.left == true && touchscreen.vpad.left == false)
272             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
273           if (prev_vpad.right == true && touchscreen.vpad.right == false)
274             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
275           if (prev_vpad.up == true && touchscreen.vpad.up == false)
276             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
277           if (prev_vpad.down == true && touchscreen.vpad.down == false)
278             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
279         }
280         return EVENT_HANDLED;
281       }
282     }
283     
284     /* Normal mouse events */
285     if (!touchscreen.vpad.on) {
286       window->State.MouseX = x;
287       window->State.MouseY = y;
288       LOGI("Changed mouse position: %f,%f", x, y);
289       if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
290         touchscreen.in_mmotion = true;
291         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
292       } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
293         touchscreen.in_mmotion = false;
294         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
295       } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
296         INVOKE_WCB(*window, Motion, (x, y));
297       }
298     }
299     
300     return EVENT_HANDLED;
301   }
302
303   /* Let Android handle other events (e.g. Back and Menu buttons) */
304   return EVENT_NOT_HANDLED;
305 }
306
307 /**
308  * Process the next main command.
309  */
310 void handle_cmd(struct android_app* app, int32_t cmd) {
311   SFG_Window* window = fgWindowByHandle(app->window);  /* may be NULL */
312   switch (cmd) {
313   /* App life cycle, in that order: */
314   case APP_CMD_START:
315     LOGI("handle_cmd: APP_CMD_START");
316     break;
317   case APP_CMD_RESUME:
318     LOGI("handle_cmd: APP_CMD_RESUME");
319     /* Cf. fgPlatformProcessSingleEvent */
320     break;
321   case APP_CMD_INIT_WINDOW: /* surfaceCreated */
322     /* The window is being shown, get it ready. */
323     LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window);
324     fgDisplay.pDisplay.single_native_window = app->window;
325     /* start|resume: glPlatformOpenWindow was waiting for Handle to be
326        defined and will now continue processing */
327     break;
328   case APP_CMD_GAINED_FOCUS:
329     LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
330     break;
331   case APP_CMD_WINDOW_RESIZED:
332     LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
333     if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
334       /* Make ProcessSingleEvent detect the new size, only available
335          after the next SwapBuffer */
336       glutPostRedisplay();
337     break;
338
339   case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
340     /* The system has asked us to save our current state, when it
341        pauses the application without destroying it right after. */
342     app->savedState = strdup("Detect me as non-NULL on next android_main");
343     app->savedStateSize = strlen(app->savedState) + 1;
344     LOGI("handle_cmd: APP_CMD_SAVE_STATE");
345     break;
346   case APP_CMD_PAUSE:
347     LOGI("handle_cmd: APP_CMD_PAUSE");
348     /* Cf. fgPlatformProcessSingleEvent */
349     break;
350   case APP_CMD_LOST_FOCUS:
351     LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
352     break;
353   case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
354     /* The application is being hidden, but may be restored */
355     LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
356     fghPlatformCloseWindowEGL(window);
357     fgDisplay.pDisplay.single_native_window = NULL;
358     break;
359   case APP_CMD_STOP:
360     LOGI("handle_cmd: APP_CMD_STOP");
361     break;
362   case APP_CMD_DESTROY: /* Activity.onDestroy */
363     LOGI("handle_cmd: APP_CMD_DESTROY");
364     /* User closed the application for good, let's kill the window */
365     {
366       /* Can't use fgWindowByHandle as app->window is NULL */
367       SFG_Window* window = fgStructure.CurrentWindow;
368       if (window != NULL) {
369         fgDestroyWindow(window);
370       } else {
371         LOGI("APP_CMD_DESTROY: No current window");
372       }
373     }
374     /* glue has already set android_app->destroyRequested=1 */
375     break;
376
377   case APP_CMD_CONFIG_CHANGED:
378     /* Handle rotation / orientation change */
379     LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
380     break;
381   case APP_CMD_LOW_MEMORY:
382     LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
383     break;
384   default:
385     LOGI("handle_cmd: unhandled cmd=%d", cmd);
386   }
387 }
388
389 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
390                            GLboolean positionUse, int x, int y,
391                            GLboolean sizeUse, int w, int h,
392                            GLboolean gameMode, GLboolean isSubWindow );
393
394 void fgPlatformProcessSingleEvent ( void )
395 {
396   /* When the screen is resized, the window handle still points to the
397      old window until the next SwapBuffer, while it's crucial to set
398      the size (onShape) correctly before the next onDisplay callback.
399      Plus we don't know if the next SwapBuffer already occurred at the
400      time we process the event (e.g. during onDisplay). */
401   /* So we do the check each time rather than on event. */
402   /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
403      2.3, that next SwapBuffer is fake (but still necessary to get the
404      new size). */
405   SFG_Window* window = fgStructure.CurrentWindow;
406   if (window != NULL && window->Window.Handle != NULL) {
407     int32_t width = ANativeWindow_getWidth(window->Window.Handle);
408     int32_t height = ANativeWindow_getHeight(window->Window.Handle);
409     if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
410       window->State.pWState.LastWidth = width;
411       window->State.pWState.LastHeight = height;
412       LOGI("width=%d, height=%d", width, height);
413       if( FETCH_WCB( *window, Reshape ) )
414         INVOKE_WCB( *window, Reshape, ( width, height ) );
415       else
416         glViewport( 0, 0, width, height );
417       glutPostRedisplay();
418     }
419   }
420
421   /* Read pending event. */
422   int ident;
423   int events;
424   struct android_poll_source* source;
425   /* This is called "ProcessSingleEvent" but this means we'd only
426      process ~60 (screen Hz) mouse events per second, plus other ports
427      are processing all events already.  So let's process all pending
428      events. */
429   /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
430   while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
431     /* Process this event. */
432     if (source != NULL) {
433       source->process(source->app, source);
434     }
435   }
436
437   /* If we're not in RESUME state, Android paused us, so wait */
438   struct android_app* app = fgDisplay.pDisplay.app;
439   if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
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     /* If 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       /* TODO: INVOKE_WCB(*window, Pause?); */
461       /* TODO: INVOKE_WCB(*window, LoadResources/ContextLost/...?); */
462       /* TODO: INVOKE_WCB(*window, Resume?); */
463     }
464   }
465 }
466
467 void fgPlatformMainLoopPreliminaryWork ( void )
468 {
469   LOGI("fgPlatformMainLoopPreliminaryWork\n");
470
471   key_init();
472
473   /* Make sure glue isn't stripped. */
474   /* JNI entry points need to be bundled even when linking statically */
475   app_dummy();
476 }