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