4 * The Android-specific windows message processing methods.
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
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:
18 * The above copyright notice and this permission notice shall be included
19 * in all copies or substantial portions of the Software.
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.
29 #include <GL/freeglut.h>
30 #include "fg_internal.h"
32 #include "egl/fg_window_egl.h"
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>
40 static struct touchscreen touchscreen;
41 static unsigned char key_a2fg[256];
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
67 #define EVENT_HANDLED 1
68 #define EVENT_NOT_HANDLED 0
71 * Initialize Android keycode to GLUT keycode mapping
73 static void key_init() {
74 memset(key_a2fg, 0, sizeof(key_a2fg));
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;
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;
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;
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;
109 * Convert an Android key event to ASCII.
111 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
112 int32_t code = AKeyEvent_getKeyCode(event);
114 /* Handle a few special cases: */
118 case AKEYCODE_FORWARD_DEL:
120 case AKEYCODE_ESCAPE:
124 /* Get usable JNI context */
125 JNIEnv* env = app->activity->env;
126 JavaVM* vm = app->activity->vm;
127 (*vm)->AttachCurrentThread(vm, &env, NULL);
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));
136 /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */
137 (*vm)->DetachCurrentThread(vm);
143 * Request a window resize
145 void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
147 fprintf(stderr, "fgPlatformReshapeWindow: STUB\n");
151 * A static helper function to execute display callback for a window
153 void fgPlatformDisplayWindow ( SFG_Window *window )
155 fghRedrawWindow ( window ) ;
158 unsigned long fgPlatformSystemTime ( void )
161 gettimeofday( &now, NULL );
162 return now.tv_usec/1000 + now.tv_sec*1000;
166 * Does the magic required to relinquish the CPU until something interesting
169 void fgPlatformSleepForEvents( long msec )
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
181 * Process the next input event.
183 int32_t handle_input(struct android_app* app, AInputEvent* event) {
184 SFG_Window* window = fgWindowByHandle(app->window);
186 return EVENT_NOT_HANDLED;
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). */
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);
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;
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;
225 int32_t source = AInputEvent_getSource(event);
226 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION
227 && source == AINPUT_SOURCE_TOUCHSCREEN) {
228 int32_t action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK;
229 /* Pointer ID for clicks */
230 int32_t pidx = AMotionEvent_getAction(event) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
231 /* TODO: Handle multi-touch; also handle multiple sources/devices */
232 /* cf. http://sourceforge.net/mailarchive/forum.php?thread_name=20120518071314.GA28061%40perso.beuc.net&forum_name=freeglut-developer */
234 LOGI("motion action=%d index=%d source=%d", action, pidx, source);
235 int count = AMotionEvent_getPointerCount(event);
237 for (i = 0; i < count; i++) {
238 LOGI("multi(%d): %.01f,%.01f",
239 AMotionEvent_getPointerId(event, i),
240 AMotionEvent_getX(event, i), AMotionEvent_getY(event, i));
243 float x = AMotionEvent_getX(event, 0);
244 float y = AMotionEvent_getY(event, 0);
246 /* Virtual arrows PAD */
247 /* Don't interfere with existing mouse move event */
248 if (!touchscreen.in_mmotion) {
249 struct vpad_state prev_vpad = touchscreen.vpad;
250 touchscreen.vpad.left = touchscreen.vpad.right
251 = touchscreen.vpad.up = touchscreen.vpad.down = false;
253 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
254 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
255 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
256 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
257 touchscreen.vpad.left = true;
258 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
259 touchscreen.vpad.right = true;
260 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
261 touchscreen.vpad.down = true;
262 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
263 touchscreen.vpad.up = true;
265 if (action == AMOTION_EVENT_ACTION_DOWN &&
266 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
267 touchscreen.vpad.on = true;
268 if (action == AMOTION_EVENT_ACTION_UP)
269 touchscreen.vpad.on = false;
270 if (prev_vpad.left != touchscreen.vpad.left
271 || prev_vpad.right != touchscreen.vpad.right
272 || prev_vpad.up != touchscreen.vpad.up
273 || prev_vpad.down != touchscreen.vpad.down
274 || prev_vpad.on != touchscreen.vpad.on) {
275 if (FETCH_WCB(*window, Special)) {
276 if (prev_vpad.left == false && touchscreen.vpad.left == true)
277 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
278 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
279 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
280 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
281 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
282 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
283 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
285 if (FETCH_WCB(*window, SpecialUp)) {
286 if (prev_vpad.left == true && touchscreen.vpad.left == false)
287 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
288 if (prev_vpad.right == true && touchscreen.vpad.right == false)
289 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
290 if (prev_vpad.up == true && touchscreen.vpad.up == false)
291 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
292 if (prev_vpad.down == true && touchscreen.vpad.down == false)
293 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
295 return EVENT_HANDLED;
299 /* Normal mouse events */
300 if (!touchscreen.vpad.on) {
301 window->State.MouseX = x;
302 window->State.MouseY = y;
303 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
304 touchscreen.in_mmotion = true;
305 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
306 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
307 touchscreen.in_mmotion = false;
308 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
309 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
310 INVOKE_WCB(*window, Motion, (x, y));
314 return EVENT_HANDLED;
317 /* Let Android handle other events (e.g. Back and Menu buttons) */
318 return EVENT_NOT_HANDLED;
322 * Process the next main command.
324 void handle_cmd(struct android_app* app, int32_t cmd) {
325 SFG_Window* window = fgWindowByHandle(app->window); /* may be NULL */
327 /* App life cycle, in that order: */
329 LOGI("handle_cmd: APP_CMD_START");
332 LOGI("handle_cmd: APP_CMD_RESUME");
333 /* Cf. fgPlatformProcessSingleEvent */
335 case APP_CMD_INIT_WINDOW: /* surfaceCreated */
336 /* The window is being shown, get it ready. */
337 LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window);
338 fgDisplay.pDisplay.single_native_window = app->window;
339 /* start|resume: glPlatformOpenWindow was waiting for Handle to be
340 defined and will now continue processing */
342 case APP_CMD_GAINED_FOCUS:
343 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
345 case APP_CMD_WINDOW_RESIZED:
346 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
347 if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
348 /* Make ProcessSingleEvent detect the new size, only available
349 after the next SwapBuffer */
353 case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
354 /* The system has asked us to save our current state, when it
355 pauses the application without destroying it right after. */
356 app->savedState = strdup("Detect me as non-NULL on next android_main");
357 app->savedStateSize = strlen(app->savedState) + 1;
358 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
361 LOGI("handle_cmd: APP_CMD_PAUSE");
362 /* Cf. fgPlatformProcessSingleEvent */
364 case APP_CMD_LOST_FOCUS:
365 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
367 case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
368 /* The application is being hidden, but may be restored */
369 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
370 fghPlatformCloseWindowEGL(window);
371 window->State.NeedToInitContext = GL_TRUE;
372 fgDisplay.pDisplay.single_native_window = NULL;
375 LOGI("handle_cmd: APP_CMD_STOP");
377 case APP_CMD_DESTROY: /* Activity.onDestroy */
378 LOGI("handle_cmd: APP_CMD_DESTROY");
379 /* User closed the application for good, let's kill the window */
381 /* Can't use fgWindowByHandle as app->window is NULL */
382 SFG_Window* window = fgStructure.CurrentWindow;
383 if (window != NULL) {
384 fgDestroyWindow(window);
386 LOGI("APP_CMD_DESTROY: No current window");
389 /* glue has already set android_app->destroyRequested=1 */
392 case APP_CMD_CONFIG_CHANGED:
393 /* Handle rotation / orientation change */
394 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
396 case APP_CMD_LOW_MEMORY:
397 LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
400 LOGI("handle_cmd: unhandled cmd=%d", cmd);
404 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
405 GLboolean positionUse, int x, int y,
406 GLboolean sizeUse, int w, int h,
407 GLboolean gameMode, GLboolean isSubWindow );
409 void fgPlatformProcessSingleEvent ( void )
411 /* When the screen is resized, the window handle still points to the
412 old window until the next SwapBuffer, while it's crucial to set
413 the size (onShape) correctly before the next onDisplay callback.
414 Plus we don't know if the next SwapBuffer already occurred at the
415 time we process the event (e.g. during onDisplay). */
416 /* So we do the check each time rather than on event. */
417 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
418 2.3, that next SwapBuffer is fake (but still necessary to get the
420 SFG_Window* window = fgStructure.CurrentWindow;
421 if (window != NULL && window->Window.Handle != NULL) {
422 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
423 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
424 if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
425 window->State.pWState.LastWidth = width;
426 window->State.pWState.LastHeight = height;
427 LOGI("width=%d, height=%d", width, height);
428 if( FETCH_WCB( *window, Reshape ) )
429 INVOKE_WCB( *window, Reshape, ( width, height ) );
431 glViewport( 0, 0, width, height );
436 /* Read pending event. */
439 struct android_poll_source* source;
440 /* This is called "ProcessSingleEvent" but this means we'd only
441 process ~60 (screen Hz) mouse events per second, plus other ports
442 are processing all events already. So let's process all pending
444 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
445 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
446 /* Process this event. */
447 if (source != NULL) {
448 source->process(source->app, source);
452 /* If we're not in RESUME state, Android paused us, so wait */
453 struct android_app* app = fgDisplay.pDisplay.app;
454 if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
455 INVOKE_WCB(*window, Pause, ());
458 while (app->destroyRequested != 1 && (app->activityState != APP_CMD_RESUME)) {
459 if ((ident=ALooper_pollOnce(FOREVER, NULL, &events, (void**)&source)) >= 0) {
460 /* Process this event. */
461 if (source != NULL) {
462 source->process(source->app, source);
466 /* Coming back from a pause: */
467 /* - Recreate window context and surface */
468 /* - Call user-defined hook to restore resources (textures...) */
469 /* - Exit pause looop */
470 if (app->destroyRequested != 1) {
471 /* Android is full-screen only, simplified call.. */
472 /* Ideally we'd have a fgPlatformReopenWindow() */
473 /* If we're hidden by a non-fullscreen or translucent activity,
474 we'll be paused but not stopped, and keep the current
475 surface; in which case fgPlatformOpenWindow will no-op. */
476 fgPlatformOpenWindow(window, "", GL_FALSE, 0, 0, GL_FALSE, 0, 0, GL_FALSE, GL_FALSE);
478 if (!FETCH_WCB(*window, InitContext))
479 fgWarning("Resuming application, but no callback to reload context resources (glutInitContextFunc)");
482 INVOKE_WCB(*window, Resume, ());
486 void fgPlatformMainLoopPreliminaryWork ( void )
488 LOGI("fgPlatformMainLoopPreliminaryWork\n");
492 /* Make sure glue isn't stripped. */
493 /* JNI entry points need to be bundled even when linking statically */