Explain why fgPlatformSleepForEvents is no-op under Android
[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
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
137   return ascii;
138 }
139
140 /*
141  * Handle a window configuration change. When no reshape
142  * callback is hooked, the viewport size is updated to
143  * match the new window size.
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 = fgStructure.CurrentWindow;
185
186   /* FIXME: in Android, when key is repeated, down and up events
187      happen most often at the exact same time.  This makes it
188      impossible to animate based on key press time. */
189   /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
190   
191   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
192     /* LOGI("action: %d", AKeyEvent_getAction(event)); */
193     /* LOGI("keycode: %d", code); */
194     int32_t code = AKeyEvent_getKeyCode(event);
195
196     if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
197       int32_t keypress = 0;
198       unsigned char ascii = 0;
199       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
200         INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
201         return EVENT_HANDLED;
202       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
203         INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
204         return EVENT_HANDLED;
205       }
206     }
207     else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
208       int32_t keypress = 0;
209       unsigned char ascii = 0;
210       if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
211         INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
212         return EVENT_HANDLED;
213       } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
214         INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
215         return EVENT_HANDLED;
216       }
217     }
218   }
219
220   if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
221     int32_t action = AMotionEvent_getAction(event);
222     float x = AMotionEvent_getX(event, 0);
223     float y = AMotionEvent_getY(event, 0);
224     LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
225     
226     /* Virtual arrows PAD */
227     /* Don't interfere with existing mouse move event */
228     if (!touchscreen.in_mmotion) {
229       struct vpad_state prev_vpad = touchscreen.vpad;
230       touchscreen.vpad.left = touchscreen.vpad.right
231         = touchscreen.vpad.up = touchscreen.vpad.down = false;
232
233       /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
234       int32_t height = ANativeWindow_getHeight(window->Window.Handle);
235       if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
236         if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
237           touchscreen.vpad.left = true;
238         if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
239           touchscreen.vpad.right = true;
240         if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
241           touchscreen.vpad.down = true;
242         if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
243           touchscreen.vpad.up = true;
244       }
245       if (action == AMOTION_EVENT_ACTION_DOWN && 
246           (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
247         touchscreen.vpad.on = true;
248       if (action == AMOTION_EVENT_ACTION_UP)
249         touchscreen.vpad.on = false;
250       if (prev_vpad.left != touchscreen.vpad.left
251           || prev_vpad.right != touchscreen.vpad.right
252           || prev_vpad.up != touchscreen.vpad.up
253           || prev_vpad.down != touchscreen.vpad.down
254           || prev_vpad.on != touchscreen.vpad.on) {
255         if (FETCH_WCB(*window, Special)) {
256           if (prev_vpad.left == false && touchscreen.vpad.left == true)
257             INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
258           else if (prev_vpad.right == false && touchscreen.vpad.right == true)
259             INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
260           else if (prev_vpad.up == false && touchscreen.vpad.up == true)
261             INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
262           else if (prev_vpad.down == false && touchscreen.vpad.down == true)
263             INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
264         }
265         if (FETCH_WCB(*window, SpecialUp)) {
266           if (prev_vpad.left == true && touchscreen.vpad.left == false)
267             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
268           if (prev_vpad.right == true && touchscreen.vpad.right == false)
269             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
270           if (prev_vpad.up == true && touchscreen.vpad.up == false)
271             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
272           if (prev_vpad.down == true && touchscreen.vpad.down == false)
273             INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
274         }
275         return EVENT_HANDLED;
276       }
277     }
278     
279     /* Normal mouse events */
280     if (!touchscreen.vpad.on) {
281       window->State.MouseX = x;
282       window->State.MouseY = y;
283       LOGI("Changed mouse position: %f,%f", x, y);
284       if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
285         touchscreen.in_mmotion = true;
286         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
287       } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
288         touchscreen.in_mmotion = false;
289         INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
290       } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
291         INVOKE_WCB(*window, Motion, (x, y));
292       }
293     }
294     
295     return EVENT_HANDLED;
296   }
297
298   /* Let Android handle other events (e.g. Back and Menu buttons) */
299   return EVENT_NOT_HANDLED;
300 }
301
302 /**
303  * Process the next main command.
304  */
305 void handle_cmd(struct android_app* app, int32_t cmd) {
306   switch (cmd) {
307   case APP_CMD_SAVE_STATE:
308     /* The system has asked us to save our current state.  Do so. */
309     LOGI("handle_cmd: APP_CMD_SAVE_STATE");
310     break;
311   case APP_CMD_INIT_WINDOW:
312     /* The window is being shown, get it ready. */
313     LOGI("handle_cmd: APP_CMD_INIT_WINDOW");
314     fgDisplay.pDisplay.single_window->Window.Handle = app->window;
315     /* glPlatformOpenWindow was waiting for Handle to be defined and
316        will now return from fgPlatformProcessSingleEvent() */
317     break;
318   case APP_CMD_TERM_WINDOW:
319     /* The window is being hidden or closed, clean it up. */
320     LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
321     fgDestroyWindow(fgDisplay.pDisplay.single_window);
322     break;
323   case APP_CMD_DESTROY:
324     /* Not reached because GLUT exit()s when last window is closed */
325     LOGI("handle_cmd: APP_CMD_DESTROY");
326     break;
327   case APP_CMD_GAINED_FOCUS:
328     LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
329     break;
330   case APP_CMD_LOST_FOCUS:
331     LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
332     break;
333   case APP_CMD_CONFIG_CHANGED:
334     /* Handle rotation / orientation change */
335     LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
336     break;
337   case APP_CMD_WINDOW_RESIZED:
338     LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
339     if (fgDisplay.pDisplay.single_window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
340       /* Make ProcessSingleEvent detect the new size, only available
341          after the next SwapBuffer */
342       glutPostRedisplay();
343     break;
344   default:
345     LOGI("handle_cmd: unhandled cmd=%d", cmd);
346   }
347 }
348
349 void fgPlatformProcessSingleEvent ( void )
350 {
351   static int32_t last_width = -1;
352   static int32_t last_height = -1;
353
354   /* When the screen is resized, the window handle still points to the
355      old window until the next SwapBuffer, while it's crucial to set
356      the size (onShape) correctly before the next onDisplay callback.
357      Plus we don't know if the next SwapBuffer already occurred at the
358      time we process the event (e.g. during onDisplay). */
359   /* So we do the check each time rather than on event. */
360   /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
361      2.3, that next SwapBuffer is fake (but still necessary to get the
362      new size). */
363   SFG_Window* window = fgDisplay.pDisplay.single_window;
364   if (window != NULL && window->Window.Handle != NULL) {
365     int32_t width = ANativeWindow_getWidth(window->Window.Handle);
366     int32_t height = ANativeWindow_getHeight(window->Window.Handle);
367     if (width != last_width || height != last_height) {
368       last_width = width;
369       last_height = height;
370       LOGI("width=%d, height=%d", width, height);
371       if( FETCH_WCB( *window, Reshape ) )
372         INVOKE_WCB( *window, Reshape, ( width, height ) );
373       else
374         glViewport( 0, 0, width, height );
375       glutPostRedisplay();
376     }
377   }
378
379   /* Read pending event. */
380   int ident;
381   int events;
382   struct android_poll_source* source;
383   /* This is called "ProcessSingleEvent" but this means we'd only
384      process ~60 (screen Hz) mouse events per second, plus other ports
385      are processing all events already.  So let's process all pending
386      events. */
387   /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
388   while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
389     /* Process this event. */
390     if (source != NULL) {
391       source->process(source->app, source);
392     }
393   }
394 }
395
396 void fgPlatformMainLoopPreliminaryWork ( void )
397 {
398   printf("fgPlatformMainLoopPreliminaryWork\n");
399
400   key_init();
401
402   /* Make sure glue isn't stripped. */
403   /* JNI entry points need to be bundled even when linking statically */
404   app_dummy();
405 }
406
407 void fgPlatformDeinitialiseInputDevices ( void )
408 {
409   fprintf(stderr, "fgPlatformDeinitialiseInputDevices: STUB\n");
410 }