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"
31 #include "egl/fg_window_egl.h"
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>
39 static struct touchscreen touchscreen;
40 static unsigned char key_a2fg[256];
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
66 #define EVENT_HANDLED 1
67 #define EVENT_NOT_HANDLED 0
70 * Initialize Android keycode to GLUT keycode mapping
72 static void key_init() {
73 memset(key_a2fg, 0, sizeof(key_a2fg));
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;
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;
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;
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;
108 * Convert an Android key event to ASCII.
110 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
111 int32_t code = AKeyEvent_getKeyCode(event);
113 /* Handle a few special cases: */
117 case AKEYCODE_FORWARD_DEL:
119 case AKEYCODE_ESCAPE:
123 /* Get usable JNI context */
124 JNIEnv* env = app->activity->env;
125 JavaVM* vm = app->activity->vm;
126 (*vm)->AttachCurrentThread(vm, &env, NULL);
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));
135 /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */
136 (*vm)->DetachCurrentThread(vm);
141 unsigned long fgPlatformSystemTime ( void )
144 gettimeofday( &now, NULL );
145 return now.tv_usec/1000 + now.tv_sec*1000;
149 * Does the magic required to relinquish the CPU until something interesting
152 void fgPlatformSleepForEvents( long msec )
154 /* Android's NativeActivity relies on a Looper/ALooper object to
155 notify about events. The Looper object is plugged on two
156 internal pipe(2)s to detect system and input events. Sadly you
157 can only ask the Looper for an event, not just ask whether
158 there is a pending event (and process it later). Consequently,
159 short of redesigning NativeActivity, we cannot
164 * Process the next input event.
166 int32_t handle_input(struct android_app* app, AInputEvent* event) {
167 SFG_Window* window = fgWindowByHandle(app->window);
169 return EVENT_NOT_HANDLED;
171 /* FIXME: in Android, when a key is repeated, down
172 and up events happen most often at the exact same time. This
173 makes it impossible to animate based on key press time. */
174 /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
175 /* This looks like a bug in the Android virtual keyboard system :/
176 Real buttons such as the Back button appear to work correctly
177 (series of down events with proper getRepeatCount value). */
179 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
180 /* LOGI("action: %d", AKeyEvent_getAction(event)); */
181 /* LOGI("keycode: %d", code); */
182 int32_t code = AKeyEvent_getKeyCode(event);
184 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
185 int32_t keypress = 0;
186 unsigned char ascii = 0;
187 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
188 INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
189 return EVENT_HANDLED;
190 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
191 INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
192 return EVENT_HANDLED;
195 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
196 int32_t keypress = 0;
197 unsigned char ascii = 0;
198 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
199 INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
200 return EVENT_HANDLED;
201 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
202 INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
203 return EVENT_HANDLED;
208 int32_t source = AInputEvent_getSource(event);
209 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION
210 && source == AINPUT_SOURCE_TOUCHSCREEN) {
211 int32_t action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK;
212 /* Pointer ID for clicks */
213 int32_t pidx = AMotionEvent_getAction(event) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
214 /* TODO: Handle multi-touch; also handle multiple sources/devices */
215 /* cf. http://sourceforge.net/mailarchive/forum.php?thread_name=20120518071314.GA28061%40perso.beuc.net&forum_name=freeglut-developer */
217 LOGI("motion action=%d index=%d source=%d", action, pidx, source);
218 int count = AMotionEvent_getPointerCount(event);
220 for (i = 0; i < count; i++) {
221 LOGI("multi(%d): %.01f,%.01f",
222 AMotionEvent_getPointerId(event, i),
223 AMotionEvent_getX(event, i), AMotionEvent_getY(event, i));
226 float x = AMotionEvent_getX(event, 0);
227 float y = AMotionEvent_getY(event, 0);
229 /* Virtual arrows PAD */
230 /* Don't interfere with existing mouse move event */
231 if (!touchscreen.in_mmotion) {
232 struct vpad_state prev_vpad = touchscreen.vpad;
233 touchscreen.vpad.left = touchscreen.vpad.right
234 = touchscreen.vpad.up = touchscreen.vpad.down = false;
236 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
237 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
238 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
239 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
240 touchscreen.vpad.left = true;
241 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
242 touchscreen.vpad.right = true;
243 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
244 touchscreen.vpad.down = true;
245 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
246 touchscreen.vpad.up = true;
248 if (action == AMOTION_EVENT_ACTION_DOWN &&
249 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
250 touchscreen.vpad.on = true;
251 if (action == AMOTION_EVENT_ACTION_UP)
252 touchscreen.vpad.on = false;
253 if (prev_vpad.left != touchscreen.vpad.left
254 || prev_vpad.right != touchscreen.vpad.right
255 || prev_vpad.up != touchscreen.vpad.up
256 || prev_vpad.down != touchscreen.vpad.down
257 || prev_vpad.on != touchscreen.vpad.on) {
258 if (FETCH_WCB(*window, Special)) {
259 if (prev_vpad.left == false && touchscreen.vpad.left == true)
260 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
261 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
262 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
263 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
264 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
265 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
266 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
268 if (FETCH_WCB(*window, SpecialUp)) {
269 if (prev_vpad.left == true && touchscreen.vpad.left == false)
270 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
271 if (prev_vpad.right == true && touchscreen.vpad.right == false)
272 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
273 if (prev_vpad.up == true && touchscreen.vpad.up == false)
274 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
275 if (prev_vpad.down == true && touchscreen.vpad.down == false)
276 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
278 return EVENT_HANDLED;
282 /* Normal mouse events */
283 if (!touchscreen.vpad.on) {
284 window->State.MouseX = x;
285 window->State.MouseY = y;
286 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
287 touchscreen.in_mmotion = true;
288 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
289 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
290 touchscreen.in_mmotion = false;
291 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
292 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
293 INVOKE_WCB(*window, Motion, (x, y));
297 return EVENT_HANDLED;
300 /* Let Android handle other events (e.g. Back and Menu buttons) */
301 return EVENT_NOT_HANDLED;
305 * Process the next main command.
307 void handle_cmd(struct android_app* app, int32_t cmd) {
308 SFG_Window* window = fgWindowByHandle(app->window); /* may be NULL */
310 /* App life cycle, in that order: */
312 LOGI("handle_cmd: APP_CMD_START");
315 LOGI("handle_cmd: APP_CMD_RESUME");
316 /* Cf. fgPlatformProcessSingleEvent */
318 case APP_CMD_INIT_WINDOW: /* surfaceCreated */
319 /* The window is being shown, get it ready. */
320 LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window);
321 fgDisplay.pDisplay.single_native_window = app->window;
322 /* start|resume: glPlatformOpenWindow was waiting for Handle to be
323 defined and will now continue processing */
325 case APP_CMD_GAINED_FOCUS:
326 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
328 case APP_CMD_WINDOW_RESIZED:
329 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
330 if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
331 /* Make ProcessSingleEvent detect the new size, only available
332 after the next SwapBuffer */
336 case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
337 /* The system has asked us to save our current state, when it
338 pauses the application without destroying it right after. */
339 app->savedState = strdup("Detect me as non-NULL on next android_main");
340 app->savedStateSize = strlen(app->savedState) + 1;
341 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
344 LOGI("handle_cmd: APP_CMD_PAUSE");
345 /* Cf. fgPlatformProcessSingleEvent */
347 case APP_CMD_LOST_FOCUS:
348 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
350 case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
351 /* The application is being hidden, but may be restored */
352 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
353 fghPlatformCloseWindowEGL(window);
354 window->State.NeedToInitContext = GL_TRUE;
355 fgDisplay.pDisplay.single_native_window = NULL;
358 LOGI("handle_cmd: APP_CMD_STOP");
360 case APP_CMD_DESTROY: /* Activity.onDestroy */
361 LOGI("handle_cmd: APP_CMD_DESTROY");
362 /* User closed the application for good, let's kill the window */
364 /* Can't use fgWindowByHandle as app->window is NULL */
365 SFG_Window* window = fgStructure.CurrentWindow;
366 if (window != NULL) {
367 fgDestroyWindow(window);
369 LOGI("APP_CMD_DESTROY: No current window");
372 /* glue has already set android_app->destroyRequested=1 */
375 case APP_CMD_CONFIG_CHANGED:
376 /* Handle rotation / orientation change */
377 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
379 case APP_CMD_LOW_MEMORY:
380 LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
383 LOGI("handle_cmd: unhandled cmd=%d", cmd);
387 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
388 GLboolean positionUse, int x, int y,
389 GLboolean sizeUse, int w, int h,
390 GLboolean gameMode, GLboolean isSubWindow );
392 void fgPlatformProcessSingleEvent ( void )
394 /* When the screen is resized, the window handle still points to the
395 old window until the next SwapBuffer, while it's crucial to set
396 the size (onShape) correctly before the next onDisplay callback.
397 Plus we don't know if the next SwapBuffer already occurred at the
398 time we process the event (e.g. during onDisplay). */
399 /* So we do the check each time rather than on event. */
400 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
401 2.3, that next SwapBuffer is fake (but still necessary to get the
403 SFG_Window* window = fgStructure.CurrentWindow;
404 if (window != NULL && window->Window.Handle != NULL) {
405 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
406 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
407 if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
408 window->State.pWState.LastWidth = width;
409 window->State.pWState.LastHeight = height;
410 LOGI("width=%d, height=%d", width, height);
411 if( FETCH_WCB( *window, Reshape ) )
412 INVOKE_WCB( *window, Reshape, ( width, height ) );
414 glViewport( 0, 0, width, height );
419 /* Read pending event. */
422 struct android_poll_source* source;
423 /* This is called "ProcessSingleEvent" but this means we'd only
424 process ~60 (screen Hz) mouse events per second, plus other ports
425 are processing all events already. So let's process all pending
427 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
428 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
429 /* Process this event. */
430 if (source != NULL) {
431 source->process(source->app, source);
435 /* If we're not in RESUME state, Android paused us, so wait */
436 struct android_app* app = fgDisplay.pDisplay.app;
437 if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
438 INVOKE_WCB(*window, Pause, ());
441 while (app->destroyRequested != 1 && (app->activityState != APP_CMD_RESUME)) {
442 if ((ident=ALooper_pollOnce(FOREVER, NULL, &events, (void**)&source)) >= 0) {
443 /* Process this event. */
444 if (source != NULL) {
445 source->process(source->app, source);
449 /* Coming back from a pause: */
450 /* - Recreate window context and surface */
451 /* - Call user-defined hook to restore resources (textures...) */
452 /* - Exit pause looop */
453 if (app->destroyRequested != 1) {
454 /* Android is full-screen only, simplified call.. */
455 /* Ideally we'd have a fgPlatformReopenWindow() */
456 /* If we're hidden by a non-fullscreen or translucent activity,
457 we'll be paused but not stopped, and keep the current
458 surface; in which case fgPlatformOpenWindow will no-op. */
459 fgPlatformOpenWindow(window, "", GL_FALSE, 0, 0, GL_FALSE, 0, 0, GL_FALSE, GL_FALSE);
461 if (!FETCH_WCB(*window, InitContext))
462 fgWarning("Resuming application, but no callback to reload context resources (glutInitContextFunc)");
465 INVOKE_WCB(*window, Resume, ());
469 void fgPlatformMainLoopPreliminaryWork ( void )
471 LOGI("fgPlatformMainLoopPreliminaryWork\n");
475 /* Make sure glue isn't stripped. */
476 /* JNI entry points need to be bundled even when linking statically */