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