2 * freeglut_main_android.c
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 <android/log.h>
33 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "FreeGLUT", __VA_ARGS__))
34 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "FreeGLUT", __VA_ARGS__))
35 #include <android/native_app_glue/android_native_app_glue.h>
36 #include <android/keycodes.h>
38 static struct touchscreen touchscreen;
39 static unsigned char key_a2fg[256];
41 /* Cf. http://developer.android.com/reference/android/view/KeyEvent.html */
42 /* These codes are missing in <android/keycodes.h> */
43 /* Don't convert to enum, since it may conflict with future version of
44 that <android/keycodes.h> */
45 #define AKEYCODE_FORWARD_DEL 112
46 #define AKEYCODE_CTRL_LEFT 113
47 #define AKEYCODE_CTRL_RIGHT 114
48 #define AKEYCODE_MOVE_HOME 122
49 #define AKEYCODE_MOVE_END 123
50 #define AKEYCODE_INSERT 124
51 #define AKEYCODE_ESCAPE 127
52 #define AKEYCODE_F1 131
53 #define AKEYCODE_F2 132
54 #define AKEYCODE_F3 133
55 #define AKEYCODE_F4 134
56 #define AKEYCODE_F5 135
57 #define AKEYCODE_F6 136
58 #define AKEYCODE_F7 137
59 #define AKEYCODE_F8 138
60 #define AKEYCODE_F9 139
61 #define AKEYCODE_F10 140
62 #define AKEYCODE_F11 141
63 #define AKEYCODE_F12 142
65 #define EVENT_HANDLED 1
66 #define EVENT_NOT_HANDLED 0
69 * Initialize Android keycode to GLUT keycode mapping
71 static void key_init() {
72 memset(key_a2fg, 0, sizeof(key_a2fg));
74 key_a2fg[AKEYCODE_F1] = GLUT_KEY_F1;
75 key_a2fg[AKEYCODE_F2] = GLUT_KEY_F2;
76 key_a2fg[AKEYCODE_F3] = GLUT_KEY_F3;
77 key_a2fg[AKEYCODE_F4] = GLUT_KEY_F4;
78 key_a2fg[AKEYCODE_F5] = GLUT_KEY_F5;
79 key_a2fg[AKEYCODE_F6] = GLUT_KEY_F6;
80 key_a2fg[AKEYCODE_F7] = GLUT_KEY_F7;
81 key_a2fg[AKEYCODE_F8] = GLUT_KEY_F8;
82 key_a2fg[AKEYCODE_F9] = GLUT_KEY_F9;
83 key_a2fg[AKEYCODE_F10] = GLUT_KEY_F10;
84 key_a2fg[AKEYCODE_F11] = GLUT_KEY_F11;
85 key_a2fg[AKEYCODE_F12] = GLUT_KEY_F12;
87 key_a2fg[AKEYCODE_PAGE_UP] = GLUT_KEY_PAGE_UP;
88 key_a2fg[AKEYCODE_PAGE_DOWN] = GLUT_KEY_PAGE_DOWN;
89 key_a2fg[AKEYCODE_MOVE_HOME] = GLUT_KEY_HOME;
90 key_a2fg[AKEYCODE_MOVE_END] = GLUT_KEY_END;
91 key_a2fg[AKEYCODE_INSERT] = GLUT_KEY_INSERT;
93 key_a2fg[AKEYCODE_DPAD_UP] = GLUT_KEY_UP;
94 key_a2fg[AKEYCODE_DPAD_DOWN] = GLUT_KEY_DOWN;
95 key_a2fg[AKEYCODE_DPAD_LEFT] = GLUT_KEY_LEFT;
96 key_a2fg[AKEYCODE_DPAD_RIGHT] = GLUT_KEY_RIGHT;
98 key_a2fg[AKEYCODE_ALT_LEFT] = GLUT_KEY_ALT_L;
99 key_a2fg[AKEYCODE_ALT_RIGHT] = GLUT_KEY_ALT_R;
100 key_a2fg[AKEYCODE_SHIFT_LEFT] = GLUT_KEY_SHIFT_L;
101 key_a2fg[AKEYCODE_SHIFT_RIGHT] = GLUT_KEY_SHIFT_R;
102 key_a2fg[AKEYCODE_CTRL_LEFT] = GLUT_KEY_CTRL_L;
103 key_a2fg[AKEYCODE_CTRL_RIGHT] = GLUT_KEY_CTRL_R;
107 * Convert an Android key event to ASCII.
109 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
110 int32_t code = AKeyEvent_getKeyCode(event);
112 /* Handle a few special cases: */
116 case AKEYCODE_FORWARD_DEL:
118 case AKEYCODE_ESCAPE:
122 /* Get usable JNI context */
123 JNIEnv* env = app->activity->env;
124 JavaVM* vm = app->activity->vm;
125 (*vm)->AttachCurrentThread(vm, &env, NULL);
127 jclass KeyEventClass = (*env)->FindClass(env, "android/view/KeyEvent");
128 jmethodID KeyEventConstructor = (*env)->GetMethodID(env, KeyEventClass, "<init>", "(II)V");
129 jobject keyEvent = (*env)->NewObject(env, KeyEventClass, KeyEventConstructor,
130 AKeyEvent_getAction(event), AKeyEvent_getKeyCode(event));
131 jmethodID KeyEvent_getUnicodeChar = (*env)->GetMethodID(env, KeyEventClass, "getUnicodeChar", "(I)I");
132 int ascii = (*env)->CallIntMethod(env, keyEvent, KeyEvent_getUnicodeChar, AKeyEvent_getMetaState(event));
134 /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */
140 * Handle a window configuration change. When no reshape
141 * callback is hooked, the viewport size is updated to
142 * match the new window size.
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 /* fprintf(stderr, "fgPlatformSleepForEvents: STUB\n"); */
174 * Process the next input event.
176 int32_t handle_input(struct android_app* app, AInputEvent* event) {
177 SFG_Window* window = fgStructure.CurrentWindow;
179 /* FIXME: in Android, when key is repeated, down and up events
180 happen most often at the exact same time. This makes it
181 impossible to animate based on key press time. */
182 /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
184 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
185 /* LOGI("action: %d", AKeyEvent_getAction(event)); */
186 /* LOGI("keycode: %d", code); */
187 int32_t code = AKeyEvent_getKeyCode(event);
189 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
190 int32_t keypress = 0;
191 unsigned char ascii = 0;
192 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
193 INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
194 return EVENT_HANDLED;
195 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
196 INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
197 return EVENT_HANDLED;
200 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
201 int32_t keypress = 0;
202 unsigned char ascii = 0;
203 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
204 INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
205 return EVENT_HANDLED;
206 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
207 INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
208 return EVENT_HANDLED;
213 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
214 int32_t action = AMotionEvent_getAction(event);
215 float x = AMotionEvent_getX(event, 0);
216 float y = AMotionEvent_getY(event, 0);
217 LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
219 /* Virtual arrows PAD */
220 // Don't interfere with existing mouse move event
221 if (!touchscreen.in_mmotion) {
222 struct vpad_state prev_vpad = touchscreen.vpad;
223 touchscreen.vpad.left = touchscreen.vpad.right
224 = touchscreen.vpad.up = touchscreen.vpad.down = false;
226 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
227 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
228 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
229 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
230 touchscreen.vpad.left = true;
231 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
232 touchscreen.vpad.right = true;
233 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
234 touchscreen.vpad.down = true;
235 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
236 touchscreen.vpad.up = true;
238 if (action == AMOTION_EVENT_ACTION_DOWN &&
239 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
240 touchscreen.vpad.on = true;
241 if (action == AMOTION_EVENT_ACTION_UP)
242 touchscreen.vpad.on = false;
243 if (prev_vpad.left != touchscreen.vpad.left
244 || prev_vpad.right != touchscreen.vpad.right
245 || prev_vpad.up != touchscreen.vpad.up
246 || prev_vpad.down != touchscreen.vpad.down
247 || prev_vpad.on != touchscreen.vpad.on) {
248 if (FETCH_WCB(*window, Special)) {
249 if (prev_vpad.left == false && touchscreen.vpad.left == true)
250 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
251 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
252 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
253 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
254 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
255 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
256 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
258 if (FETCH_WCB(*window, SpecialUp)) {
259 if (prev_vpad.left == true && touchscreen.vpad.left == false)
260 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
261 if (prev_vpad.right == true && touchscreen.vpad.right == false)
262 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
263 if (prev_vpad.up == true && touchscreen.vpad.up == false)
264 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
265 if (prev_vpad.down == true && touchscreen.vpad.down == false)
266 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
268 return EVENT_HANDLED;
272 /* Normal mouse events */
273 if (!touchscreen.vpad.on) {
274 window->State.MouseX = x;
275 window->State.MouseY = y;
276 LOGI("Changed mouse position: %d,%d", x, y);
277 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
278 touchscreen.in_mmotion = true;
279 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
280 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
281 touchscreen.in_mmotion = false;
282 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
283 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
284 INVOKE_WCB(*window, Motion, (x, y));
288 return EVENT_HANDLED;
291 /* Let Android handle other events (e.g. Back and Menu buttons) */
292 return EVENT_NOT_HANDLED;
296 * Process the next main command.
298 void handle_cmd(struct android_app* app, int32_t cmd) {
300 case APP_CMD_SAVE_STATE:
301 /* The system has asked us to save our current state. Do so. */
302 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
304 case APP_CMD_INIT_WINDOW:
305 /* The window is being shown, get it ready. */
306 LOGI("handle_cmd: APP_CMD_INIT_WINDOW");
307 fgDisplay.pDisplay.single_window->Window.Handle = app->window;
308 /* glPlatformOpenWindow was waiting for Handle to be defined and
309 will now return from fgPlatformProcessSingleEvent() */
311 case APP_CMD_TERM_WINDOW:
312 /* The window is being hidden or closed, clean it up. */
313 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
314 fgDestroyWindow(fgDisplay.pDisplay.single_window);
316 case APP_CMD_DESTROY:
317 /* Not reached because GLUT exit()s when last window is closed */
318 LOGI("handle_cmd: APP_CMD_DESTROY");
320 case APP_CMD_GAINED_FOCUS:
321 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
323 case APP_CMD_LOST_FOCUS:
324 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
326 case APP_CMD_CONFIG_CHANGED:
327 /* Handle rotation / orientation change */
328 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
330 case APP_CMD_WINDOW_RESIZED:
331 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
332 if (fgDisplay.pDisplay.single_window->Window.pContext.eglSurface != EGL_NO_SURFACE)
333 /* Make ProcessSingleEvent detect the new size, only available
334 after the next SwapBuffer */
338 LOGI("handle_cmd: unhandled cmd=%d", cmd);
342 void fgPlatformProcessSingleEvent ( void )
344 static int32_t last_width = -1;
345 static int32_t last_height = -1;
347 /* When the screen is resized, the window handle still points to the
348 old window until the next SwapBuffer, while it's crucial to set
349 the size (onShape) correctly before the next onDisplay callback.
350 Plus we don't know if the next SwapBuffer already occurred at the
351 time we process the event (e.g. during onDisplay). */
352 /* So we do the check each time rather than on event. */
353 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
354 2.3, that next SwapBuffer is fake (but still necessary to get the
356 SFG_Window* window = fgDisplay.pDisplay.single_window;
357 if (window != NULL && window->Window.Handle != NULL) {
358 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
359 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
360 if (width != last_width || height != last_height) {
362 last_height = height;
363 LOGI("width=%d, height=%d", width, height);
364 if( FETCH_WCB( *window, Reshape ) )
365 INVOKE_WCB( *window, Reshape, ( width, height ) );
367 glViewport( 0, 0, width, height );
372 /* Read pending event. */
375 struct android_poll_source* source;
376 /* This is called "ProcessSingleEvent" but this means we'd only
377 process ~60 (screen Hz) mouse events per second, plus other ports
378 are processing all events already. So let's process all pending
380 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
381 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
382 /* Process this event. */
383 if (source != NULL) {
384 source->process(source->app, source);
389 void fgPlatformMainLoopPreliminaryWork ( void )
391 printf("fgPlatformMainLoopPreliminaryWork\n");
395 /* Make sure glue isn't stripped. */
396 /* JNI entry points need to be bundled even when linking statically */
400 void fgPlatformDeinitialiseInputDevices ( void )
402 fprintf(stderr, "fgPlatformDeinitialiseInputDevices: STUB\n");