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); */
141 * Request a window resize
143 void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
145 fprintf(stderr, "fgPlatformReshapeWindow: STUB\n");
149 * A static helper function to execute display callback for a window
151 void fgPlatformDisplayWindow ( SFG_Window *window )
153 fghRedrawWindow ( window ) ;
156 unsigned long fgPlatformSystemTime ( void )
159 gettimeofday( &now, NULL );
160 return now.tv_usec/1000 + now.tv_sec*1000;
164 * Does the magic required to relinquish the CPU until something interesting
167 void fgPlatformSleepForEvents( long msec )
169 /* Android's NativeActivity relies on a Looper/ALooper object to
170 notify about events. The Looper object is plugged on two
171 internal pipe(2)s to detect system and input events. Sadly you
172 can only ask the Looper for an event, not just ask whether
173 there is a pending event (and process it later). Consequently,
174 short of redesigning NativeActivity, we cannot
179 * Process the next input event.
181 int32_t handle_input(struct android_app* app, AInputEvent* event) {
182 SFG_Window* window = fgStructure.CurrentWindow;
184 /* FIXME: in Android, when key is repeated, down and up events
185 happen most often at the exact same time. This makes it
186 impossible to animate based on key press time. */
187 /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
189 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
190 /* LOGI("action: %d", AKeyEvent_getAction(event)); */
191 /* LOGI("keycode: %d", code); */
192 int32_t code = AKeyEvent_getKeyCode(event);
194 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
195 int32_t keypress = 0;
196 unsigned char ascii = 0;
197 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
198 INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
199 return EVENT_HANDLED;
200 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
201 INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
202 return EVENT_HANDLED;
205 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
206 int32_t keypress = 0;
207 unsigned char ascii = 0;
208 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
209 INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
210 return EVENT_HANDLED;
211 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
212 INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
213 return EVENT_HANDLED;
218 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
219 int32_t action = AMotionEvent_getAction(event);
220 float x = AMotionEvent_getX(event, 0);
221 float y = AMotionEvent_getY(event, 0);
222 LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
224 /* Virtual arrows PAD */
225 /* Don't interfere with existing mouse move event */
226 if (!touchscreen.in_mmotion) {
227 struct vpad_state prev_vpad = touchscreen.vpad;
228 touchscreen.vpad.left = touchscreen.vpad.right
229 = touchscreen.vpad.up = touchscreen.vpad.down = false;
231 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
232 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
233 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
234 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
235 touchscreen.vpad.left = true;
236 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
237 touchscreen.vpad.right = true;
238 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
239 touchscreen.vpad.down = true;
240 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
241 touchscreen.vpad.up = true;
243 if (action == AMOTION_EVENT_ACTION_DOWN &&
244 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
245 touchscreen.vpad.on = true;
246 if (action == AMOTION_EVENT_ACTION_UP)
247 touchscreen.vpad.on = false;
248 if (prev_vpad.left != touchscreen.vpad.left
249 || prev_vpad.right != touchscreen.vpad.right
250 || prev_vpad.up != touchscreen.vpad.up
251 || prev_vpad.down != touchscreen.vpad.down
252 || prev_vpad.on != touchscreen.vpad.on) {
253 if (FETCH_WCB(*window, Special)) {
254 if (prev_vpad.left == false && touchscreen.vpad.left == true)
255 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
256 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
257 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
258 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
259 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
260 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
261 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
263 if (FETCH_WCB(*window, SpecialUp)) {
264 if (prev_vpad.left == true && touchscreen.vpad.left == false)
265 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
266 if (prev_vpad.right == true && touchscreen.vpad.right == false)
267 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
268 if (prev_vpad.up == true && touchscreen.vpad.up == false)
269 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
270 if (prev_vpad.down == true && touchscreen.vpad.down == false)
271 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
273 return EVENT_HANDLED;
277 /* Normal mouse events */
278 if (!touchscreen.vpad.on) {
279 window->State.MouseX = x;
280 window->State.MouseY = y;
281 LOGI("Changed mouse position: %f,%f", x, y);
282 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
283 touchscreen.in_mmotion = true;
284 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
285 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
286 touchscreen.in_mmotion = false;
287 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
288 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
289 INVOKE_WCB(*window, Motion, (x, y));
293 return EVENT_HANDLED;
296 /* Let Android handle other events (e.g. Back and Menu buttons) */
297 return EVENT_NOT_HANDLED;
301 * Process the next main command.
303 void handle_cmd(struct android_app* app, int32_t cmd) {
305 case APP_CMD_SAVE_STATE:
306 /* The system has asked us to save our current state. Do so. */
307 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
309 case APP_CMD_INIT_WINDOW:
310 /* The window is being shown, get it ready. */
311 LOGI("handle_cmd: APP_CMD_INIT_WINDOW");
312 fgDisplay.pDisplay.single_window->Window.Handle = app->window;
313 /* glPlatformOpenWindow was waiting for Handle to be defined and
314 will now return from fgPlatformProcessSingleEvent() */
316 case APP_CMD_TERM_WINDOW:
317 /* The window is being hidden or closed, clean it up. */
318 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
319 fgDestroyWindow(fgDisplay.pDisplay.single_window);
321 case APP_CMD_DESTROY:
322 /* Not reached because GLUT exit()s when last window is closed */
323 LOGI("handle_cmd: APP_CMD_DESTROY");
325 case APP_CMD_GAINED_FOCUS:
326 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
328 case APP_CMD_LOST_FOCUS:
329 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
331 case APP_CMD_CONFIG_CHANGED:
332 /* Handle rotation / orientation change */
333 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
335 case APP_CMD_WINDOW_RESIZED:
336 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
337 if (fgDisplay.pDisplay.single_window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
338 /* Make ProcessSingleEvent detect the new size, only available
339 after the next SwapBuffer */
343 LOGI("handle_cmd: unhandled cmd=%d", cmd);
347 void fgPlatformProcessSingleEvent ( void )
349 static int32_t last_width = -1;
350 static int32_t last_height = -1;
352 /* When the screen is resized, the window handle still points to the
353 old window until the next SwapBuffer, while it's crucial to set
354 the size (onShape) correctly before the next onDisplay callback.
355 Plus we don't know if the next SwapBuffer already occurred at the
356 time we process the event (e.g. during onDisplay). */
357 /* So we do the check each time rather than on event. */
358 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
359 2.3, that next SwapBuffer is fake (but still necessary to get the
361 SFG_Window* window = fgDisplay.pDisplay.single_window;
362 if (window != NULL && window->Window.Handle != NULL) {
363 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
364 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
365 if (width != last_width || height != last_height) {
367 last_height = height;
368 LOGI("width=%d, height=%d", width, height);
369 if( FETCH_WCB( *window, Reshape ) )
370 INVOKE_WCB( *window, Reshape, ( width, height ) );
372 glViewport( 0, 0, width, height );
377 /* Read pending event. */
380 struct android_poll_source* source;
381 /* This is called "ProcessSingleEvent" but this means we'd only
382 process ~60 (screen Hz) mouse events per second, plus other ports
383 are processing all events already. So let's process all pending
385 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
386 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
387 /* Process this event. */
388 if (source != NULL) {
389 source->process(source->app, source);
394 void fgPlatformMainLoopPreliminaryWork ( void )
396 printf("fgPlatformMainLoopPreliminaryWork\n");
400 /* Make sure glue isn't stripped. */
401 /* JNI entry points need to be bundled even when linking statically */