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 * Handle a window configuration change. When no reshape
142 * callback is hooked, the viewport size is updated to
143 * match the new window size.
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 = fgStructure.CurrentWindow;
186 /* FIXME: in Android, when key is repeated, down and up events
187 happen most often at the exact same time. This makes it
188 impossible to animate based on key press time. */
189 /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */
191 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
192 /* LOGI("action: %d", AKeyEvent_getAction(event)); */
193 /* LOGI("keycode: %d", code); */
194 int32_t code = AKeyEvent_getKeyCode(event);
196 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
197 int32_t keypress = 0;
198 unsigned char ascii = 0;
199 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
200 INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
201 return EVENT_HANDLED;
202 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
203 INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
204 return EVENT_HANDLED;
207 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
208 int32_t keypress = 0;
209 unsigned char ascii = 0;
210 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
211 INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
212 return EVENT_HANDLED;
213 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
214 INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
215 return EVENT_HANDLED;
220 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
221 int32_t action = AMotionEvent_getAction(event);
222 float x = AMotionEvent_getX(event, 0);
223 float y = AMotionEvent_getY(event, 0);
224 LOGI("motion %.01f,%.01f action=%d", x, y, AMotionEvent_getAction(event));
226 /* Virtual arrows PAD */
227 /* Don't interfere with existing mouse move event */
228 if (!touchscreen.in_mmotion) {
229 struct vpad_state prev_vpad = touchscreen.vpad;
230 touchscreen.vpad.left = touchscreen.vpad.right
231 = touchscreen.vpad.up = touchscreen.vpad.down = false;
233 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */
234 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
235 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
236 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
237 touchscreen.vpad.left = true;
238 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
239 touchscreen.vpad.right = true;
240 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
241 touchscreen.vpad.down = true;
242 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
243 touchscreen.vpad.up = true;
245 if (action == AMOTION_EVENT_ACTION_DOWN &&
246 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
247 touchscreen.vpad.on = true;
248 if (action == AMOTION_EVENT_ACTION_UP)
249 touchscreen.vpad.on = false;
250 if (prev_vpad.left != touchscreen.vpad.left
251 || prev_vpad.right != touchscreen.vpad.right
252 || prev_vpad.up != touchscreen.vpad.up
253 || prev_vpad.down != touchscreen.vpad.down
254 || prev_vpad.on != touchscreen.vpad.on) {
255 if (FETCH_WCB(*window, Special)) {
256 if (prev_vpad.left == false && touchscreen.vpad.left == true)
257 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
258 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
259 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
260 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
261 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
262 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
263 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
265 if (FETCH_WCB(*window, SpecialUp)) {
266 if (prev_vpad.left == true && touchscreen.vpad.left == false)
267 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
268 if (prev_vpad.right == true && touchscreen.vpad.right == false)
269 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
270 if (prev_vpad.up == true && touchscreen.vpad.up == false)
271 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
272 if (prev_vpad.down == true && touchscreen.vpad.down == false)
273 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
275 return EVENT_HANDLED;
279 /* Normal mouse events */
280 if (!touchscreen.vpad.on) {
281 window->State.MouseX = x;
282 window->State.MouseY = y;
283 LOGI("Changed mouse position: %f,%f", x, y);
284 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
285 touchscreen.in_mmotion = true;
286 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
287 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
288 touchscreen.in_mmotion = false;
289 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
290 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
291 INVOKE_WCB(*window, Motion, (x, y));
295 return EVENT_HANDLED;
298 /* Let Android handle other events (e.g. Back and Menu buttons) */
299 return EVENT_NOT_HANDLED;
303 * Process the next main command.
305 void handle_cmd(struct android_app* app, int32_t cmd) {
307 case APP_CMD_SAVE_STATE:
308 /* The system has asked us to save our current state. Do so. */
309 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
311 case APP_CMD_INIT_WINDOW:
312 /* The window is being shown, get it ready. */
313 LOGI("handle_cmd: APP_CMD_INIT_WINDOW");
314 fgDisplay.pDisplay.single_window->Window.Handle = app->window;
315 /* glPlatformOpenWindow was waiting for Handle to be defined and
316 will now return from fgPlatformProcessSingleEvent() */
318 case APP_CMD_TERM_WINDOW:
319 /* The window is being hidden or closed, clean it up. */
320 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
321 fgDestroyWindow(fgDisplay.pDisplay.single_window);
323 case APP_CMD_DESTROY:
324 /* Not reached because GLUT exit()s when last window is closed */
325 LOGI("handle_cmd: APP_CMD_DESTROY");
327 case APP_CMD_GAINED_FOCUS:
328 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
330 case APP_CMD_LOST_FOCUS:
331 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
333 case APP_CMD_CONFIG_CHANGED:
334 /* Handle rotation / orientation change */
335 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
337 case APP_CMD_WINDOW_RESIZED:
338 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
339 if (fgDisplay.pDisplay.single_window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
340 /* Make ProcessSingleEvent detect the new size, only available
341 after the next SwapBuffer */
345 LOGI("handle_cmd: unhandled cmd=%d", cmd);
349 void fgPlatformProcessSingleEvent ( void )
351 static int32_t last_width = -1;
352 static int32_t last_height = -1;
354 /* When the screen is resized, the window handle still points to the
355 old window until the next SwapBuffer, while it's crucial to set
356 the size (onShape) correctly before the next onDisplay callback.
357 Plus we don't know if the next SwapBuffer already occurred at the
358 time we process the event (e.g. during onDisplay). */
359 /* So we do the check each time rather than on event. */
360 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
361 2.3, that next SwapBuffer is fake (but still necessary to get the
363 SFG_Window* window = fgDisplay.pDisplay.single_window;
364 if (window != NULL && window->Window.Handle != NULL) {
365 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
366 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
367 if (width != last_width || height != last_height) {
369 last_height = height;
370 LOGI("width=%d, height=%d", width, height);
371 if( FETCH_WCB( *window, Reshape ) )
372 INVOKE_WCB( *window, Reshape, ( width, height ) );
374 glViewport( 0, 0, width, height );
379 /* Read pending event. */
382 struct android_poll_source* source;
383 /* This is called "ProcessSingleEvent" but this means we'd only
384 process ~60 (screen Hz) mouse events per second, plus other ports
385 are processing all events already. So let's process all pending
387 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */
388 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
389 /* Process this event. */
390 if (source != NULL) {
391 source->process(source->app, source);
396 void fgPlatformMainLoopPreliminaryWork ( void )
398 printf("fgPlatformMainLoopPreliminaryWork\n");
402 /* Make sure glue isn't stripped. */
403 /* JNI entry points need to be bundled even when linking statically */
407 void fgPlatformDeinitialiseInputDevices ( void )
409 fprintf(stderr, "fgPlatformDeinitialiseInputDevices: STUB\n");