4 * The BlackBerry-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
10 * Copyright (C) 2013 Vincent Simonetti
12 * Permission is hereby granted, free of charge, to any person obtaining a
13 * copy of this software and associated documentation files (the "Software"),
14 * to deal in the Software without restriction, including without limitation
15 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 * and/or sell copies of the Software, and to permit persons to whom the
17 * Software is furnished to do so, subject to the following conditions:
19 * The above copyright notice and this permission notice shall be included
20 * in all copies or substantial portions of the Software.
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25 * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
26 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
27 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 #include <GL/freeglut.h>
31 #include "fg_internal.h"
32 #include "egl/fg_window_egl.h"
35 #define LOGI(...) ((void)slog2fa(NULL, 1337, SLOG2_INFO, __VA_ARGS__))
36 #define LOGW(...) ((void)slog2fa(NULL, 1337, SLOG2_WARNING, __VA_ARGS__))
37 #include <sys/keycodes.h>
39 extern void fghOnReshapeNotify(SFG_Window *window, int width, int height, GLboolean forceNotify);
40 extern void fghOnPositionNotify(SFG_Window *window, int x, int y, GLboolean forceNotify);
41 extern void fgPlatformFullScreenToggle( SFG_Window *win );
42 extern void fgPlatformPositionWindow( SFG_Window *window, int x, int y );
43 extern void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height );
44 extern void fgPlatformPushWindow( SFG_Window *window );
45 extern void fgPlatformPopWindow( SFG_Window *window );
46 extern void fgPlatformHideWindow( SFG_Window *window );
47 extern void fgPlatformIconifyWindow( SFG_Window *window );
48 extern void fgPlatformShowWindow( SFG_Window *window );
50 static struct touchscreen touchscreen;
51 static unsigned char key_a2fg[256];
54 * Initialize BlackBerry keycode to GLUT keycode mapping
56 static void key_init() {
57 memset(key_a2fg, 0, sizeof(key_a2fg));
61 key_a2fg[KEYCODE_F1] = GLUT_KEY_F1;
62 key_a2fg[KEYCODE_F2] = GLUT_KEY_F2;
63 key_a2fg[KEYCODE_F3] = GLUT_KEY_F3;
64 key_a2fg[KEYCODE_F4] = GLUT_KEY_F4;
65 key_a2fg[KEYCODE_F5] = GLUT_KEY_F5;
66 key_a2fg[KEYCODE_F6] = GLUT_KEY_F6;
67 key_a2fg[KEYCODE_F7] = GLUT_KEY_F7;
68 key_a2fg[KEYCODE_F8] = GLUT_KEY_F8;
69 key_a2fg[KEYCODE_F9] = GLUT_KEY_F9;
70 key_a2fg[KEYCODE_F10] = GLUT_KEY_F10;
71 key_a2fg[KEYCODE_F11] = GLUT_KEY_F11;
72 key_a2fg[KEYCODE_F12] = GLUT_KEY_F12;
74 key_a2fg[KEYCODE_PG_UP] = GLUT_KEY_PAGE_UP;
75 key_a2fg[KEYCODE_PG_DOWN] = GLUT_KEY_PAGE_DOWN;
76 key_a2fg[KEYCODE_HOME] = GLUT_KEY_HOME;
77 key_a2fg[KEYCODE_END] = GLUT_KEY_END;
78 key_a2fg[KEYCODE_INSERT] = GLUT_KEY_INSERT;
80 key_a2fg[KEYCODE_UP] = GLUT_KEY_UP;
81 key_a2fg[KEYCODE_DOWN] = GLUT_KEY_DOWN;
82 key_a2fg[KEYCODE_LEFT] = GLUT_KEY_LEFT;
83 key_a2fg[KEYCODE_RIGHT] = GLUT_KEY_RIGHT;
85 key_a2fg[KEYCODE_LEFT_ALT] = GLUT_KEY_ALT_L;
86 key_a2fg[KEYCODE_RIGHT_ALT] = GLUT_KEY_ALT_R;
87 key_a2fg[KEYCODE_LEFT_SHIFT] = GLUT_KEY_SHIFT_L;
88 key_a2fg[KEYCODE_RIGHT_SHIFT] = GLUT_KEY_SHIFT_R;
89 key_a2fg[KEYCODE_LEFT_CTRL] = GLUT_KEY_CTRL_L;
90 key_a2fg[KEYCODE_RIGHT_CTRL] = GLUT_KEY_CTRL_R;
95 * Convert an Android key event to ASCII.
97 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) {
98 int32_t code = AKeyEvent_getKeyCode(event);
100 /* Handle a few special cases: * /
104 case AKEYCODE_FORWARD_DEL:
106 case AKEYCODE_ESCAPE:
110 /* Get usable JNI context * /
111 JNIEnv* env = app->activity->env;
112 JavaVM* vm = app->activity->vm;
113 (*vm)->AttachCurrentThread(vm, &env, NULL);
115 jclass KeyEventClass = (*env)->FindClass(env, "android/view/KeyEvent");
116 jmethodID KeyEventConstructor = (*env)->GetMethodID(env, KeyEventClass, "<init>", "(II)V");
117 jobject keyEvent = (*env)->NewObject(env, KeyEventClass, KeyEventConstructor,
118 AKeyEvent_getAction(event), AKeyEvent_getKeyCode(event));
119 jmethodID KeyEvent_getUnicodeChar = (*env)->GetMethodID(env, KeyEventClass, "getUnicodeChar", "(I)I");
120 int ascii = (*env)->CallIntMethod(env, keyEvent, KeyEvent_getUnicodeChar, AKeyEvent_getMetaState(event));
122 /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); * /
123 (*vm)->DetachCurrentThread(vm);
129 uint64_t fgPlatformSystemTime ( void )
132 gettimeofday( &now, NULL );
133 return now.tv_usec / 1000 + now.tv_sec * 1000;
137 * Does the magic required to relinquish the CPU until something interesting
140 void fgPlatformSleepForEvents( long msec )
146 * Process the next input event.
148 int32_t handle_input(struct android_app* app, AInputEvent* event) {
149 SFG_Window* window = fgWindowByHandle(app->window);
151 return EVENT_NOT_HANDLED;
153 /* FIXME: in Android, when a key is repeated, down
154 and up events happen most often at the exact same time. This
155 makes it impossible to animate based on key press time. * /
156 /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up * /
157 /* This looks like a bug in the Android virtual keyboard system :/
158 Real buttons such as the Back button appear to work correctly
159 (series of down events with proper getRepeatCount value). * /
161 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
162 /* LOGI("action: %d", AKeyEvent_getAction(event)); */
163 /* LOGI("keycode: %d", code); * /
164 int32_t code = AKeyEvent_getKeyCode(event);
166 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) {
167 int32_t keypress = 0;
168 unsigned char ascii = 0;
169 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
170 INVOKE_WCB(*window, Special, (keypress, window->State.MouseX, window->State.MouseY));
171 return EVENT_HANDLED;
172 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
173 INVOKE_WCB(*window, Keyboard, (ascii, window->State.MouseX, window->State.MouseY));
174 return EVENT_HANDLED;
177 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
178 int32_t keypress = 0;
179 unsigned char ascii = 0;
180 if ((keypress = key_a2fg[code]) && FETCH_WCB(*window, Special)) {
181 INVOKE_WCB(*window, SpecialUp, (keypress, window->State.MouseX, window->State.MouseY));
182 return EVENT_HANDLED;
183 } else if ((ascii = key_ascii(app, event)) && FETCH_WCB(*window, Keyboard)) {
184 INVOKE_WCB(*window, KeyboardUp, (ascii, window->State.MouseX, window->State.MouseY));
185 return EVENT_HANDLED;
190 int32_t source = AInputEvent_getSource(event);
191 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION
192 && source == AINPUT_SOURCE_TOUCHSCREEN) {
193 int32_t action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK;
194 /* Pointer ID for clicks * /
195 int32_t pidx = AMotionEvent_getAction(event) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
196 /* TODO: Handle multi-touch; also handle multiple sources/devices */
197 /* cf. http://sourceforge.net/mailarchive/forum.php?thread_name=20120518071314.GA28061%40perso.beuc.net&forum_name=freeglut-developer * /
199 LOGI("motion action=%d index=%d source=%d", action, pidx, source);
200 int count = AMotionEvent_getPointerCount(event);
202 for (i = 0; i < count; i++) {
203 LOGI("multi(%d): %.01f,%.01f",
204 AMotionEvent_getPointerId(event, i),
205 AMotionEvent_getX(event, i), AMotionEvent_getY(event, i));
208 float x = AMotionEvent_getX(event, 0);
209 float y = AMotionEvent_getY(event, 0);
211 /* Virtual arrows PAD */
212 /* Don't interfere with existing mouse move event * /
213 if (!touchscreen.in_mmotion) {
214 struct vpad_state prev_vpad = touchscreen.vpad;
215 touchscreen.vpad.left = touchscreen.vpad.right
216 = touchscreen.vpad.up = touchscreen.vpad.down = false;
218 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); * /
219 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
220 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) {
221 if ((x > 0 && x < 100) && (y > (height - 100) && y < height))
222 touchscreen.vpad.left = true;
223 if ((x > 200 && x < 300) && (y > (height - 100) && y < height))
224 touchscreen.vpad.right = true;
225 if ((x > 100 && x < 200) && (y > (height - 100) && y < height))
226 touchscreen.vpad.down = true;
227 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100)))
228 touchscreen.vpad.up = true;
230 if (action == AMOTION_EVENT_ACTION_DOWN &&
231 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up))
232 touchscreen.vpad.on = true;
233 if (action == AMOTION_EVENT_ACTION_UP)
234 touchscreen.vpad.on = false;
235 if (prev_vpad.left != touchscreen.vpad.left
236 || prev_vpad.right != touchscreen.vpad.right
237 || prev_vpad.up != touchscreen.vpad.up
238 || prev_vpad.down != touchscreen.vpad.down
239 || prev_vpad.on != touchscreen.vpad.on) {
240 if (FETCH_WCB(*window, Special)) {
241 if (prev_vpad.left == false && touchscreen.vpad.left == true)
242 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y));
243 else if (prev_vpad.right == false && touchscreen.vpad.right == true)
244 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y));
245 else if (prev_vpad.up == false && touchscreen.vpad.up == true)
246 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y));
247 else if (prev_vpad.down == false && touchscreen.vpad.down == true)
248 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y));
250 if (FETCH_WCB(*window, SpecialUp)) {
251 if (prev_vpad.left == true && touchscreen.vpad.left == false)
252 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y));
253 if (prev_vpad.right == true && touchscreen.vpad.right == false)
254 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y));
255 if (prev_vpad.up == true && touchscreen.vpad.up == false)
256 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y));
257 if (prev_vpad.down == true && touchscreen.vpad.down == false)
258 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y));
260 return EVENT_HANDLED;
264 /* Normal mouse events * /
265 if (!touchscreen.vpad.on) {
266 window->State.MouseX = x;
267 window->State.MouseY = y;
268 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) {
269 touchscreen.in_mmotion = true;
270 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y));
271 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) {
272 touchscreen.in_mmotion = false;
273 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y));
274 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) {
275 INVOKE_WCB(*window, Motion, (x, y));
279 return EVENT_HANDLED;
282 /* Let Android handle other events (e.g. Back and Menu buttons) * /
283 return EVENT_NOT_HANDLED;
288 * Process the next main command.
290 void handle_cmd(struct android_app* app, int32_t cmd) {
291 SFG_Window* window = fgWindowByHandle(app->window); /* may be NULL * /
293 /* App life cycle, in that order: * /
295 LOGI("handle_cmd: APP_CMD_START");
298 LOGI("handle_cmd: APP_CMD_RESUME");
299 /* Cf. fgPlatformProcessSingleEvent * /
301 case APP_CMD_INIT_WINDOW: /* surfaceCreated * /
302 /* The window is being shown, get it ready. * /
303 LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window);
304 fgDisplay.pDisplay.single_native_window = app->window;
305 /* start|resume: glPlatformOpenWindow was waiting for Handle to be
306 defined and will now continue processing * /
308 case APP_CMD_GAINED_FOCUS:
309 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS");
311 case APP_CMD_WINDOW_RESIZED:
312 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED");
313 if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE)
314 /* Make ProcessSingleEvent detect the new size, only available
315 after the next SwapBuffer * /
319 case APP_CMD_SAVE_STATE: /* onSaveInstanceState */
320 /* The system has asked us to save our current state, when it
321 pauses the application without destroying it right after. * /
322 app->savedState = strdup("Detect me as non-NULL on next android_main");
323 app->savedStateSize = strlen(app->savedState) + 1;
324 LOGI("handle_cmd: APP_CMD_SAVE_STATE");
327 LOGI("handle_cmd: APP_CMD_PAUSE");
328 /* Cf. fgPlatformProcessSingleEvent * /
330 case APP_CMD_LOST_FOCUS:
331 LOGI("handle_cmd: APP_CMD_LOST_FOCUS");
333 case APP_CMD_TERM_WINDOW: /* surfaceDestroyed * /
334 /* The application is being hidden, but may be restored * /
335 LOGI("handle_cmd: APP_CMD_TERM_WINDOW");
336 fghPlatformCloseWindowEGL(window);
337 fgDisplay.pDisplay.single_native_window = NULL;
340 LOGI("handle_cmd: APP_CMD_STOP");
342 case APP_CMD_DESTROY: /* Activity.onDestroy * /
343 LOGI("handle_cmd: APP_CMD_DESTROY");
344 /* User closed the application for good, let's kill the window * /
346 /* Can't use fgWindowByHandle as app->window is NULL * /
347 SFG_Window* window = fgStructure.CurrentWindow;
348 if (window != NULL) {
349 fgDestroyWindow(window);
351 LOGI("APP_CMD_DESTROY: No current window");
354 /* glue has already set android_app->destroyRequested=1 * /
357 case APP_CMD_CONFIG_CHANGED:
358 /* Handle rotation / orientation change * /
359 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED");
361 case APP_CMD_LOW_MEMORY:
362 LOGI("handle_cmd: APP_CMD_LOW_MEMORY");
365 LOGI("handle_cmd: unhandled cmd=%d", cmd);
370 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
371 GLboolean positionUse, int x, int y,
372 GLboolean sizeUse, int w, int h,
373 GLboolean gameMode, GLboolean isSubWindow );
375 void fgPlatformProcessSingleEvent ( void )
378 //--------------------------------
379 /* When the screen is resized, the window handle still points to the
380 old window until the next SwapBuffer, while it's crucial to set
381 the size (onShape) correctly before the next onDisplay callback.
382 Plus we don't know if the next SwapBuffer already occurred at the
383 time we process the event (e.g. during onDisplay). * /
384 /* So we do the check each time rather than on event. * /
385 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android
386 2.3, that next SwapBuffer is fake (but still necessary to get the
388 SFG_Window* window = fgStructure.CurrentWindow;
389 if (window != NULL && window->Window.Handle != NULL) {
390 int32_t width = ANativeWindow_getWidth(window->Window.Handle);
391 int32_t height = ANativeWindow_getHeight(window->Window.Handle);
392 fghOnReshapeNotify(window,width,height,GL_FALSE);
395 /* Read pending event. * /
398 struct android_poll_source* source;
399 /* This is called "ProcessSingleEvent" but this means we'd only
400 process ~60 (screen Hz) mouse events per second, plus other ports
401 are processing all events already. So let's process all pending
403 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { * /
404 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) {
405 /* Process this event. * /
406 if (source != NULL) {
407 source->process(source->app, source);
411 /* If we're not in RESUME state, Android paused us, so wait * /
412 struct android_app* app = fgDisplay.pDisplay.app;
413 if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) {
414 INVOKE_WCB(*window, AppStatus, (GLUT_APPSTATUS_PAUSE));
417 while (app->destroyRequested != 1 && (app->activityState != APP_CMD_RESUME)) {
418 if ((ident=ALooper_pollOnce(FOREVER, NULL, &events, (void**)&source)) >= 0) {
419 /* Process this event. * /
420 if (source != NULL) {
421 source->process(source->app, source);
425 /* Coming back from a pause: * /
426 /* - Recreate window context and surface */
427 /* - Call user-defined hook to restore resources (textures...) * /
428 /* - Exit pause loop * /
429 if (app->destroyRequested != 1) {
430 /* Android is full-screen only, simplified call.. * /
431 /* Ideally we'd have a fgPlatformReopenWindow() * /
432 /* If we're hidden by a non-fullscreen or translucent activity,
433 we'll be paused but not stopped, and keep the current
434 surface; in which case fgPlatformOpenWindow will no-op. * /
435 fgPlatformOpenWindow(window, "", GL_FALSE, 0, 0, GL_FALSE, 0, 0, GL_FALSE, GL_FALSE);
437 /* TODO: should there be a whole GLUT_INIT_WORK forced? probably...
438 * Could queue that up in APP_CMD_TERM_WINDOW handler, but it'll
439 * be not possible to ensure InitContext CB gets called before
440 * Resume CB like that.. so maybe just force calling initContext CB
441 * here is best. Or we could force work on the window in question..
442 * 1) save old work mask, 2) set mask to init only, 3) call fgProcessWork directly
443 * 4) set work mask back to the one saved in step 1.
445 if (!FETCH_WCB(*window, InitContext))
446 fgWarning("Resuming application, but no callback to reload context resources (glutInitContextFunc)");
449 INVOKE_WCB(*window, AppStatus, (GLUT_APPSTATUS_RESUME));
454 void fgPlatformMainLoopPreliminaryWork ( void )
456 LOGI("fgPlatformMainLoopPreliminaryWork\n", SLOG2_FA_END);
462 /* deal with work list items */
463 void fgPlatformInitWork(SFG_Window* window)
465 /* notify windowStatus/visibility */
466 INVOKE_WCB( *window, WindowStatus, ( GLUT_FULLY_RETAINED ) );
468 /* Position callback, always at 0,0 */
469 fghOnPositionNotify(window, 0, 0, GL_TRUE);
471 /* Size gets notified on window creation with size detection in mainloop above
472 * XXX CHECK: does this messages happen too early like on windows,
473 * so client code cannot have registered a callback yet and the message
474 * is thus never received by client?
478 void fgPlatformPosResZordWork(SFG_Window* window, unsigned int workMask)
480 if (workMask & GLUT_FULL_SCREEN_WORK)
481 fgPlatformFullScreenToggle( window );
482 if (workMask & GLUT_POSITION_WORK)
483 fgPlatformPositionWindow( window, window->State.DesiredXpos, window->State.DesiredYpos );
484 if (workMask & GLUT_SIZE_WORK)
485 fgPlatformReshapeWindow ( window, window->State.DesiredWidth, window->State.DesiredHeight );
486 if (workMask & GLUT_ZORDER_WORK)
488 if (window->State.DesiredZOrder < 0)
489 fgPlatformPushWindow( window );
491 fgPlatformPopWindow( window );
495 void fgPlatformVisibilityWork(SFG_Window* window)
497 /* Visibility status of window should get updated in the window message handlers
498 * For now, none of these functions called below do anything, so don't worry
501 SFG_Window *win = window;
502 switch (window->State.DesiredVisibility)
504 case DesireHiddenState:
505 fgPlatformHideWindow( window );
507 case DesireIconicState:
508 /* Call on top-level window */
511 fgPlatformIconifyWindow( win );
513 case DesireNormalState:
514 fgPlatformShowWindow( window );