/* xscreensaver, Copyright (c) 2016-2018 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * This file is three related things: * * - It is the Android-specific C companion to jwxyz-gl.c; * - It is how C calls into Java to do things that OpenGL does not * have access to without Java-based APIs; * - It is how the jwxyz.java class calls into C to run the hacks. */ #ifdef HAVE_ANDROID /* whole file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "screenhackI.h" #include "jwxyzI.h" #include "jwzglesI.h" #include "jwxyz-android.h" #include "textclient.h" #include "grabscreen.h" #include "pow2.h" #define countof(x) (sizeof(x)/sizeof(*(x))) extern struct xscreensaver_function_table *xscreensaver_function_table; struct function_table_entry { const char *progname; struct xscreensaver_function_table *xsft; }; #include "gen/function-table.h" struct event_queue { XEvent event; struct event_queue *next; }; static void send_queued_events (struct running_hack *rh); const char *progname; const char *progclass; int mono_p = 0; static JavaVM *global_jvm; static jmp_buf jmp_target; static double current_rotation = 0; extern void check_gl_error (const char *type); void jwxyz_logv(Bool error, const char *fmt, va_list args) { __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO, "xscreensaver", fmt, args); /* The idea here is that if the device/emulator dies shortly after a log message, then waiting here for a short while should increase the odds that adb logcat can pick up the message before everything blows up. Of course, doing this means dumping a ton of messages will slow things down significantly. */ # if 0 struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 25 * 1000000; nanosleep(&ts, NULL); # endif } /* Handle an abort on Android TODO: Test that Android handles aborts properly */ void jwxyz_abort (const char *fmt, ...) { /* Send error to Android device log */ if (!fmt || !*fmt) fmt = "abort"; va_list args; va_start (args, fmt); jwxyz_logv(True, fmt, args); va_end (args); char buf[10240]; va_start (args, fmt); vsprintf (buf, fmt, args); va_end (args); JNIEnv *env; (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL); if (! (*env)->ExceptionOccurred(env)) { // If there's already an exception queued, let's just go with that one. // Else, queue a Java exception to be thrown. (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"), buf); } // Nonlocal exit out of the jwxyz code. longjmp (jmp_target, 1); } /* We get to keep live references to Java classes in use because the VM can unload a class that isn't being used, which invalidates field and method IDs. https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074 */ // #### only need one var I think static size_t classRefCount = 0; static jobject globalRefjwxyz, globalRefIterable, globalRefIterator, globalRefMapEntry; static jfieldID runningHackField; static jmethodID iterableIterator, iteratorHasNext, iteratorNext; static jmethodID entryGetKey, entryGetValue; static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER; static void screenhack_do_fps (Display *, Window, fps_state *, void *); static char *get_string_resource_window (Window window, char *name); /* Also creates double-buffered windows. */ static void create_pixmap (Window win, Drawable p) { // See also: // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/ // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt Assert (p->frame.width, "p->frame.width"); Assert (p->frame.height, "p->frame.height"); if (win->window.rh->jwxyz_gl_p) { struct running_hack *rh = win->window.rh; if (rh->gl_fbo_p) { glGenTextures (1, &p->texture); glBindTexture (GL_TEXTURE_2D, p->texture); glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, to_pow2(p->frame.width), to_pow2(p->frame.height), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { EGLint attribs[5]; attribs[0] = EGL_WIDTH; attribs[1] = p->frame.width; attribs[2] = EGL_HEIGHT; attribs[3] = p->frame.height; attribs[4] = EGL_NONE; p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config, attribs); Assert (p->egl_surface != EGL_NO_SURFACE, "XCreatePixmap: got EGL_NO_SURFACE"); } } else { p->image_data = malloc (p->frame.width * p->frame.height * 4); } } static void free_pixmap (struct running_hack *rh, Pixmap p) { if (rh->jwxyz_gl_p) { if (rh->gl_fbo_p) { glDeleteTextures (1, &p->texture); } else { eglDestroySurface(rh->egl_display, p->egl_surface); } } else { free (p->image_data); } } static void prepare_context (struct running_hack *rh) { if (rh->egl_p) { /* TODO: Adreno recommends against doing this every frame. */ Assert (eglMakeCurrent(rh->egl_display, rh->egl_surface, rh->egl_surface, rh->egl_ctx), "eglMakeCurrent failed"); } /* Don't set matrices here; set them when an Xlib call triggers jwxyz_bind_drawable/jwxyz_set_matrices. */ if (rh->jwxyz_gl_p) rh->current_drawable = NULL; if (rh->xsft->visual == GL_VISUAL) jwzgles_make_current (rh->gles_state); } // Initialized OpenGL and runs the screenhack's init function. // static void doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env, const struct function_table_entry *chosen, jobject defaults, jint w, jint h, jobject jni_surface) { if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return. progname = chosen->progname; rh->xsft = chosen->xsft; rh->jni_env = env; rh->jobject = jwxyz_obj; // update this every time we call into C (*env)->GetJavaVM (env, &global_jvm); # undef ya_rand_init // This is the one and only place it is allowed ya_rand_init (0); Window wnd = (Window) calloc(1, sizeof(*wnd)); wnd->window.rh = rh; wnd->frame.width = w; wnd->frame.height = h; wnd->type = WINDOW; rh->window = wnd; progclass = rh->xsft->progclass; if ((*env)->ExceptionOccurred(env)) abort(); // This has to come before resource processing. It does not do graphics. if (rh->xsft->setup_cb) rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg); if ((*env)->ExceptionOccurred(env)) abort(); // Load the defaults. // Unceremoniously stolen from [PrefsReader defaultsToDict:]. jclass c = (*env)->GetObjectClass (env, defaults); jmethodID m = (*env)->GetMethodID (env, c, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); if (! m) abort(); if ((*env)->ExceptionOccurred(env)) abort(); const struct { const char *key, *val; } default_defaults[] = { { "doubleBuffer", "True" }, { "multiSample", "False" }, { "texFontCacheSize", "30" }, { "textMode", "date" }, { "textURL", "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" }, { "grabDesktopImages", "True" }, { "chooseRandomImages", "True" }, }; for (int i = 0; i < countof(default_defaults); i++) { const char *key = default_defaults[i].key; const char *val = default_defaults[i].val; char *key2 = malloc (strlen(progname) + strlen(key) + 2); strcpy (key2, progname); strcat (key2, "_"); strcat (key2, key); // defaults.put(key2, val); jstring jkey = (*env)->NewStringUTF (env, key2); jstring jval = (*env)->NewStringUTF (env, val); (*env)->CallObjectMethod (env, defaults, m, jkey, jval); (*env)->DeleteLocalRef (env, jkey); (*env)->DeleteLocalRef (env, jval); // Log ("default0: \"%s\" = \"%s\"", key2, val); free (key2); } const char *const *defs = rh->xsft->defaults; while (*defs) { char *line = strdup (*defs); char *key, *val; key = line; while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t') key++; val = key; while (*val && *val != ':') val++; if (*val != ':') abort(); *val++ = 0; while (*val == ' ' || *val == '\t') val++; unsigned long L = strlen(val); while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t')) val[--L] = 0; char *key2 = malloc (strlen(progname) + strlen(key) + 2); strcpy (key2, progname); strcat (key2, "_"); strcat (key2, key); // defaults.put(key2, val); jstring jkey = (*env)->NewStringUTF (env, key2); jstring jval = (*env)->NewStringUTF (env, val); (*env)->CallObjectMethod (env, defaults, m, jkey, jval); (*env)->DeleteLocalRef (env, jkey); (*env)->DeleteLocalRef (env, jval); // Log ("default: \"%s\" = \"%s\"", key2, val); free (key2); free (line); defs++; } (*env)->DeleteLocalRef (env, c); if ((*env)->ExceptionOccurred(env)) abort(); /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */ /* ####: This is lame, use a resource. */ rh->jwxyz_gl_p = rh->xsft->visual == DEFAULT_VISUAL && strcmp (progname, "kumppa") && strcmp (progname, "petri") && strcmp (progname, "slip") && strcmp (progname, "testx11"); Log ("init: %s @ %dx%d: using JWXYZ_%s", progname, w, h, rh->jwxyz_gl_p ? "GL" : "IMAGE"); rh->egl_p = rh->jwxyz_gl_p || rh->xsft->visual == GL_VISUAL; if (rh->egl_p) { // GL init. Must come after resource processing. rh->egl_display = eglGetDisplay (EGL_DEFAULT_DISPLAY); Assert (rh->egl_display != EGL_NO_DISPLAY, "init: EGL_NO_DISPLAY"); int egl_major, egl_minor; Assert (eglInitialize (rh->egl_display, &egl_major, &egl_minor), "eglInitialize failed"); // TODO: Skip depth and (probably) alpha for Xlib. // TODO: Could ask for EGL_SWAP_BEHAVIOR_PRESERVED_BIT here...maybe? // TODO: Probably should try to ask for EGL_PBUFFER_BIT. // TODO: Do like visual-gl.c and work from a list of configs. /* Probably don't need EGL_FRAMEBUFFER_TARGET_ANDROID here if GLSurfaceView doesn't use it. */ EGLint config_attribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 16, EGL_NONE }; EGLint num_config; Assert (eglChooseConfig (rh->egl_display, config_attribs, &rh->egl_config, 1, &num_config), "eglChooseConfig failed"); Assert (num_config == 1, "no EGL config chosen"); EGLint no_attribs[] = {EGL_NONE}; rh->egl_ctx = eglCreateContext (rh->egl_display, rh->egl_config, EGL_NO_CONTEXT, no_attribs); Assert (rh->egl_ctx != EGL_NO_CONTEXT, "init: EGL_NO_CONTEXT"); ANativeWindow *native_window = ANativeWindow_fromSurface (env, jni_surface); rh->egl_surface = eglCreateWindowSurface (rh->egl_display, rh->egl_config, native_window, no_attribs); Assert (rh->egl_surface != EGL_NO_SURFACE, "init: EGL_NO_SURFACE"); ANativeWindow_release (native_window); } else { rh->native_window = ANativeWindow_fromSurface (env, jni_surface); int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h, WINDOW_FORMAT_RGBX_8888); if (result < 0) { // Maybe check this earlier? Log ("can't set format (%d), surface may be invalid.", result); (*env)->ThrowNew (env, (*env)->FindClass(env, "org/jwz/xscreensaver/jwxyz$SurfaceLost"), "Surface lost"); ANativeWindow_release (rh->native_window); rh->native_window = NULL; return; } } prepare_context (rh); if (rh->egl_p) { Log ("init %s / %s / %s", glGetString (GL_VENDOR), glGetString (GL_RENDERER), glGetString (GL_VERSION)); } if (rh->jwxyz_gl_p) { const GLubyte *extensions = glGetString (GL_EXTENSIONS); rh->gl_fbo_p = jwzgles_gluCheckExtension ( (const GLubyte *)"GL_OES_framebuffer_object", extensions); if (rh->gl_fbo_p) { glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default); Assert (!rh->fb_default, "default framebuffer not current framebuffer"); glGenFramebuffersOES (1, &rh->fb_pixmap); wnd->texture = 0; } else { wnd->egl_surface = rh->egl_surface; } rh->frontbuffer_p = False; if (rh->xsft->visual == DEFAULT_VISUAL || (rh->xsft->visual == GL_VISUAL && strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) { rh->frontbuffer_p = True; # if 0 /* Might need to be 0 for Adreno...? */ if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) { EGLint surface_type; eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE, &surface_type); if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) { eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); rh->frontbuffer_p = False; } } # endif if (rh->frontbuffer_p) { /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */ create_pixmap (wnd, wnd); /* No preserving backbuffers, so manual blit from back to "front". */ rh->frontbuffer.type = PIXMAP; rh->frontbuffer.frame = wnd->frame; rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL); if(rh->gl_fbo_p) { rh->frontbuffer.texture = 0; } else { Assert (wnd->egl_surface != rh->egl_surface, "oops: wnd->egl_surface == rh->egl_surface"); rh->frontbuffer.egl_surface = rh->egl_surface; } } } rh->dpy = jwxyz_gl_make_display(wnd); } else { if (rh->xsft->visual == DEFAULT_VISUAL) create_pixmap (wnd, wnd); else wnd->image_data = NULL; static const unsigned char rgba_bytes[] = {0, 1, 2, 3}; rh->dpy = jwxyz_image_make_display(wnd, rgba_bytes); } Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window."); // TODO: Zero looks right, but double-check that is the right number /* Requires valid rh->dpy. */ if (rh->jwxyz_gl_p) rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL); if (rh->xsft->visual == GL_VISUAL) rh->gles_state = jwzgles_make_state(); END: ; } #undef DEBUG_FPS #ifdef DEBUG_FPS static double double_time (void) { struct timeval now; # ifdef GETTIMEOFDAY_TWO_ARGS struct timezone tzp; gettimeofday(&now, &tzp); # else gettimeofday(&now); # endif return (now.tv_sec + ((double) now.tv_usec * 0.000001)); } #endif // Animates a single frame of the current hack. // static jlong drawXScreenSaver (JNIEnv *env, struct running_hack *rh) { # ifdef DEBUG_FPS double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0; fps0 = fps1 = fps2 = fps3 = fps4 = double_time(); # endif unsigned long delay = 0; if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return. Window wnd = rh->window; prepare_context (rh); if (rh->egl_p) { /* There is some kind of weird redisplay race condition between Settings and the launching hack: e.g., Abstractile does XClearWindow at init, but the screen is getting filled with random bits. So let's wait a few frames before really starting up. TODO: Is this still true? */ if (++rh->frame_count < 8) { /* glClearColor (1.0, 0.0, 1.0, 0.0); */ glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */ goto END; } } # ifdef DEBUG_FPS fps1 = double_time(); # endif // The init function might do graphics (e.g. XClearWindow) so it has // to be run from inside onDrawFrame, not onSurfaceChanged. if (! rh->initted_p) { void *(*init_cb) (Display *, Window, void *) = (void *(*)(Display *, Window, void *)) rh->xsft->init_cb; if (rh->xsft->visual == DEFAULT_VISUAL) { unsigned int bg = get_pixel_resource (rh->dpy, 0, "background", "Background"); XSetWindowBackground (rh->dpy, wnd, bg); XClearWindow (rh->dpy, wnd); } rh->closure = init_cb (rh->dpy, wnd, rh->xsft->setup_arg); rh->initted_p = True; /* ignore_rotation_p doesn't quite work at the moment. */ rh->ignore_rotation_p = False; /* (rh->xsft->visual == DEFAULT_VISUAL && get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation")); */ if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) { rh->fpst = fps_init (rh->dpy, wnd); if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps; } else { rh->fpst = NULL; rh->xsft->fps_cb = 0; } if ((*env)->ExceptionOccurred(env)) abort(); } # ifdef DEBUG_FPS fps2 = double_time(); # endif // Apparently events don't come in on the drawing thread, and JNI flips // out. So we queue them there and run them here. // TODO: Events should be coming in on the drawing thread now, so dump this. send_queued_events (rh); # ifdef DEBUG_FPS fps3 = double_time(); # endif delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure); if (rh->jwxyz_gl_p) jwxyz_gl_flush (rh->dpy); # ifdef DEBUG_FPS fps4 = double_time(); # endif if (rh->fpst && rh->xsft->fps_cb) rh->xsft->fps_cb (rh->dpy, wnd, rh->fpst, rh->closure); if (rh->egl_p) { if (rh->jwxyz_gl_p && rh->frontbuffer_p) { jwxyz_gl_copy_area (rh->dpy, wnd, &rh->frontbuffer, rh->copy_gc, 0, 0, wnd->frame.width, wnd->frame.height, 0, 0); } // Getting failure here before/during/after resize, sometimes. Log sez: // W/Adreno-EGLSUB(18428): : dequeue native buffer fail: No such device, buffer=0x5f93bf5c, handle=0x0 if (!eglSwapBuffers(rh->egl_display, rh->egl_surface)) { Log ("eglSwapBuffers failed: 0x%x (probably asynchronous resize)", eglGetError()); } } else { ANativeWindow_Buffer buffer; ARect rect = {0, 0, wnd->frame.width, wnd->frame.height}; int32_t result = ANativeWindow_lock(rh->native_window, &buffer, &rect); if (result) { Log ("ANativeWindow_lock failed (result = %d), frame dropped", result); } else { /* Android can resize surfaces asynchronously. */ if (wnd->frame.width != buffer.width || wnd->frame.height != buffer.height) { Log ("buffer/window size mismatch: %dx%d (format = %d), wnd: %dx%d", buffer.width, buffer.height, buffer.format, wnd->frame.width, wnd->frame.height); } Assert (buffer.format == WINDOW_FORMAT_RGBA_8888 || buffer.format == WINDOW_FORMAT_RGBX_8888, "bad buffer format"); jwxyz_blit (wnd->image_data, jwxyz_image_pitch (wnd), 0, 0, buffer.bits, buffer.stride * 4, 0, 0, MIN(wnd->frame.width, buffer.width), MIN(wnd->frame.height, buffer.height)); // TODO: Clear any area to sides and bottom. ANativeWindow_unlockAndPost (rh->native_window); } } END: ; # ifdef DEBUG_FPS Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n", (int) ((fps1-fps0)*1000000), (int) ((fps2-fps1)*1000000), (int) ((fps3-fps2)*1000000), (int) ((fps4-fps3)*1000000), (int) ((double_time()-fps4)*1000000)); # endif return delay; } // Extracts the C structure that is stored in the jwxyz Java object. static struct running_hack * getRunningHack (JNIEnv *env, jobject thiz) { jlong result = (*env)->GetLongField (env, thiz, runningHackField); struct running_hack *rh = (struct running_hack *)(intptr_t)result; if (rh) rh->jobject = thiz; // update this every time we call into C return rh; } // Look up a class and mark it global in the provided variable. static jclass acquireClass (JNIEnv *env, const char *className, jobject *globalRef) { jclass clazz = (*env)->FindClass(env, className); *globalRef = (*env)->NewGlobalRef(env, clazz); return clazz; } /* Note: to find signature strings for native methods: cd ./project/xscreensaver/build/intermediates/classes/debug/ javap -s -p org.jwz.xscreensaver.jwxyz */ // Implementation of jwxyz's nativeInit Java method. // JNIEXPORT void JNICALL Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz, jstring jhack, jobject defaults, jint w, jint h, jobject jni_surface) { pthread_mutex_lock(&mutg); struct running_hack *rh = calloc(1, sizeof(struct running_hack)); if ((*env)->ExceptionOccurred(env)) abort(); // #### simplify if (!classRefCount) { jclass classjwxyz = (*env)->GetObjectClass(env, thiz); globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz); runningHackField = (*env)->GetFieldID (env, classjwxyz, "nativeRunningHackPtr", "J"); if ((*env)->ExceptionOccurred(env)) abort(); jclass classIterable = acquireClass(env, "java/lang/Iterable", &globalRefIterable); iterableIterator = (*env)->GetMethodID (env, classIterable, "iterator", "()Ljava/util/Iterator;"); if ((*env)->ExceptionOccurred(env)) abort(); jclass classIterator = acquireClass(env, "java/util/Iterator", &globalRefIterator); iteratorHasNext = (*env)->GetMethodID (env, classIterator, "hasNext", "()Z"); iteratorNext = (*env)->GetMethodID (env, classIterator, "next", "()Ljava/lang/Object;"); if ((*env)->ExceptionOccurred(env)) abort(); jclass classMapEntry = acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry); entryGetKey = (*env)->GetMethodID (env, classMapEntry, "getKey", "()Ljava/lang/Object;"); entryGetValue = (*env)->GetMethodID (env, classMapEntry, "getValue", "()Ljava/lang/Object;"); if ((*env)->ExceptionOccurred(env)) abort(); } ++classRefCount; // Store the C struct into the Java object. (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh); // TODO: Sort the list so binary search works. const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL); int chosen = 0; for (;;) { if (chosen == countof(function_table)) { Log ("Hack not found: %s", hack); abort(); } if (!strcmp(function_table[chosen].progname, hack)) break; chosen++; } (*env)->ReleaseStringUTFChars(env, jhack, hack); doinit (thiz, rh, env, &function_table[chosen], defaults, w, h, jni_surface); pthread_mutex_unlock(&mutg); } JNIEXPORT void JNICALL Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz, jint w, jint h, jdouble rot) { pthread_mutex_lock(&mutg); if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return. current_rotation = rot; Log ("native rotation: %f", current_rotation); struct running_hack *rh = getRunningHack(env, thiz); prepare_context (rh); if (rh->egl_p) { glViewport (0, 0, w, h); } else { int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h, WINDOW_FORMAT_RGBX_8888); if (result < 0) Log ("failed to resize surface (%d)", result); } Window wnd = rh->window; wnd->frame.x = 0; wnd->frame.y = 0; wnd->frame.width = w; wnd->frame.height = h; if (ignore_rotation_p(rh->dpy) && rot != 0 && rot != 180 && rot != -180) { int swap = w; w = h; h = swap; wnd->frame.width = w; wnd->frame.height = h; } if (rh->jwxyz_gl_p) { if (rh->frontbuffer_p) { free_pixmap (rh, wnd); create_pixmap (wnd, wnd); rh->frontbuffer.frame = wnd->frame; if (!rh->gl_fbo_p) rh->frontbuffer.egl_surface = rh->egl_surface; } jwxyz_window_resized (rh->dpy); } else if (rh->xsft->visual == DEFAULT_VISUAL) { free_pixmap (rh, wnd); create_pixmap (wnd, wnd); XClearWindow (rh->dpy, wnd); // TODO: This is lame. Copy the bits. } if (rh->initted_p) rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure, wnd->frame.width, wnd->frame.height); if (rh->xsft->visual == GL_VISUAL) { glMatrixMode (GL_PROJECTION); glRotatef (-rot, 0, 0, 1); glMatrixMode (GL_MODELVIEW); } END: pthread_mutex_unlock(&mutg); } JNIEXPORT jlong JNICALL Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz) { pthread_mutex_lock(&mutg); struct running_hack *rh = getRunningHack(env, thiz); jlong result = drawXScreenSaver(env, rh); pthread_mutex_unlock(&mutg); return result; } // TODO: Check Java side is calling this properly JNIEXPORT void JNICALL Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz) { pthread_mutex_lock(&mutg); if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return. struct running_hack *rh = getRunningHack(env, thiz); prepare_context (rh); if (rh->initted_p) rh->xsft->free_cb (rh->dpy, rh->window, rh->closure); if (rh->jwxyz_gl_p) XFreeGC (rh->dpy, rh->copy_gc); if (rh->xsft->visual == GL_VISUAL) jwzgles_free_state (); if (rh->jwxyz_gl_p) jwxyz_gl_free_display(rh->dpy); else jwxyz_image_free_display(rh->dpy); if (rh->egl_p) { // eglDestroy* probably isn't necessary here. eglMakeCurrent (rh->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface (rh->egl_display, rh->egl_surface); eglDestroyContext (rh->egl_display, rh->egl_ctx); eglTerminate (rh->egl_display); } else { if (rh->xsft->visual == DEFAULT_VISUAL) free_pixmap (rh, rh->window); if (rh->native_window) ANativeWindow_release (rh->native_window); } free(rh); (*env)->SetLongField(env, thiz, runningHackField, 0); --classRefCount; if (!classRefCount) { (*env)->DeleteGlobalRef(env, globalRefjwxyz); (*env)->DeleteGlobalRef(env, globalRefIterable); (*env)->DeleteGlobalRef(env, globalRefIterator); (*env)->DeleteGlobalRef(env, globalRefMapEntry); } END: pthread_mutex_unlock(&mutg); } static int send_event (struct running_hack *rh, XEvent *e) { // Assumes mutex is locked and context is prepared int *xP = 0, *yP = 0; switch (e->xany.type) { case ButtonPress: case ButtonRelease: xP = &e->xbutton.x; yP = &e->xbutton.y; break; case MotionNotify: xP = &e->xmotion.x; yP = &e->xmotion.y; break; } // Rotate the coordinates in the events to match the pixels. if (xP) { if (ignore_rotation_p (rh->dpy)) { Window win = XRootWindow (rh->dpy, 0); int w = win->frame.width; int h = win->frame.height; int swap; switch ((int) current_rotation) { case 180: case -180: // #### untested *xP = w - *xP; *yP = h - *yP; break; case 90: case -270: swap = *xP; *xP = *yP; *yP = swap; *yP = h - *yP; break; case -90: case 270: // #### untested swap = *xP; *xP = *yP; *yP = swap; *xP = w - *xP; break; } } rh->window->window.last_mouse_x = *xP; rh->window->window.last_mouse_y = *yP; } return (rh->xsft->event_cb ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e) : 0); } static void send_queued_events (struct running_hack *rh) { struct event_queue *event, *next; if (! rh->event_queue) return; for (event = rh->event_queue, next = event->next; event; event = next, next = (event ? event->next : 0)) { if (! send_event (rh, &event->event)) { // #### flash the screen or something } free (event); } rh->event_queue = 0; } static void queue_event (JNIEnv *env, jobject thiz, XEvent *e) { pthread_mutex_lock (&mutg); struct running_hack *rh = getRunningHack (env, thiz); struct event_queue *q = (struct event_queue *) malloc (sizeof(*q)); memcpy (&q->event, e, sizeof(*e)); q->next = 0; // Put it at the end. struct event_queue *oq; for (oq = rh->event_queue; oq && oq->next; oq = oq->next) ; if (oq) oq->next = q; else rh->event_queue = q; pthread_mutex_unlock (&mutg); } JNIEXPORT void JNICALL Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz, int x, int y, jboolean down) { XEvent e; memset (&e, 0, sizeof(e)); e.xany.type = (down ? ButtonPress : ButtonRelease); e.xbutton.button = Button1; e.xbutton.x = x; e.xbutton.y = y; queue_event (env, thiz, &e); } JNIEXPORT void JNICALL Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz, int x, int y) { XEvent e; memset (&e, 0, sizeof(e)); e.xany.type = MotionNotify; e.xmotion.x = x; e.xmotion.y = y; queue_event (env, thiz, &e); } JNIEXPORT void JNICALL Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz, jboolean down_p, int code, int mods) { XEvent e; memset (&e, 0, sizeof(e)); e.xkey.keycode = code; e.xkey.state = code; e.xany.type = (down_p ? KeyPress : KeyRelease); queue_event (env, thiz, &e); e.xany.type = KeyRelease; queue_event (env, thiz, &e); } /*************************************************************************** Backend functions for jwxyz-gl.c */ static void finish_bind_drawable (Display *dpy, Drawable dst) { jwxyz_assert_gl (); glViewport (0, 0, dst->frame.width, dst->frame.height); jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False); } static void bind_drawable_fbo (struct running_hack *rh, Drawable d) { glBindFramebufferOES (GL_FRAMEBUFFER_OES, d->texture ? rh->fb_pixmap : rh->fb_default); if (d->texture) { glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, d->texture, 0); } } void jwxyz_bind_drawable (Display *dpy, Window w, Drawable d) { struct running_hack *rh = w->window.rh; JNIEnv *env = w->window.rh->jni_env; if ((*env)->ExceptionOccurred(env)) abort(); if (rh->current_drawable != d) { if (rh->gl_fbo_p) { bind_drawable_fbo (rh, d); } else { eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx); } finish_bind_drawable (dpy, d); rh->current_drawable = d; } } void jwxyz_gl_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc, int src_x, int src_y, unsigned int width, unsigned int height, int dst_x, int dst_y) { Window w = XRootWindow (dpy, 0); struct running_hack *rh = w->window.rh; jwxyz_gl_flush (dpy); if (rh->gl_fbo_p && src->texture && src != dst) { bind_drawable_fbo (rh, dst); finish_bind_drawable (dpy, dst); rh->current_drawable = NULL; jwxyz_gl_set_gc (dpy, gc); glBindTexture (GL_TEXTURE_2D, src->texture); jwxyz_gl_draw_image (dpy, gc, GL_TEXTURE_2D, to_pow2(src->frame.width), to_pow2(src->frame.height), src_x, src->frame.height - src_y - height, jwxyz_drawable_depth (src), width, height, dst_x, dst_y, False); return; } #if 1 // Kumppa: 0.24 FPS // Hilarious display corruption ahoy! (Note to self: it's on the emulator.) // TODO for Dave: Recheck behavior on the emulator with the better Pixmap support. rh->current_drawable = NULL; if (rh->gl_fbo_p) bind_drawable_fbo (rh, src); else eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx); jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y, width, height, dst_x, dst_y); if (rh->gl_fbo_p) bind_drawable_fbo (rh, dst); finish_bind_drawable (dpy, dst); jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y, width, height, dst_x, dst_y); #else // Kumppa: 0.17 FPS jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y, width, height, dst_x, dst_y); #endif jwxyz_assert_gl (); } void jwxyz_assert_drawable (Window main_window, Drawable d) { check_gl_error("jwxyz_assert_drawable"); } void jwxyz_assert_gl (void) { check_gl_error("jwxyz_assert_gl"); } /*************************************************************************** Backend functions for jwxyz-image.c */ ptrdiff_t jwxyz_image_pitch (Drawable d) { return d->frame.width * 4; } void * jwxyz_image_data (Drawable d) { Assert (d->image_data, "no image storage (i.e. keep Xlib off the Window)"); return d->image_data; } const XRectangle * jwxyz_frame (Drawable d) { return &d->frame; } unsigned int jwxyz_drawable_depth (Drawable d) { return (d->type == WINDOW ? visual_depth (NULL, NULL) : d->pixmap.depth); } void jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp) { xvpos->x = 0; xvpos->y = 0; if (xp) { xp->x = w->window.last_mouse_x; xp->y = w->window.last_mouse_y; } } static void screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure) { fps_compute (fpst, 0, -1); fps_draw (fpst); } Pixmap XCreatePixmap (Display *dpy, Drawable d, unsigned int width, unsigned int height, unsigned int depth) { Window win = XRootWindow(dpy, 0); Pixmap p = malloc(sizeof(*p)); p->type = PIXMAP; p->frame.x = 0; p->frame.y = 0; p->frame.width = width; p->frame.height = height; Assert(depth == 1 || depth == visual_depth(NULL, NULL), "XCreatePixmap: bad depth"); p->pixmap.depth = depth; create_pixmap (win, p); /* For debugging. */ # if 0 jwxyz_bind_drawable (dpy, win, p); glClearColor (frand(1), frand(1), frand(1), 0); glClear (GL_COLOR_BUFFER_BIT); # endif return p; } int XFreePixmap (Display *d, Pixmap p) { struct running_hack *rh = XRootWindow(d, 0)->window.rh; if (rh->jwxyz_gl_p) { jwxyz_gl_flush (d); if (rh->current_drawable == p) rh->current_drawable = NULL; } free_pixmap (rh, p); free (p); return 0; } double current_device_rotation (void) { return current_rotation; } Bool ignore_rotation_p (Display *dpy) { struct running_hack *rh = XRootWindow(dpy, 0)->window.rh; return rh->ignore_rotation_p; } static char * jstring_dup (JNIEnv *env, jstring str) { Assert (str, "expected jstring, not null"); const char *cstr = (*env)->GetStringUTFChars (env, str, 0); size_t len = (*env)->GetStringUTFLength (env, str) + 1; char *result = malloc (len); if (result) { memcpy (result, cstr, len); } (*env)->ReleaseStringUTFChars (env, str, cstr); return result; } static char * get_string_resource_window (Window window, char *name) { JNIEnv *env = window->window.rh->jni_env; jobject obj = window->window.rh->jobject; if ((*env)->ExceptionOccurred(env)) abort(); jstring jstr = (*env)->NewStringUTF (env, name); jclass c = (*env)->GetObjectClass (env, obj); jmethodID m = (*env)->GetMethodID (env, c, "getStringResource", "(Ljava/lang/String;)Ljava/lang/String;"); if ((*env)->ExceptionOccurred(env)) abort(); jstring jvalue = (m ? (*env)->CallObjectMethod (env, obj, m, jstr) : NULL); (*env)->DeleteLocalRef (env, c); (*env)->DeleteLocalRef (env, jstr); char *ret = 0; if (jvalue) ret = jstring_dup (env, jvalue); Log("pref %s = %s", name, (ret ? ret : "(null)")); return ret; } char * get_string_resource (Display *dpy, char *name, char *class) { return get_string_resource_window (RootWindow (dpy, 0), name); } /* Returns the contents of the URL. */ char * textclient_mobile_url_string (Display *dpy, const char *url) { Window window = RootWindow (dpy, 0); JNIEnv *env = window->window.rh->jni_env; jobject obj = window->window.rh->jobject; jstring jstr = (*env)->NewStringUTF (env, url); jclass c = (*env)->GetObjectClass (env, obj); jmethodID m = (*env)->GetMethodID (env, c, "loadURL", "(Ljava/lang/String;)Ljava/nio/ByteBuffer;"); if ((*env)->ExceptionOccurred(env)) abort(); jobject buf = (m ? (*env)->CallObjectMethod (env, obj, m, jstr) : NULL); (*env)->DeleteLocalRef (env, c); (*env)->DeleteLocalRef (env, jstr); char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0); char *body2; if (body) { int L = (*env)->GetDirectBufferCapacity (env, buf); body2 = malloc (L + 1); memcpy (body2, body, L); body2[L] = 0; } else { body2 = strdup ("ERROR"); } if (buf) (*env)->DeleteLocalRef (env, buf); return body2; } float jwxyz_scale (Window main_window) { // TODO: Use the actual device resolution. return 2; } const char * jwxyz_default_font_family (int require) { /* Font families in XLFDs are totally ignored (for now). */ return "sans-serif"; } void * jwxyz_load_native_font (Window window, int traits_jwxyz, int mask_jwxyz, const char *font_name_ptr, size_t font_name_length, int font_name_type, float size, char **family_name_ret, int *ascent_ret, int *descent_ret) { JNIEnv *env = window->window.rh->jni_env; jobject obj = window->window.rh->jobject; jstring jname = NULL; if (font_name_ptr) { char *name_nul = malloc(font_name_length + 1); memcpy(name_nul, font_name_ptr, font_name_length); name_nul[font_name_length] = 0; jname = (*env)->NewStringUTF (env, name_nul); free(name_nul); } jclass c = (*env)->GetObjectClass (env, obj); jmethodID m = (*env)->GetMethodID (env, c, "loadFont", "(IILjava/lang/String;IF)[Ljava/lang/Object;"); if ((*env)->ExceptionOccurred(env)) abort(); jobjectArray array = (m ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz, (jint)traits_jwxyz, jname, (jint)font_name_type, (jfloat)size) : NULL); (*env)->DeleteLocalRef (env, c); if (array) { jobject font = (*env)->GetObjectArrayElement (env, array, 0); jobject family_name = (jstring) ((*env)->GetObjectArrayElement (env, array, 1)); jobject asc = (*env)->GetObjectArrayElement (env, array, 2); jobject desc = (*env)->GetObjectArrayElement (env, array, 3); if ((*env)->ExceptionOccurred(env)) abort(); if (family_name_ret) *family_name_ret = jstring_dup (env, family_name); jobject paint = (*env)->NewGlobalRef (env, font); if ((*env)->ExceptionOccurred(env)) abort(); c = (*env)->GetObjectClass(env, asc); m = (*env)->GetMethodID (env, c, "floatValue", "()F"); if ((*env)->ExceptionOccurred(env)) abort(); *ascent_ret = (int) (*env)->CallFloatMethod (env, asc, m); *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m); return (void *) paint; } else { return 0; } } void jwxyz_release_native_font (Display *dpy, void *native_font) { Window window = RootWindow (dpy, 0); JNIEnv *env = window->window.rh->jni_env; if ((*env)->ExceptionOccurred(env)) abort(); (*env)->DeleteGlobalRef (env, (jobject) native_font); if ((*env)->ExceptionOccurred(env)) abort(); } /* If the local reference table fills up, use this to figure out where you missed a call to DeleteLocalRef. */ /* static void dump_reference_tables(JNIEnv *env) { jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug"); jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables", "()V"); (*env)->CallStaticVoidMethod (env, c, m); (*env)->DeleteLocalRef (env, c); } */ // Returns the metrics of the multi-character, single-line UTF8 or Latin1 // string. If pixmap_ret is provided, also renders the text. // void jwxyz_render_text (Display *dpy, void *native_font, const char *str, size_t len, Bool utf8, Bool antialias_p, XCharStruct *cs, char **pixmap_ret) { Window window = RootWindow (dpy, 0); JNIEnv *env = window->window.rh->jni_env; jobject obj = window->window.rh->jobject; char *s2; if (utf8) { s2 = malloc (len + 1); memcpy (s2, str, len); s2[len] = 0; } else { // Convert Latin1 to UTF8 s2 = malloc (len * 2 + 1); unsigned char *s3 = (unsigned char *) s2; int i; for (i = 0; i < len; i++) { unsigned char c = ((unsigned char *) str)[i]; if (! (c & 0x80)) { *s3++ = c; } else { *s3++ = (0xC0 | (0x03 & (c >> 6))); *s3++ = (0x80 | (0x3F & c)); } } *s3 = 0; } jstring jstr = (*env)->NewStringUTF (env, s2); jclass c = (*env)->GetObjectClass (env, obj); jmethodID m = (*env)->GetMethodID (env, c, "renderText", "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;"); if ((*env)->ExceptionOccurred(env)) abort(); jobject buf = (m ? (*env)->CallObjectMethod (env, obj, m, (jobject) native_font, jstr, (pixmap_ret ? JNI_TRUE : JNI_FALSE), antialias_p) : NULL); (*env)->DeleteLocalRef (env, c); (*env)->DeleteLocalRef (env, jstr); free (s2); if ((*env)->ExceptionOccurred(env)) abort(); unsigned char *bits = (unsigned char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0); if (bits) { int i = 0; int L = (*env)->GetDirectBufferCapacity (env, buf); if (L < 10) abort(); cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2; cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2; cs->width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2; cs->ascent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2; cs->descent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2; if (pixmap_ret) { char *pix = malloc (L - i); if (! pix) abort(); memcpy (pix, bits + i, L - i); *pixmap_ret = pix; } } else { memset (cs, 0, sizeof(*cs)); if (pixmap_ret) *pixmap_ret = 0; } if (buf) (*env)->DeleteLocalRef (env, buf); } char * jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc) { JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env; /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */ jclass c = (*env)->FindClass (env, "java/lang/Character"), c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError"); if ((*env)->ExceptionOccurred(env)) abort(); jmethodID m = (*env)->GetStaticMethodID ( env, c, "getName", "(I)Ljava/lang/String;"); jthrowable exc = (*env)->ExceptionOccurred(env); if (exc) { if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) { (*env)->ExceptionClear (env); Assert (!m, "jwxyz_unicode_character_name: m?"); } else { abort(); } } char *ret = NULL; if (m) { jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc); if (name) ret = jstring_dup (env, name); } if (!ret) { asprintf(&ret, "U+%.4lX", uc); } return ret; } /* Called from utils/grabclient.c */ char * jwxyz_draw_random_image (Display *dpy, Drawable drawable, GC gc) { Window window = RootWindow (dpy, 0); struct running_hack *rh = window->window.rh; JNIEnv *env = rh->jni_env; jobject obj = rh->jobject; Bool images_p = get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages"); Bool grab_p = get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages"); Bool rotate_p = get_boolean_resource (rh->dpy, "rotateImages", "RotateImages"); if (!images_p && !grab_p) return 0; if (grab_p && images_p) { grab_p = !(random() & 5); /* if both, screenshot 1/5th of the time */ images_p = !grab_p; } jclass c = (*env)->GetObjectClass (env, obj); jmethodID m = (*env)->GetMethodID (env, c, (grab_p ? "getScreenshot" : "checkThenLoadRandomImage"), "(IIZ)[Ljava/lang/Object;"); if ((*env)->ExceptionOccurred(env)) abort(); jobjectArray img_name = ( m ? (*env)->CallObjectMethod (env, obj, m, drawable->frame.width, drawable->frame.height, (rotate_p ? JNI_TRUE : JNI_FALSE)) : NULL); if ((*env)->ExceptionOccurred(env)) abort(); (*env)->DeleteLocalRef (env, c); if (!img_name) { fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image")); return NULL; } jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 0); AndroidBitmapInfo bmp_info; AndroidBitmap_getInfo (env, jbitmap, &bmp_info); XImage *img = XCreateImage (dpy, NULL, visual_depth(NULL, NULL), ZPixmap, 0, NULL, bmp_info.width, bmp_info.height, 0, bmp_info.stride); AndroidBitmap_lockPixels (env, jbitmap, (void **) &img->data); XPutImage (dpy, drawable, gc, img, 0, 0, (drawable->frame.width - bmp_info.width) / 2, (drawable->frame.height - bmp_info.height) / 2, bmp_info.width, bmp_info.height); AndroidBitmap_unlockPixels (env, jbitmap); img->data = NULL; XDestroyImage (img); return jstring_dup (env, (*env)->GetObjectArrayElement (env, img_name, 1)); } XImage * jwxyz_png_to_ximage (Display *dpy, Visual *visual, const unsigned char *png_data, unsigned long data_size) { Window window = RootWindow (dpy, 0); struct running_hack *rh = window->window.rh; JNIEnv *env = rh->jni_env; jobject obj = rh->jobject; jclass c = (*env)->GetObjectClass (env, obj); jmethodID m = (*env)->GetMethodID (env, c, "decodePNG", "([B)Landroid/graphics/Bitmap;"); if ((*env)->ExceptionOccurred(env)) abort(); jbyteArray jdata = (*env)->NewByteArray (env, data_size); (*env)->SetByteArrayRegion (env, jdata, 0, data_size, (const jbyte *) png_data); jobject jbitmap = ( m ? (*env)->CallObjectMethod (env, obj, m, jdata) : NULL); if ((*env)->ExceptionOccurred(env)) abort(); (*env)->DeleteLocalRef (env, c); (*env)->DeleteLocalRef (env, jdata); if (!jbitmap) return NULL; AndroidBitmapInfo bmp_info; AndroidBitmap_getInfo (env, jbitmap, &bmp_info); XImage *img = XCreateImage (dpy, NULL, 32, ZPixmap, 0, NULL, bmp_info.width, bmp_info.height, 8, bmp_info.stride); char *bits = 0; AndroidBitmap_lockPixels (env, jbitmap, (void **) &bits); img->data = (char *) calloc (img->bytes_per_line, img->height); memcpy (img->data, bits, img->bytes_per_line * img->height); AndroidBitmap_unlockPixels (env, jbitmap); // Java should have returned ARGB data. // WTF, why isn't ANDROID_BITMAP_FORMAT_ARGB_8888 defined? if (bmp_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) abort(); # ifndef __BYTE_ORDER__ // A GCC (and Clang)-ism. # error Need a __BYTE_ORDER__. # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ img->byte_order = img->bitmap_bit_order = LSBFirst; # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ img->byte_order = img->bitmap_bit_order = MSBFirst; # else # error Need a __BYTE_ORDER__. # endif static const union { uint8_t bytes[4]; uint32_t pixel; } c0 = {{0xff, 0x00, 0x00, 0x00}}, c1 = {{0x00, 0xff, 0x00, 0x00}}, c2 = {{0x00, 0x00, 0xff, 0x00}}; img->red_mask = c0.pixel; img->green_mask = c1.pixel; img->blue_mask = c2.pixel; return img; } #endif /* HAVE_ANDROID */