Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/thirdparty/freeglut/src/android/fg_main_android.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 /* | |
| 2 * fg_main_android.c | |
| 3 * | |
| 4 * The Android-specific windows message processing methods. | |
| 5 * | |
| 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 * | |
| 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: | |
| 17 * | |
| 18 * The above copyright notice and this permission notice shall be included | |
| 19 * in all copies or substantial portions of the Software. | |
| 20 * | |
| 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. | |
| 27 */ | |
| 28 | |
| 29 #include <GL/freeglut.h> | |
| 30 #include "fg_internal.h" | |
| 31 #include "egl/fg_window_egl.h" | |
| 32 | |
| 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> | |
| 38 | |
| 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 ); | |
| 49 | |
| 50 static struct touchscreen touchscreen; | |
| 51 static unsigned char key_a2fg[256]; | |
| 52 | |
| 53 /* Cf. http://developer.android.com/reference/android/view/KeyEvent.html */ | |
| 54 /* These codes are missing in <android/keycodes.h> */ | |
| 55 /* Don't convert to enum, since it may conflict with future version of | |
| 56 that <android/keycodes.h> */ | |
| 57 #define AKEYCODE_FORWARD_DEL 112 | |
| 58 #define AKEYCODE_CTRL_LEFT 113 | |
| 59 #define AKEYCODE_CTRL_RIGHT 114 | |
| 60 #define AKEYCODE_MOVE_HOME 122 | |
| 61 #define AKEYCODE_MOVE_END 123 | |
| 62 #define AKEYCODE_INSERT 124 | |
| 63 #define AKEYCODE_ESCAPE 127 | |
| 64 #define AKEYCODE_F1 131 | |
| 65 #define AKEYCODE_F2 132 | |
| 66 #define AKEYCODE_F3 133 | |
| 67 #define AKEYCODE_F4 134 | |
| 68 #define AKEYCODE_F5 135 | |
| 69 #define AKEYCODE_F6 136 | |
| 70 #define AKEYCODE_F7 137 | |
| 71 #define AKEYCODE_F8 138 | |
| 72 #define AKEYCODE_F9 139 | |
| 73 #define AKEYCODE_F10 140 | |
| 74 #define AKEYCODE_F11 141 | |
| 75 #define AKEYCODE_F12 142 | |
| 76 | |
| 77 #define EVENT_HANDLED 1 | |
| 78 #define EVENT_NOT_HANDLED 0 | |
| 79 | |
| 80 /** | |
| 81 * Initialize Android keycode to GLUT keycode mapping | |
| 82 */ | |
| 83 static void key_init() { | |
| 84 memset(key_a2fg, 0, sizeof(key_a2fg)); | |
| 85 | |
| 86 key_a2fg[AKEYCODE_F1] = GLUT_KEY_F1; | |
| 87 key_a2fg[AKEYCODE_F2] = GLUT_KEY_F2; | |
| 88 key_a2fg[AKEYCODE_F3] = GLUT_KEY_F3; | |
| 89 key_a2fg[AKEYCODE_F4] = GLUT_KEY_F4; | |
| 90 key_a2fg[AKEYCODE_F5] = GLUT_KEY_F5; | |
| 91 key_a2fg[AKEYCODE_F6] = GLUT_KEY_F6; | |
| 92 key_a2fg[AKEYCODE_F7] = GLUT_KEY_F7; | |
| 93 key_a2fg[AKEYCODE_F8] = GLUT_KEY_F8; | |
| 94 key_a2fg[AKEYCODE_F9] = GLUT_KEY_F9; | |
| 95 key_a2fg[AKEYCODE_F10] = GLUT_KEY_F10; | |
| 96 key_a2fg[AKEYCODE_F11] = GLUT_KEY_F11; | |
| 97 key_a2fg[AKEYCODE_F12] = GLUT_KEY_F12; | |
| 98 | |
| 99 key_a2fg[AKEYCODE_PAGE_UP] = GLUT_KEY_PAGE_UP; | |
| 100 key_a2fg[AKEYCODE_PAGE_DOWN] = GLUT_KEY_PAGE_DOWN; | |
| 101 key_a2fg[AKEYCODE_MOVE_HOME] = GLUT_KEY_HOME; | |
| 102 key_a2fg[AKEYCODE_MOVE_END] = GLUT_KEY_END; | |
| 103 key_a2fg[AKEYCODE_INSERT] = GLUT_KEY_INSERT; | |
| 104 | |
| 105 key_a2fg[AKEYCODE_DPAD_UP] = GLUT_KEY_UP; | |
| 106 key_a2fg[AKEYCODE_DPAD_DOWN] = GLUT_KEY_DOWN; | |
| 107 key_a2fg[AKEYCODE_DPAD_LEFT] = GLUT_KEY_LEFT; | |
| 108 key_a2fg[AKEYCODE_DPAD_RIGHT] = GLUT_KEY_RIGHT; | |
| 109 | |
| 110 key_a2fg[AKEYCODE_ALT_LEFT] = GLUT_KEY_ALT_L; | |
| 111 key_a2fg[AKEYCODE_ALT_RIGHT] = GLUT_KEY_ALT_R; | |
| 112 key_a2fg[AKEYCODE_SHIFT_LEFT] = GLUT_KEY_SHIFT_L; | |
| 113 key_a2fg[AKEYCODE_SHIFT_RIGHT] = GLUT_KEY_SHIFT_R; | |
| 114 key_a2fg[AKEYCODE_CTRL_LEFT] = GLUT_KEY_CTRL_L; | |
| 115 key_a2fg[AKEYCODE_CTRL_RIGHT] = GLUT_KEY_CTRL_R; | |
| 116 } | |
| 117 | |
| 118 /** | |
| 119 * Convert an Android key event to ASCII. | |
| 120 */ | |
| 121 static unsigned char key_ascii(struct android_app* app, AInputEvent* event) { | |
| 122 int32_t code = AKeyEvent_getKeyCode(event); | |
| 123 | |
| 124 /* Handle a few special cases: */ | |
| 125 switch (code) { | |
| 126 case AKEYCODE_DEL: | |
| 127 return 8; | |
| 128 case AKEYCODE_FORWARD_DEL: | |
| 129 return 127; | |
| 130 case AKEYCODE_ESCAPE: | |
| 131 return 27; | |
| 132 } | |
| 133 | |
| 134 /* Get usable JNI context */ | |
| 135 JNIEnv* env = app->activity->env; | |
| 136 JavaVM* vm = app->activity->vm; | |
| 137 (*vm)->AttachCurrentThread(vm, &env, NULL); | |
| 138 | |
| 139 jclass KeyEventClass = (*env)->FindClass(env, "android/view/KeyEvent"); | |
| 140 jmethodID KeyEventConstructor = (*env)->GetMethodID(env, KeyEventClass, "<init>", "(II)V"); | |
| 141 jobject keyEvent = (*env)->NewObject(env, KeyEventClass, KeyEventConstructor, | |
| 142 AKeyEvent_getAction(event), AKeyEvent_getKeyCode(event)); | |
| 143 jmethodID KeyEvent_getUnicodeChar = (*env)->GetMethodID(env, KeyEventClass, "getUnicodeChar", "(I)I"); | |
| 144 int ascii = (*env)->CallIntMethod(env, keyEvent, KeyEvent_getUnicodeChar, AKeyEvent_getMetaState(event)); | |
| 145 | |
| 146 /* LOGI("getUnicodeChar(%d) = %d ('%c')", AKeyEvent_getKeyCode(event), ascii, ascii); */ | |
| 147 (*vm)->DetachCurrentThread(vm); | |
| 148 | |
| 149 return ascii; | |
| 150 } | |
| 151 | |
| 152 unsigned long fgPlatformSystemTime ( void ) | |
| 153 { | |
| 154 struct timeval now; | |
| 155 gettimeofday( &now, NULL ); | |
| 156 return now.tv_usec/1000 + now.tv_sec*1000; | |
| 157 } | |
| 158 | |
| 159 /* | |
| 160 * Does the magic required to relinquish the CPU until something interesting | |
| 161 * happens. | |
| 162 */ | |
| 163 void fgPlatformSleepForEvents( long msec ) | |
| 164 { | |
| 165 /* Android's NativeActivity relies on a Looper/ALooper object to | |
| 166 notify about events. The Looper object is plugged on two | |
| 167 internal pipe(2)s to detect system and input events. Sadly you | |
| 168 can only ask the Looper for an event, not just ask whether | |
| 169 there is a pending event (and process it later). Consequently, | |
| 170 short of redesigning NativeActivity, we cannot | |
| 171 SleepForEvents. */ | |
| 172 } | |
| 173 | |
| 174 /** | |
| 175 * Process the next input event. | |
| 176 */ | |
| 177 int32_t handle_input(struct android_app* app, AInputEvent* event) { | |
| 178 SFG_Window* window = fgWindowByHandle(app->window); | |
| 179 if (window == NULL) | |
| 180 return EVENT_NOT_HANDLED; | |
| 181 | |
| 182 /* FIXME: in Android, when a key is repeated, down | |
| 183 and up events happen most often at the exact same time. This | |
| 184 makes it impossible to animate based on key press time. */ | |
| 185 /* e.g. down/up/wait/down/up rather than down/wait/down/wait/up */ | |
| 186 /* This looks like a bug in the Android virtual keyboard system :/ | |
| 187 Real buttons such as the Back button appear to work correctly | |
| 188 (series of down events with proper getRepeatCount value). */ | |
| 189 | |
| 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); | |
| 194 | |
| 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; | |
| 204 } | |
| 205 } | |
| 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; | |
| 215 } | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 int32_t source = AInputEvent_getSource(event); | |
| 220 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION | |
| 221 && source == AINPUT_SOURCE_TOUCHSCREEN) { | |
| 222 int32_t action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; | |
| 223 /* Pointer ID for clicks */ | |
| 224 int32_t pidx = AMotionEvent_getAction(event) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; | |
| 225 /* TODO: Handle multi-touch; also handle multiple sources/devices */ | |
| 226 /* cf. http://sourceforge.net/mailarchive/forum.php?thread_name=20120518071314.GA28061%40perso.beuc.net&forum_name=freeglut-developer */ | |
| 227 if (0) { | |
| 228 LOGI("motion action=%d index=%d source=%d", action, pidx, source); | |
| 229 int count = AMotionEvent_getPointerCount(event); | |
| 230 int i; | |
| 231 for (i = 0; i < count; i++) { | |
| 232 LOGI("multi(%d): %.01f,%.01f", | |
| 233 AMotionEvent_getPointerId(event, i), | |
| 234 AMotionEvent_getX(event, i), AMotionEvent_getY(event, i)); | |
| 235 } | |
| 236 } | |
| 237 float x = AMotionEvent_getX(event, 0); | |
| 238 float y = AMotionEvent_getY(event, 0); | |
| 239 | |
| 240 /* Virtual arrows PAD */ | |
| 241 /* Don't interfere with existing mouse move event */ | |
| 242 if (!touchscreen.in_mmotion) { | |
| 243 struct vpad_state prev_vpad = touchscreen.vpad; | |
| 244 touchscreen.vpad.left = touchscreen.vpad.right | |
| 245 = touchscreen.vpad.up = touchscreen.vpad.down = false; | |
| 246 | |
| 247 /* int32_t width = ANativeWindow_getWidth(window->Window.Handle); */ | |
| 248 int32_t height = ANativeWindow_getHeight(window->Window.Handle); | |
| 249 if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) { | |
| 250 if ((x > 0 && x < 100) && (y > (height - 100) && y < height)) | |
| 251 touchscreen.vpad.left = true; | |
| 252 if ((x > 200 && x < 300) && (y > (height - 100) && y < height)) | |
| 253 touchscreen.vpad.right = true; | |
| 254 if ((x > 100 && x < 200) && (y > (height - 100) && y < height)) | |
| 255 touchscreen.vpad.down = true; | |
| 256 if ((x > 100 && x < 200) && (y > (height - 200) && y < (height - 100))) | |
| 257 touchscreen.vpad.up = true; | |
| 258 } | |
| 259 if (action == AMOTION_EVENT_ACTION_DOWN && | |
| 260 (touchscreen.vpad.left || touchscreen.vpad.right || touchscreen.vpad.down || touchscreen.vpad.up)) | |
| 261 touchscreen.vpad.on = true; | |
| 262 if (action == AMOTION_EVENT_ACTION_UP) | |
| 263 touchscreen.vpad.on = false; | |
| 264 if (prev_vpad.left != touchscreen.vpad.left | |
| 265 || prev_vpad.right != touchscreen.vpad.right | |
| 266 || prev_vpad.up != touchscreen.vpad.up | |
| 267 || prev_vpad.down != touchscreen.vpad.down | |
| 268 || prev_vpad.on != touchscreen.vpad.on) { | |
| 269 if (FETCH_WCB(*window, Special)) { | |
| 270 if (prev_vpad.left == false && touchscreen.vpad.left == true) | |
| 271 INVOKE_WCB(*window, Special, (GLUT_KEY_LEFT, x, y)); | |
| 272 else if (prev_vpad.right == false && touchscreen.vpad.right == true) | |
| 273 INVOKE_WCB(*window, Special, (GLUT_KEY_RIGHT, x, y)); | |
| 274 else if (prev_vpad.up == false && touchscreen.vpad.up == true) | |
| 275 INVOKE_WCB(*window, Special, (GLUT_KEY_UP, x, y)); | |
| 276 else if (prev_vpad.down == false && touchscreen.vpad.down == true) | |
| 277 INVOKE_WCB(*window, Special, (GLUT_KEY_DOWN, x, y)); | |
| 278 } | |
| 279 if (FETCH_WCB(*window, SpecialUp)) { | |
| 280 if (prev_vpad.left == true && touchscreen.vpad.left == false) | |
| 281 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_LEFT, x, y)); | |
| 282 if (prev_vpad.right == true && touchscreen.vpad.right == false) | |
| 283 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_RIGHT, x, y)); | |
| 284 if (prev_vpad.up == true && touchscreen.vpad.up == false) | |
| 285 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_UP, x, y)); | |
| 286 if (prev_vpad.down == true && touchscreen.vpad.down == false) | |
| 287 INVOKE_WCB(*window, SpecialUp, (GLUT_KEY_DOWN, x, y)); | |
| 288 } | |
| 289 return EVENT_HANDLED; | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 /* Normal mouse events */ | |
| 294 if (!touchscreen.vpad.on) { | |
| 295 window->State.MouseX = x; | |
| 296 window->State.MouseY = y; | |
| 297 if (action == AMOTION_EVENT_ACTION_DOWN && FETCH_WCB(*window, Mouse)) { | |
| 298 touchscreen.in_mmotion = true; | |
| 299 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_DOWN, x, y)); | |
| 300 } else if (action == AMOTION_EVENT_ACTION_UP && FETCH_WCB(*window, Mouse)) { | |
| 301 touchscreen.in_mmotion = false; | |
| 302 INVOKE_WCB(*window, Mouse, (GLUT_LEFT_BUTTON, GLUT_UP, x, y)); | |
| 303 } else if (action == AMOTION_EVENT_ACTION_MOVE && FETCH_WCB(*window, Motion)) { | |
| 304 INVOKE_WCB(*window, Motion, (x, y)); | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 return EVENT_HANDLED; | |
| 309 } | |
| 310 | |
| 311 /* Let Android handle other events (e.g. Back and Menu buttons) */ | |
| 312 return EVENT_NOT_HANDLED; | |
| 313 } | |
| 314 | |
| 315 /** | |
| 316 * Process the next main command. | |
| 317 */ | |
| 318 void handle_cmd(struct android_app* app, int32_t cmd) { | |
| 319 SFG_Window* window = fgWindowByHandle(app->window); /* may be NULL */ | |
| 320 switch (cmd) { | |
| 321 /* App life cycle, in that order: */ | |
| 322 case APP_CMD_START: | |
| 323 LOGI("handle_cmd: APP_CMD_START"); | |
| 324 break; | |
| 325 case APP_CMD_RESUME: | |
| 326 LOGI("handle_cmd: APP_CMD_RESUME"); | |
| 327 /* Cf. fgPlatformProcessSingleEvent */ | |
| 328 break; | |
| 329 case APP_CMD_INIT_WINDOW: /* surfaceCreated */ | |
| 330 /* The window is being shown, get it ready. */ | |
| 331 LOGI("handle_cmd: APP_CMD_INIT_WINDOW %p", app->window); | |
| 332 fgDisplay.pDisplay.single_native_window = app->window; | |
| 333 /* start|resume: glPlatformOpenWindow was waiting for Handle to be | |
| 334 defined and will now continue processing */ | |
| 335 break; | |
| 336 case APP_CMD_GAINED_FOCUS: | |
| 337 LOGI("handle_cmd: APP_CMD_GAINED_FOCUS"); | |
| 338 break; | |
| 339 case APP_CMD_WINDOW_RESIZED: | |
| 340 LOGI("handle_cmd: APP_CMD_WINDOW_RESIZED"); | |
| 341 if (window->Window.pContext.egl.Surface != EGL_NO_SURFACE) | |
| 342 /* Make ProcessSingleEvent detect the new size, only available | |
| 343 after the next SwapBuffer */ | |
| 344 glutPostRedisplay(); | |
| 345 break; | |
| 346 | |
| 347 case APP_CMD_SAVE_STATE: /* onSaveInstanceState */ | |
| 348 /* The system has asked us to save our current state, when it | |
| 349 pauses the application without destroying it right after. */ | |
| 350 app->savedState = strdup("Detect me as non-NULL on next android_main"); | |
| 351 app->savedStateSize = strlen(app->savedState) + 1; | |
| 352 LOGI("handle_cmd: APP_CMD_SAVE_STATE"); | |
| 353 break; | |
| 354 case APP_CMD_PAUSE: | |
| 355 LOGI("handle_cmd: APP_CMD_PAUSE"); | |
| 356 /* Cf. fgPlatformProcessSingleEvent */ | |
| 357 break; | |
| 358 case APP_CMD_LOST_FOCUS: | |
| 359 LOGI("handle_cmd: APP_CMD_LOST_FOCUS"); | |
| 360 break; | |
| 361 case APP_CMD_TERM_WINDOW: /* surfaceDestroyed */ | |
| 362 /* The application is being hidden, but may be restored */ | |
| 363 LOGI("handle_cmd: APP_CMD_TERM_WINDOW"); | |
| 364 fghPlatformCloseWindowEGL(window); | |
| 365 fgDisplay.pDisplay.single_native_window = NULL; | |
| 366 break; | |
| 367 case APP_CMD_STOP: | |
| 368 LOGI("handle_cmd: APP_CMD_STOP"); | |
| 369 break; | |
| 370 case APP_CMD_DESTROY: /* Activity.onDestroy */ | |
| 371 LOGI("handle_cmd: APP_CMD_DESTROY"); | |
| 372 /* User closed the application for good, let's kill the window */ | |
| 373 { | |
| 374 /* Can't use fgWindowByHandle as app->window is NULL */ | |
| 375 SFG_Window* window = fgStructure.CurrentWindow; | |
| 376 if (window != NULL) { | |
| 377 fgDestroyWindow(window); | |
| 378 } else { | |
| 379 LOGI("APP_CMD_DESTROY: No current window"); | |
| 380 } | |
| 381 } | |
| 382 /* glue has already set android_app->destroyRequested=1 */ | |
| 383 break; | |
| 384 | |
| 385 case APP_CMD_CONFIG_CHANGED: | |
| 386 /* Handle rotation / orientation change */ | |
| 387 LOGI("handle_cmd: APP_CMD_CONFIG_CHANGED"); | |
| 388 break; | |
| 389 case APP_CMD_LOW_MEMORY: | |
| 390 LOGI("handle_cmd: APP_CMD_LOW_MEMORY"); | |
| 391 break; | |
| 392 default: | |
| 393 LOGI("handle_cmd: unhandled cmd=%d", cmd); | |
| 394 } | |
| 395 } | |
| 396 | |
| 397 void fgPlatformOpenWindow( SFG_Window* window, const char* title, | |
| 398 GLboolean positionUse, int x, int y, | |
| 399 GLboolean sizeUse, int w, int h, | |
| 400 GLboolean gameMode, GLboolean isSubWindow ); | |
| 401 | |
| 402 void fgPlatformProcessSingleEvent ( void ) | |
| 403 { | |
| 404 /* When the screen is resized, the window handle still points to the | |
| 405 old window until the next SwapBuffer, while it's crucial to set | |
| 406 the size (onShape) correctly before the next onDisplay callback. | |
| 407 Plus we don't know if the next SwapBuffer already occurred at the | |
| 408 time we process the event (e.g. during onDisplay). */ | |
| 409 /* So we do the check each time rather than on event. */ | |
| 410 /* Interestingly, on a Samsung Galaxy S/PowerVR SGX540 GPU/Android | |
| 411 2.3, that next SwapBuffer is fake (but still necessary to get the | |
| 412 new size). */ | |
| 413 SFG_Window* window = fgStructure.CurrentWindow; | |
| 414 if (window != NULL && window->Window.Handle != NULL) { | |
| 415 int32_t width = ANativeWindow_getWidth(window->Window.Handle); | |
| 416 int32_t height = ANativeWindow_getHeight(window->Window.Handle); | |
| 417 fghOnReshapeNotify(window,width,height,GL_FALSE); | |
| 418 } | |
| 419 | |
| 420 /* Read pending event. */ | |
| 421 int ident; | |
| 422 int events; | |
| 423 struct android_poll_source* source; | |
| 424 /* This is called "ProcessSingleEvent" but this means we'd only | |
| 425 process ~60 (screen Hz) mouse events per second, plus other ports | |
| 426 are processing all events already. So let's process all pending | |
| 427 events. */ | |
| 428 /* if ((ident=ALooper_pollOnce(0, NULL, &events, (void**)&source)) >= 0) { */ | |
| 429 while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) { | |
| 430 /* Process this event. */ | |
| 431 if (source != NULL) { | |
| 432 source->process(source->app, source); | |
| 433 } | |
| 434 } | |
| 435 | |
| 436 /* If we're not in RESUME state, Android paused us, so wait */ | |
| 437 struct android_app* app = fgDisplay.pDisplay.app; | |
| 438 if (app->destroyRequested != 1 && app->activityState != APP_CMD_RESUME) { | |
| 439 INVOKE_WCB(*window, AppStatus, (GLUT_APPSTATUS_PAUSE)); | |
| 440 | |
| 441 int FOREVER = -1; | |
| 442 while (app->destroyRequested != 1 && (app->activityState != APP_CMD_RESUME)) { | |
| 443 if ((ident=ALooper_pollOnce(FOREVER, NULL, &events, (void**)&source)) >= 0) { | |
| 444 /* Process this event. */ | |
| 445 if (source != NULL) { | |
| 446 source->process(source->app, source); | |
| 447 } | |
| 448 } | |
| 449 } | |
| 450 /* Coming back from a pause: */ | |
| 451 /* - Recreate window context and surface */ | |
| 452 /* - Call user-defined hook to restore resources (textures...) */ | |
| 453 /* - Exit pause loop */ | |
| 454 if (app->destroyRequested != 1) { | |
| 455 /* Android is full-screen only, simplified call.. */ | |
| 456 /* Ideally we'd have a fgPlatformReopenWindow() */ | |
| 457 /* If we're hidden by a non-fullscreen or translucent activity, | |
| 458 we'll be paused but not stopped, and keep the current | |
| 459 surface; in which case fgPlatformOpenWindow will no-op. */ | |
| 460 fgPlatformOpenWindow(window, "", GL_FALSE, 0, 0, GL_FALSE, 0, 0, GL_FALSE, GL_FALSE); | |
| 461 | |
| 462 /* TODO: should there be a whole GLUT_INIT_WORK forced? probably... | |
| 463 * Could queue that up in APP_CMD_TERM_WINDOW handler, but it'll | |
| 464 * be not possible to ensure InitContext CB gets called before | |
| 465 * Resume CB like that.. so maybe just force calling initContext CB | |
| 466 * here is best. Or we could force work on the window in question.. | |
| 467 * 1) save old work mask, 2) set mask to init only, 3) call fgProcessWork directly | |
| 468 * 4) set work mask back to the one saved in step 1. | |
| 469 */ | |
| 470 if (!FETCH_WCB(*window, InitContext)) | |
| 471 fgWarning("Resuming application, but no callback to reload context resources (glutInitContextFunc)"); | |
| 472 } | |
| 473 | |
| 474 INVOKE_WCB(*window, AppStatus, (GLUT_APPSTATUS_RESUME)); | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 void fgPlatformMainLoopPreliminaryWork ( void ) | |
| 479 { | |
| 480 LOGI("fgPlatformMainLoopPreliminaryWork\n"); | |
| 481 | |
| 482 key_init(); | |
| 483 | |
| 484 /* Make sure glue isn't stripped. */ | |
| 485 /* JNI entry points need to be bundled even when linking statically */ | |
| 486 app_dummy(); | |
| 487 } | |
| 488 | |
| 489 | |
| 490 /* deal with work list items */ | |
| 491 void fgPlatformInitWork(SFG_Window* window) | |
| 492 { | |
| 493 /* notify windowStatus/visibility */ | |
| 494 INVOKE_WCB( *window, WindowStatus, ( GLUT_FULLY_RETAINED ) ); | |
| 495 | |
| 496 /* Position callback, always at 0,0 */ | |
| 497 fghOnPositionNotify(window, 0, 0, GL_TRUE); | |
| 498 | |
| 499 /* Size gets notified on window creation with size detection in mainloop above | |
| 500 * XXX CHECK: does this messages happen too early like on windows, | |
| 501 * so client code cannot have registered a callback yet and the message | |
| 502 * is thus never received by client? | |
| 503 */ | |
| 504 } | |
| 505 | |
| 506 void fgPlatformPosResZordWork(SFG_Window* window, unsigned int workMask) | |
| 507 { | |
| 508 if (workMask & GLUT_FULL_SCREEN_WORK) | |
| 509 fgPlatformFullScreenToggle( window ); | |
| 510 if (workMask & GLUT_POSITION_WORK) | |
| 511 fgPlatformPositionWindow( window, window->State.DesiredXpos, window->State.DesiredYpos ); | |
| 512 if (workMask & GLUT_SIZE_WORK) | |
| 513 fgPlatformReshapeWindow ( window, window->State.DesiredWidth, window->State.DesiredHeight ); | |
| 514 if (workMask & GLUT_ZORDER_WORK) | |
| 515 { | |
| 516 if (window->State.DesiredZOrder < 0) | |
| 517 fgPlatformPushWindow( window ); | |
| 518 else | |
| 519 fgPlatformPopWindow( window ); | |
| 520 } | |
| 521 } | |
| 522 | |
| 523 void fgPlatformVisibilityWork(SFG_Window* window) | |
| 524 { | |
| 525 /* Visibility status of window should get updated in the window message handlers | |
| 526 * For now, none of these functions called below do anything, so don't worry | |
| 527 * about it | |
| 528 */ | |
| 529 SFG_Window *win = window; | |
| 530 switch (window->State.DesiredVisibility) | |
| 531 { | |
| 532 case DesireHiddenState: | |
| 533 fgPlatformHideWindow( window ); | |
| 534 break; | |
| 535 case DesireIconicState: | |
| 536 /* Call on top-level window */ | |
| 537 while (win->Parent) | |
| 538 win = win->Parent; | |
| 539 fgPlatformIconifyWindow( win ); | |
| 540 break; | |
| 541 case DesireNormalState: | |
| 542 fgPlatformShowWindow( window ); | |
| 543 break; | |
| 544 } | |
| 545 } | |
| 546 |
