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