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 virtual keyboard system :/ Real
193 buttons such as the Back button appear to work correctly (series
194 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 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
226 int32_t action = AMotionEvent_getAction(event);
227 float x = AMotionEvent_getX(event, 0);
228 float y = AMotionEvent_getY(event, 0);
229 LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
231 /* Virtual arrows PAD */
232 /* Don't interfere with existing mouse move event */
233 if (!touchscreen.in_mmotion) {
234 struct vpad_state prev_vpad = touchscreen.vpad;
235 touchscreen.vpad.left = touchscreen.vpad.right
236 = touchscreen.vpad.up = touchscreen.vpad.down = false;
238 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
239 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
240 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
241 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
242 touchscreen.vpad.left = true;
243 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
244 touchscreen.vpad.right = true;
245 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
246 touchscreen.vpad.down = true;
247 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
248 touchscreen.vpad.up = true;
250 if (action == AMOTION_EVENT_ACTION_DOWN &&
251 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
252 touchscreen.vpad.on = true;
253 if (action == AMOTION_EVENT_ACTION_UP)
254 touchscreen.vpad.on = false;
255 if (prev_vpad.left != touchscreen.vpad.left
256 || prev_vpad.right != touchscreen.vpad.right
257 || prev_vpad.up != touchscreen.vpad.up
258 || prev_vpad.down != touchscreen.vpad.down
259 || prev_vpad.on != touchscreen.vpad.on) {
260 if (FETCH_WCB(*window, Special)) {
261 if (prev_vpad.left == false && touchscreen.vpad.left == true)
262 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
263 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
264 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
265 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
266 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
267 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
268 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
270 if (FETCH_WCB(*window, SpecialUp)) {
271 if (prev_vpad.left == true && touchscreen.vpad.left == false)
272 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
273 if (prev_vpad.right == true && touchscreen.vpad.right == false)
274 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
275 if (prev_vpad.up == true && touchscreen.vpad.up == false)
276 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
277 if (prev_vpad.down == true && touchscreen.vpad.down == false)
278 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
280 return EVENT_HANDLED;
284 /* Normal mouse events */
285 if (!touchscreen.vpad.on) {
286 window->State.MouseX = x;
287 window->State.MouseY = y;
288 LOGI("Changed mouse position: %f,%f", x, y);
289 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
290 touchscreen.in_mmotion = true;
291 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
292 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
293 touchscreen.in_mmotion = false;
294 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
295 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
296 INVOKE_WCB(*window, Motion, (x, y));
300 return EVENT_HANDLED;
303 /* Let Android handle other events (e.g. Back and Menu buttons) */
304 return EVENT_NOT_HANDLED;
308 * Process the next main command.
310 void handle_cmd(struct android_app* app, int32_t cmd) {
311 SFG_Window* window = fgWindowByHandle(app->window); /* may be NULL */
313 /* App life cycle, in that order: */
315 LOGI("handle_cmd: APP_CMD_START");
318 LOGI("handle_cmd: APP_CMD_RESUME");
319 /* Cf. fgPlatformProcessSingleEvent */
321 case APP_CMD_INIT_WINDOW: /* surfaceCreated */
322 /* The window is being shown, get it ready. */
323 LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window);
324 fgDisplay.pDisplay.single_native_window = app->window;
325 /* start|resume: glPlatformOpenWindow was waiting for Handle to be
326 defined and will now continue processing */
328 case APP_CMD_GAINED_FOCUS:
329 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
331 case APP_CMD_WINDOW_RESIZED:
332 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
333 if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
334 /* Make ProcessSingleEvent detect the new size, only available
335 after the next SwapBuffer */
339 case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
340 /* The system has asked us to save our current state, when it
341 pauses the application without destroying it right after. */
342 app->savedState = strdup("Detect me as non-NULL on next android_main");
343 app->savedStateSize = strlen(app->savedState) + 1;
344 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
347 LOGI("handle_cmd: APP_CMD_PAUSE");
348 /* Cf. fgPlatformProcessSingleEvent */
350 case APP_CMD_LOST_FOCUS:
351 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
353 case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
354 /* The application is being hidden, but may be restored */
355 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
356 fghPlatformCloseWindowEGL(window);
357 fgDisplay.pDisplay.single_native_window = NULL;
360 LOGI("handle_cmd: APP_CMD_STOP");
362 case APP_CMD_DESTROY: /* Activity.onDestroy */
363 LOGI("handle_cmd: APP_CMD_DESTROY");
364 /* User closed the application for good, let's kill the window */
366 /* Can't use fgWindowByHandle as app->window is NULL */
367 SFG_Window* window = fgStructure.CurrentWindow;
368 if (window != NULL) {
369 fgDestroyWindow(window);
371 LOGI("APP_CMD_DESTROY: No current window");
374 /* glue has already set android_app->destroyRequested=1 */
377 case APP_CMD_CONFIG_CHANGED:
378 /* Handle rotation / orientation change */
379 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
381 case APP_CMD_LOW_MEMORY:
382 LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
385 LOGI("handle_cmd: unhandled cmd=%d", cmd);
389 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
390 GLboolean positionUse, int x, int y,
391 GLboolean sizeUse, int w, int h,
392 GLboolean gameMode, GLboolean isSubWindow );
394 void fgPlatformProcessSingleEvent ( void )
396 /* When the screen is resized, the window handle still points to the
397 old window until the next SwapBuffer, while it's crucial to set
398 the size (onShape) correctly before the next onDisplay callback.
399 Plus we don't know if the next SwapBuffer already occurred at the
400 time we process the event (e.g. during onDisplay). */
401 /* So we do the check each time rather than on event. */
402 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
403 2.3, that next SwapBuffer is fake (but still necessary to get the
405 SFG_Window* window = fgStructure.CurrentWindow;
406 if (window != NULL && window->Window.Handle != NULL) {
407 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
408 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
409 if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
410 window->State.pWState.LastWidth = width;
411 window->State.pWState.LastHeight = height;
412 LOGI("width=%d, height=%d", width, height);
413 if( FETCH_WCB( *window, Reshape ) )
414 INVOKE_WCB( *window, Reshape, ( width, height ) );
416 glViewport( 0, 0, width, height );
421 /* Read pending event. */
424 struct android_poll_source* source;
425 /* This is called "ProcessSingleEvent" but this means we'd only
426 process ~60 (screen Hz) mouse events per second, plus other ports
427 are processing all events already. So let's process all pending
429 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
430 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
431 /* Process this event. */
432 if (source != NULL) {
433 source->process(source->app, source);
437 /* If we're not in RESUME state, Android paused us, so wait */
438 struct android_app* app = fgDisplay.pDisplay.app;
439 if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
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 /* If 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);
460 /* TODO: INVOKE_WCB(*window, Pause?); */
461 /* TODO: INVOKE_WCB(*window, LoadResources/ContextLost/...?); */
462 /* TODO: INVOKE_WCB(*window, Resume?); */
467 void fgPlatformMainLoopPreliminaryWork ( void )
469 LOGI("fgPlatformMainLoopPreliminaryWork\n");
473 /* Make sure glue isn't stripped. */
474 /* JNI entry points need to be bundled even when linking statically */