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"
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);
142 * Request a window resize
144 void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
146 fprintf(stderr, "fgPlatformReshapeWindow: STUB\n");
150 * A static helper function to execute display callback for a window
152 void fgPlatformDisplayWindow ( SFG_Window *window )
154 fghRedrawWindow ( window ) ;
157 unsigned long fgPlatformSystemTime ( void )
160 gettimeofday( &now, NULL );
161 return now.tv_usec/1000 + now.tv_sec*1000;
165 * Does the magic required to relinquish the CPU until something interesting
168 void fgPlatformSleepForEvents( long msec )
170 /* Android's NativeActivity relies on a Looper/ALooper object to
171 notify about events. The Looper object is plugged on two
172 internal pipe(2)s to detect system and input events. Sadly you
173 can only ask the Looper for an event, not just ask whether
174 there is a pending event (and process it later). Consequently,
175 short of redesigning NativeActivity, we cannot
180 * Process the next input event.
182 int32_t handle_input(struct android_app* app, AInputEvent* event) {
183 SFG_Window* window = fgStructure.CurrentWindow;
185 /* FIXME: in Android, when key is repeated, down and up events
186 happen most often at the exact same time. This makes it
187 impossible to animate based on key press time. */
188 /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
190 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
191 /* LOGI("action: %d", AKeyEvent_getAction(event)); */
192 /* LOGI("keycode: %d", code); */
193 int32_t code = AKeyEvent_getKeyCode(event);
195 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
196 int32_t keypress = 0;
197 unsigned char ascii = 0;
198 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
199 INVOKE_WCB(*window, Special, (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, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
203 return EVENT_HANDLED;
206 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
207 int32_t keypress = 0;
208 unsigned char ascii = 0;
209 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
210 INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
211 return EVENT_HANDLED;
212 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
213 INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
214 return EVENT_HANDLED;
219 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
220 int32_t action = AMotionEvent_getAction(event);
221 float x = AMotionEvent_getX(event, 0);
222 float y = AMotionEvent_getY(event, 0);
223 LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
225 /* Virtual arrows PAD */
226 /* Don't interfere with existing mouse move event */
227 if (!touchscreen.in_mmotion) {
228 struct vpad_state prev_vpad = touchscreen.vpad;
229 touchscreen.vpad.left = touchscreen.vpad.right
230 = touchscreen.vpad.up = touchscreen.vpad.down = false;
232 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
233 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
234 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
235 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
236 touchscreen.vpad.left = true;
237 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
238 touchscreen.vpad.right = true;
239 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
240 touchscreen.vpad.down = true;
241 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
242 touchscreen.vpad.up = true;
244 if (action == AMOTION_EVENT_ACTION_DOWN &&
245 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
246 touchscreen.vpad.on = true;
247 if (action == AMOTION_EVENT_ACTION_UP)
248 touchscreen.vpad.on = false;
249 if (prev_vpad.left != touchscreen.vpad.left
250 || prev_vpad.right != touchscreen.vpad.right
251 || prev_vpad.up != touchscreen.vpad.up
252 || prev_vpad.down != touchscreen.vpad.down
253 || prev_vpad.on != touchscreen.vpad.on) {
254 if (FETCH_WCB(*window, Special)) {
255 if (prev_vpad.left == false && touchscreen.vpad.left == true)
256 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
257 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
258 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
259 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
260 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
261 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
262 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
264 if (FETCH_WCB(*window, SpecialUp)) {
265 if (prev_vpad.left == true && touchscreen.vpad.left == false)
266 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
267 if (prev_vpad.right == true && touchscreen.vpad.right == false)
268 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
269 if (prev_vpad.up == true && touchscreen.vpad.up == false)
270 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
271 if (prev_vpad.down == true && touchscreen.vpad.down == false)
272 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
274 return EVENT_HANDLED;
278 /* Normal mouse events */
279 if (!touchscreen.vpad.on) {
280 window->State.MouseX = x;
281 window->State.MouseY = y;
282 LOGI("Changed mouse position: %f,%f", x, y);
283 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
284 touchscreen.in_mmotion = true;
285 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
286 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
287 touchscreen.in_mmotion = false;
288 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
289 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
290 INVOKE_WCB(*window, Motion, (x, y));
294 return EVENT_HANDLED;
297 /* Let Android handle other events (e.g. Back and Menu buttons) */
298 return EVENT_NOT_HANDLED;
302 * Process the next main command.
304 void handle_cmd(struct android_app* app, int32_t cmd) {
306 /* App life cycle, in that order: */
308 LOGI("handle_cmd: APP_CMD_START");
311 LOGI("handle_cmd: APP_CMD_RESUME");
312 /* If coming back from a pause: */
313 /* - Recreate window context and surface */
314 /* - Call user-defined hook to restore resources (textures...) */
315 /* - Unpause GLUT callbacks */
317 case APP_CMD_INIT_WINDOW: /* surfaceCreated */
318 /* The window is being shown, get it ready. */
319 LOGI("handle_cmd: APP_CMD_INIT_WINDOW");
320 fgDisplay.pDisplay.single_window->Window.Handle = app->window;
321 /* glPlatformOpenWindow was waiting for Handle to be defined and
322 will now return from fgPlatformProcessSingleEvent() */
324 case APP_CMD_GAINED_FOCUS:
325 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
327 case APP_CMD_WINDOW_RESIZED:
328 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
329 if (fgDisplay.pDisplay.single_window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
330 /* Make ProcessSingleEvent detect the new size, only available
331 after the next SwapBuffer */
335 case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
336 /* The system has asked us to save our current state, when it
337 pauses the application without destroying it right after. */
338 /* app->savedState = ... */
339 /* app->savedStateSize = ... */
340 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
343 LOGI("handle_cmd: APP_CMD_PAUSE");
344 /* - Pause GLUT callbacks */
346 case APP_CMD_LOST_FOCUS:
347 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
349 case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */
350 /* The application is being hidden, but may be restored */
351 /* TODO: Pausing/resuming windows not ready yet, so killing it now */
352 fgDestroyWindow(fgDisplay.pDisplay.single_window);
353 fgDisplay.pDisplay.single_window = NULL;
354 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
357 LOGI("handle_cmd: APP_CMD_STOP");
359 case APP_CMD_DESTROY: /* Activity.onDestroy */
360 LOGI("handle_cmd: APP_CMD_DESTROY");
361 /* User closed the application for good, let's kill the window */
362 if (fgDisplay.pDisplay.single_window != NULL) {
363 fgDestroyWindow(fgDisplay.pDisplay.single_window);
364 fgDisplay.pDisplay.single_window = NULL;
366 /* glue has already set android_app->destroyRequested=1 */
369 case APP_CMD_CONFIG_CHANGED:
370 /* Handle rotation / orientation change */
371 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
373 case APP_CMD_LOW_MEMORY:
374 LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
377 LOGI("handle_cmd: unhandled cmd=%d", cmd);
381 void fgPlatformProcessSingleEvent ( void )
383 /* When the screen is resized, the window handle still points to the
384 old window until the next SwapBuffer, while it's crucial to set
385 the size (onShape) correctly before the next onDisplay callback.
386 Plus we don't know if the next SwapBuffer already occurred at the
387 time we process the event (e.g. during onDisplay). */
388 /* So we do the check each time rather than on event. */
389 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
390 2.3, that next SwapBuffer is fake (but still necessary to get the
392 SFG_Window* window = fgDisplay.pDisplay.single_window;
393 if (window != NULL && window->Window.Handle != NULL) {
394 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
395 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
396 if (width != window->State.pWState.LastWidth || height != window->State.pWState.LastHeight) {
397 window->State.pWState.LastWidth = width;
398 window->State.pWState.LastHeight = height;
399 LOGI("width=%d, height=%d", width, height);
400 if( FETCH_WCB( *window, Reshape ) )
401 INVOKE_WCB( *window, Reshape, ( width, height ) );
403 glViewport( 0, 0, width, height );
408 /* Read pending event. */
411 struct android_poll_source* source;
412 /* This is called "ProcessSingleEvent" but this means we'd only
413 process ~60 (screen Hz) mouse events per second, plus other ports
414 are processing all events already. So let's process all pending
416 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
417 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
418 /* Process this event. */
419 if (source != NULL) {
420 source->process(source->app, source);
425 void fgPlatformMainLoopPreliminaryWork ( void )
427 LOGI("fgPlatformMainLoopPreliminaryWork\n");
431 /* Make sure glue isn't stripped. */
432 /* JNI entry points need to be bundled even when linking statically */