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 */
233 LOGI("motion action=%d index=%d source=%d", action, pidx, source);
234 int count = AMotionEvent_getPointerCount(event);
236 for (i = 0; i < count; i++) {
237 LOGI("multi(%d): %.01f,%.01f",
238 AMotionEvent_getPointerId(event, i),
239 AMotionEvent_getX(event, i), AMotionEvent_getY(event, i));
242 float x = AMotionEvent_getX(event, 0);
243 float y = AMotionEvent_getY(event, 0);
245 /* Virtual arrows PAD */
246 /* Don't interfere with existing mouse move event */
247 if (!touchscreen.in_mmotion) {
248 struct vpad_state prev_vpad = touchscreen.vpad;
249 touchscreen.vpad.left = touchscreen.vpad.right
250 = touchscreen.vpad.up = touchscreen.vpad.down = false;
252 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
253 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
254 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
255 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
256 touchscreen.vpad.left = true;
257 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
258 touchscreen.vpad.right = true;
259 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
260 touchscreen.vpad.down = true;
261 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
262 touchscreen.vpad.up = true;
264 if (action == AMOTION_EVENT_ACTION_DOWN &&
265 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
266 touchscreen.vpad.on = true;
267 if (action == AMOTION_EVENT_ACTION_UP)
268 touchscreen.vpad.on = false;
269 if (prev_vpad.left != touchscreen.vpad.left
270 || prev_vpad.right != touchscreen.vpad.right
271 || prev_vpad.up != touchscreen.vpad.up
272 || prev_vpad.down != touchscreen.vpad.down
273 || prev_vpad.on != touchscreen.vpad.on) {
274 if (FETCH_WCB(*window, Special)) {
275 if (prev_vpad.left == false && touchscreen.vpad.left == true)
276 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
277 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
278 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
279 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
280 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
281 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
282 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
284 if (FETCH_WCB(*window, SpecialUp)) {
285 if (prev_vpad.left == true && touchscreen.vpad.left == false)
286 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
287 if (prev_vpad.right == true && touchscreen.vpad.right == false)
288 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
289 if (prev_vpad.up == true && touchscreen.vpad.up == false)
290 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
291 if (prev_vpad.down == true && touchscreen.vpad.down == false)
292 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
294 return EVENT_HANDLED;
298 /* Normal mouse events */
299 if (!touchscreen.vpad.on) {
300 window->State.MouseX = x;
301 window->State.MouseY = y;
302 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
303 touchscreen.in_mmotion = true;
304 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
305 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
306 touchscreen.in_mmotion = false;
307 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
308 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
309 INVOKE_WCB(*window, Motion, (x, y));
313 return EVENT_HANDLED;
316 /* Let Android handle other events (e.g. Back and Menu buttons) */
317 return EVENT_NOT_HANDLED;
321 * Process the next main command.
323 void handle_cmd(struct android_app* app, int32_t cmd) {
324 SFG_Window* window = fgWindowByHandle(app->window); /* may be NULL */
326 /* App life cycle, in that order: */
328 LOGI("handle_cmd: APP_CMD_START");
331 LOGI("handle_cmd: APP_CMD_RESUME");
332 /* Cf. fgPlatformProcessSingleEvent */
334 case APP_CMD_INIT_WINDOW: /* surfaceCreated */
335 /* The window is being shown, get it ready. */
336 LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window);
337 fgDisplay.pDisplay.single_native_window = app->window;
338 /* start|resume: glPlatformOpenWindow was waiting for Handle to be
339 defined and will now continue processing */
341 case APP_CMD_GAINED_FOCUS:
342 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
344 case APP_CMD_WINDOW_RESIZED:
345 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
346 if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
347 /* Make ProcessSingleEvent detect the new size, only available
348 after the next SwapBuffer */
352 case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
353 /* The system has asked us to save our current state, when it
354 pauses the application without destroying it right after. */
355 app->savedState = strdup("Detect me as non-NULL on next android_main");
356 app->savedStateSize = strlen(app->savedState) + 1;
357 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
360 LOGI("handle_cmd: APP_CMD_PAUSE");
361 /* Cf. fgPlatformProcessSingleEvent */
363 case APP_CMD_LOST_FOCUS:
364 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
366 case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
367 /* The application is being hidden, but may be restored */
368 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
369 fghPlatformCloseWindowEGL(window);
370 window->State.NeedToFixMyNameInitContext = GL_TRUE;
371 fgDisplay.pDisplay.single_native_window = NULL;
374 LOGI("handle_cmd: APP_CMD_STOP");
376 case APP_CMD_DESTROY: /* Activity.onDestroy */
377 LOGI("handle_cmd: APP_CMD_DESTROY");
378 /* User closed the application for good, let's kill the window */
380 /* Can't use fgWindowByHandle as app->window is NULL */
381 SFG_Window* window = fgStructure.CurrentWindow;
382 if (window != NULL) {
383 fgDestroyWindow(window);
385 LOGI("APP_CMD_DESTROY: No current window");
388 /* glue has already set android_app->destroyRequested=1 */
391 case APP_CMD_CONFIG_CHANGED:
392 /* Handle rotation / orientation change */
393 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
395 case APP_CMD_LOW_MEMORY:
396 LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
399 LOGI("handle_cmd: unhandled cmd=%d", cmd);
403 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
404 GLboolean positionUse, int x, int y,
405 GLboolean sizeUse, int w, int h,
406 GLboolean gameMode, GLboolean isSubWindow );
408 void fgPlatformProcessSingleEvent ( void )
410 /* When the screen is resized, the window handle still points to the
411 old window until the next SwapBuffer, while it's crucial to set
412 the size (onShape) correctly before the next onDisplay callback.
413 Plus we don't know if the next SwapBuffer already occurred at the
414 time we process the event (e.g. during onDisplay). */
415 /* So we do the check each time rather than on event. */
416 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
417 2.3, that next SwapBuffer is fake (but still necessary to get the
419 SFG_Window* window = fgStructure.CurrentWindow;
420 if (window != NULL && window->Window.Handle != NULL) {
421 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
422 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
423 if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
424 window->State.pWState.LastWidth = width;
425 window->State.pWState.LastHeight = height;
426 LOGI("width=%d, height=%d", width, height);
427 if( FETCH_WCB( *window, Reshape ) )
428 INVOKE_WCB( *window, Reshape, ( width, height ) );
430 glViewport( 0, 0, width, height );
435 /* Read pending event. */
438 struct android_poll_source* source;
439 /* This is called "ProcessSingleEvent" but this means we'd only
440 process ~60 (screen Hz) mouse events per second, plus other ports
441 are processing all events already. So let's process all pending
443 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
444 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
445 /* Process this event. */
446 if (source != NULL) {
447 source->process(source->app, source);
451 /* If we're not in RESUME state, Android paused us, so wait */
452 struct android_app* app = fgDisplay.pDisplay.app;
453 if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
455 while (app->destroyRequested != 1 && (app->activityState != APP_CMD_RESUME)) {
456 if ((ident=ALooper_pollOnce(FOREVER, NULL, &events, (void**)&source)) >= 0) {
457 /* Process this event. */
458 if (source != NULL) {
459 source->process(source->app, source);
463 /* Coming back from a pause: */
464 /* - Recreate window context and surface */
465 /* - Call user-defined hook to restore resources (textures...) */
466 /* - Exit pause looop */
467 if (app->destroyRequested != 1) {
468 /* Android is full-screen only, simplified call.. */
469 /* Ideally we'd have a fgPlatformReopenWindow() */
470 /* If we're hidden by a non-fullscreen or translucent activity,
471 we'll be paused but not stopped, and keep the current
472 surface; in which case fgPlatformOpenWindow will no-op. */
473 fgPlatformOpenWindow(window, "", GL_FALSE, 0, 0, GL_FALSE, 0, 0, GL_FALSE, GL_FALSE);
474 /* TODO: INVOKE_WCB(*window, Pause?); */
475 /* TODO: INVOKE_WCB(*window, Resume?); */
476 if (!FETCH_WCB(*window, FixMyNameInitContext)
477 fgWarning("Resuming application, but no callback to reload context resources (glutFixMyNameInitContextFunc)");
482 void fgPlatformMainLoopPreliminaryWork ( void )
484 LOGI("fgPlatformMainLoopPreliminaryWork\n");
488 /* Make sure glue isn't stripped. */
489 /* JNI entry points need to be bundled even when linking statically */