1 /* xscreensaver, Copyright (c) 2016-2017 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * This file is three related things:
13 * - It is the Android-specific C companion to jwxyz-gl.c;
14 * - It is how C calls into Java to do things that OpenGL does not
15 * have access to without Java-based APIs;
16 * - It is how the jwxyz.java class calls into C to run the hacks.
19 #ifdef HAVE_ANDROID /* whole file */
30 #include <android/log.h>
31 #include <android/native_window_jni.h>
34 #include "screenhackI.h"
37 #include "jwxyz-android.h"
38 #include "textclient.h"
39 #include "grabscreen.h"
43 #define countof(x) (sizeof(x)/sizeof(*(x)))
45 extern struct xscreensaver_function_table *xscreensaver_function_table;
47 struct function_table_entry {
49 struct xscreensaver_function_table *xsft;
53 #include "gen/function-table.h"
57 struct event_queue *next;
60 static void send_queued_events (struct running_hack *rh);
63 const char *progclass;
66 static JavaVM *global_jvm;
67 static jmp_buf jmp_target;
69 static double current_rotation = 0;
71 extern void check_gl_error (const char *type);
74 jwxyz_logv(Bool error, const char *fmt, va_list args)
76 __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
77 "xscreensaver", fmt, args);
79 /* The idea here is that if the device/emulator dies shortly after a log
80 message, then waiting here for a short while should increase the odds
81 that adb logcat can pick up the message before everything blows up. Of
82 course, doing this means dumping a ton of messages will slow things down
88 ts.tv_nsec = 25 * 1000000;
93 /* Handle an abort on Android
94 TODO: Test that Android handles aborts properly
97 jwxyz_abort (const char *fmt, ...)
99 /* Send error to Android device log */
104 va_start (args, fmt);
105 jwxyz_logv(True, fmt, args);
109 va_start (args, fmt);
110 vsprintf (buf, fmt, args);
114 (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
116 if (! (*env)->ExceptionOccurred(env)) {
117 // If there's already an exception queued, let's just go with that one.
118 // Else, queue a Java exception to be thrown.
119 (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"),
123 // Nonlocal exit out of the jwxyz code.
124 longjmp (jmp_target, 1);
128 /* We get to keep live references to Java classes in use because the VM can
129 unload a class that isn't being used, which invalidates field and method
131 https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
135 // #### only need one var I think
136 static size_t classRefCount = 0;
137 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
140 static jfieldID runningHackField;
141 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
142 static jmethodID entryGetKey, entryGetValue;
144 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
146 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
147 static char *get_string_resource_window (Window window, char *name);
150 /* Also creates double-buffered windows. */
152 create_pixmap (Window win, Drawable p)
155 // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
156 // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
157 // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
159 Assert (p->frame.width, "p->frame.width");
160 Assert (p->frame.height, "p->frame.height");
162 if (win->window.rh->jwxyz_gl_p) {
163 struct running_hack *rh = win->window.rh;
166 glGenTextures (1, &p->texture);
167 glBindTexture (GL_TEXTURE_2D, p->texture);
169 glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
170 to_pow2(p->frame.width), to_pow2(p->frame.height),
171 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
173 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
174 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
177 attribs[0] = EGL_WIDTH;
178 attribs[1] = p->frame.width;
179 attribs[2] = EGL_HEIGHT;
180 attribs[3] = p->frame.height;
181 attribs[4] = EGL_NONE;
183 p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
185 Assert (p->egl_surface != EGL_NO_SURFACE,
186 "XCreatePixmap: got EGL_NO_SURFACE");
189 p->image_data = malloc (p->frame.width * p->frame.height * 4);
195 free_pixmap (struct running_hack *rh, Pixmap p)
197 if (rh->jwxyz_gl_p) {
199 glDeleteTextures (1, &p->texture);
201 eglDestroySurface(rh->egl_display, p->egl_surface);
204 free (p->image_data);
210 prepare_context (struct running_hack *rh)
212 if (rh->jwxyz_gl_p) {
213 /* TODO: Adreno recommends against doing this every frame. */
214 Assert (eglMakeCurrent(rh->egl_display, rh->egl_surface, rh->egl_surface,
216 "eglMakeCurrent failed");
218 /* Don't set matrices here; set them when an Xlib call triggers
219 jwxyz_bind_drawable/jwxyz_set_matrices.
221 rh->current_drawable = NULL;
224 jwzgles_make_current (rh->gles_state);
228 // Initialized OpenGL and runs the screenhack's init function.
231 doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
232 const struct function_table_entry *chosen,
233 jobject defaults, jint w, jint h, jobject jni_surface)
235 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
237 progname = chosen->progname;
238 rh->xsft = chosen->xsft;
239 rh->api = chosen->api;
241 rh->jobject = jwxyz_obj; // update this every time we call into C
243 (*env)->GetJavaVM (env, &global_jvm);
245 # undef ya_rand_init // This is the one and only place it is allowed
248 Window wnd = (Window) calloc(1, sizeof(*wnd));
250 wnd->frame.width = w;
251 wnd->frame.height = h;
255 progclass = rh->xsft->progclass;
257 if ((*env)->ExceptionOccurred(env)) abort();
259 // This has to come before resource processing. It does not do graphics.
260 if (rh->xsft->setup_cb)
261 rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);
263 if ((*env)->ExceptionOccurred(env)) abort();
265 // Load the defaults.
266 // Unceremoniously stolen from [PrefsReader defaultsToDict:].
268 jclass c = (*env)->GetObjectClass (env, defaults);
269 jmethodID m = (*env)->GetMethodID (env, c, "put",
270 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
272 if ((*env)->ExceptionOccurred(env)) abort();
274 const struct { const char *key, *val; } default_defaults[] = {
275 { "doubleBuffer", "True" },
276 { "multiSample", "False" },
277 { "texFontCacheSize", "30" },
278 { "textMode", "date" },
280 "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
281 { "grabDesktopImages", "True" },
282 { "chooseRandomImages", "True" },
285 for (int i = 0; i < countof(default_defaults); i++) {
286 const char *key = default_defaults[i].key;
287 const char *val = default_defaults[i].val;
288 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
289 strcpy (key2, progname);
293 // defaults.put(key2, val);
294 jstring jkey = (*env)->NewStringUTF (env, key2);
295 jstring jval = (*env)->NewStringUTF (env, val);
296 (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
297 (*env)->DeleteLocalRef (env, jkey);
298 (*env)->DeleteLocalRef (env, jval);
299 // Log ("default0: \"%s\" = \"%s\"", key2, val);
303 const char *const *defs = rh->xsft->defaults;
305 char *line = strdup (*defs);
308 while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
311 while (*val && *val != ':')
313 if (*val != ':') abort();
315 while (*val == ' ' || *val == '\t')
318 unsigned long L = strlen(val);
319 while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
322 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
323 strcpy (key2, progname);
327 // defaults.put(key2, val);
328 jstring jkey = (*env)->NewStringUTF (env, key2);
329 jstring jval = (*env)->NewStringUTF (env, val);
330 (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
331 (*env)->DeleteLocalRef (env, jkey);
332 (*env)->DeleteLocalRef (env, jval);
333 // Log ("default: \"%s\" = \"%s\"", key2, val);
339 (*env)->DeleteLocalRef (env, c);
340 if ((*env)->ExceptionOccurred(env)) abort();
343 /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */
345 /* TODO: This is lame, use a resource. */
347 strcmp (progname, "kumppa") &&
348 strcmp (progname, "petri") &&
349 strcmp (progname, "slip") &&
350 strcmp (progname, "testx11");
352 Log ("init: %s @ %dx%d: using JWXYZ_%s", progname, w, h,
353 rh->jwxyz_gl_p ? "GL" : "IMAGE");
355 if (rh->jwxyz_gl_p) {
356 // GL init. Must come after resource processing.
358 rh->egl_display = eglGetDisplay (EGL_DEFAULT_DISPLAY);
359 Assert (rh->egl_display != EGL_NO_DISPLAY, "init: EGL_NO_DISPLAY");
361 int egl_major, egl_minor;
362 Assert (eglInitialize (rh->egl_display, &egl_major, &egl_minor),
363 "eglInitialize failed");
365 // TODO: Skip depth and (probably) alpha for Xlib.
366 // TODO: Could ask for EGL_SWAP_BEHAVIOR_PRESERVED_BIT here...maybe?
367 // TODO: Probably should try to ask for EGL_PBUFFER_BIT.
368 // TODO: Do like visual-gl.c and work from a list of configs.
369 /* Probably don't need EGL_FRAMEBUFFER_TARGET_ANDROID here if GLSurfaceView
372 EGLint config_attribs[] = {
382 Assert (eglChooseConfig (rh->egl_display, config_attribs,
383 &rh->egl_config, 1, &num_config),
384 "eglChooseConfig failed");
385 Assert (num_config == 1, "no EGL config chosen");
387 EGLint no_attribs[] = {EGL_NONE};
388 rh->egl_ctx = eglCreateContext (rh->egl_display, rh->egl_config,
389 EGL_NO_CONTEXT, no_attribs);
390 Assert (rh->egl_ctx != EGL_NO_CONTEXT, "init: EGL_NO_CONTEXT");
392 ANativeWindow *native_window =
393 ANativeWindow_fromSurface (env, jni_surface);
395 rh->egl_surface = eglCreateWindowSurface (rh->egl_display, rh->egl_config,
396 native_window, no_attribs);
397 Assert (rh->egl_surface != EGL_NO_SURFACE, "init: EGL_NO_SURFACE");
399 ANativeWindow_release (native_window);
401 prepare_context (rh);
403 Log ("init %s / %s / %s",
404 glGetString (GL_VENDOR),
405 glGetString (GL_RENDERER),
406 glGetString (GL_VERSION));
408 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
409 rh->gl_fbo_p = jwzgles_gluCheckExtension (
410 (const GLubyte *)"GL_OES_framebuffer_object", extensions);
413 PFNGLGENFRAMEBUFFERSOESPROC glGenFramebuffersOES =
414 (PFNGLGENFRAMEBUFFERSOESPROC)
415 eglGetProcAddress ("glGenFramebuffersOES");
417 rh->glBindFramebufferOES = (PFNGLBINDFRAMEBUFFEROESPROC)
418 eglGetProcAddress ("glBindFramebufferOES");
419 rh->glFramebufferTexture2DOES = (PFNGLFRAMEBUFFERTEXTURE2DOESPROC)
420 eglGetProcAddress ("glFramebufferTexture2DOES");
422 glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default);
423 Assert (!rh->fb_default, "default framebuffer not current framebuffer");
424 glGenFramebuffersOES (1, &rh->fb_pixmap);
427 wnd->egl_surface = rh->egl_surface;
430 rh->frontbuffer_p = False;
432 if (rh->api == API_XLIB ||
433 (rh->api == API_GL &&
434 strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
436 rh->frontbuffer_p = True;
438 # if 0 /* Might need to be 0 for Adreno...? */
439 if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
441 eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
443 if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
444 eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR,
445 EGL_BUFFER_PRESERVED);
446 rh->frontbuffer_p = False;
451 if (rh->frontbuffer_p) {
452 /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
453 create_pixmap (wnd, wnd);
455 /* No preserving backbuffers, so manual blit from back to "front". */
456 rh->frontbuffer.type = PIXMAP;
457 rh->frontbuffer.frame = wnd->frame;
458 rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL);
461 rh->frontbuffer.texture = 0;
463 Assert (wnd->egl_surface != rh->egl_surface,
464 "oops: wnd->egl_surface == rh->egl_surface");
465 rh->frontbuffer.egl_surface = rh->egl_surface;
470 rh->dpy = jwxyz_gl_make_display(wnd);
474 rh->native_window = ANativeWindow_fromSurface (env, jni_surface);
476 int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
477 WINDOW_FORMAT_RGBX_8888);
479 // Maybe check this earlier?
480 Log ("can't set format (%d), surface may be invalid.", result);
481 (*env)->ThrowNew (env,
482 (*env)->FindClass(env, "org/jwz/xscreensaver/jwxyz$SurfaceLost"),
485 ANativeWindow_release (rh->native_window);
486 rh->native_window = NULL;
490 create_pixmap (wnd, wnd);
492 static const unsigned char rgba_bytes[] = {0, 1, 2, 3};
493 rh->dpy = jwxyz_image_make_display(wnd, rgba_bytes);
497 Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
498 // TODO: Zero looks right, but double-check that is the right number
500 /* Requires valid rh->dpy. */
502 rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
504 rh->gles_state = jwzgles_make_state();
517 # ifdef GETTIMEOFDAY_TWO_ARGS
519 gettimeofday(&now, &tzp);
524 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
529 // Animates a single frame of the current hack.
532 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
535 double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
536 fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
539 unsigned long delay = 0;
541 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
543 Window wnd = rh->window;
545 if (rh->jwxyz_gl_p) {
546 prepare_context (rh);
548 /* There is some kind of weird redisplay race condition between Settings
549 and the launching hack: e.g., Abstractile does XClearWindow at init,
550 but the screen is getting filled with random bits. So let's wait a
551 few frames before really starting up.
553 TODO: Is this still true?
555 if (++rh->frame_count < 8) {
556 /* glClearColor (1.0, 0.0, 1.0, 0.0); */
557 glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */
563 fps1 = double_time();
566 // The init function might do graphics (e.g. XClearWindow) so it has
567 // to be run from inside onDrawFrame, not onSurfaceChanged.
569 if (! rh->initted_p) {
571 void *(*init_cb) (Display *, Window, void *) =
572 (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
575 get_pixel_resource (rh->dpy, 0, "background", "Background");
576 XSetWindowBackground (rh->dpy, wnd, bg);
577 XClearWindow (rh->dpy, wnd);
579 rh->closure = init_cb (rh->dpy, wnd, rh->xsft->setup_arg);
580 rh->initted_p = True;
582 /* ignore_rotation_p doesn't quite work at the moment. */
583 rh->ignore_rotation_p = False;
585 (rh->api == API_XLIB &&
586 get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
589 if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
590 rh->fpst = fps_init (rh->dpy, wnd);
591 if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
594 rh->xsft->fps_cb = 0;
597 if ((*env)->ExceptionOccurred(env)) abort();
601 fps2 = double_time();
604 // Apparently events don't come in on the drawing thread, and JNI flips
605 // out. So we queue them there and run them here.
606 // TODO: Events should be coming in on the drawing thread now, so dump this.
607 send_queued_events (rh);
610 fps3 = double_time();
613 delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure);
616 jwxyz_gl_flush (rh->dpy);
619 fps4 = double_time();
621 if (rh->fpst && rh->xsft->fps_cb)
622 rh->xsft->fps_cb (rh->dpy, wnd, rh->fpst, rh->closure);
624 if (rh->jwxyz_gl_p) {
625 if (rh->frontbuffer_p) {
626 jwxyz_gl_copy_area (rh->dpy, wnd, &rh->frontbuffer, rh->copy_gc,
627 0, 0, wnd->frame.width, wnd->frame.height,
631 // TODO: Getting crashes here after resize, sometimes. Log sez:
632 // W/Adreno-EGLSUB(18428): <DequeueBuffer:607>: dequeue native buffer fail: No such device, buffer=0x5f93bf5c, handle=0x0
633 Assert (eglSwapBuffers(rh->egl_display, rh->egl_surface),
634 "eglSwapBuffers failed");
636 ANativeWindow_Buffer buffer;
637 ARect rect = {0, 0, wnd->frame.width, wnd->frame.height};
638 int32_t result = ANativeWindow_lock(rh->native_window, &buffer, &rect);
640 Log ("ANativeWindow_lock failed (result = %d), frame dropped", result);
642 /* Android can resize surfaces asynchronously. */
643 if (wnd->frame.width != buffer.width ||
644 wnd->frame.height != buffer.height) {
645 Log ("buffer/window size mismatch: %dx%d (format = %d), wnd: %dx%d",
646 buffer.width, buffer.height, buffer.format,
647 wnd->frame.width, wnd->frame.height);
650 Assert (buffer.format == WINDOW_FORMAT_RGBA_8888 ||
651 buffer.format == WINDOW_FORMAT_RGBX_8888,
652 "bad buffer format");
654 jwxyz_blit (wnd->image_data, jwxyz_image_pitch (wnd), 0, 0,
655 buffer.bits, buffer.stride * 4, 0, 0,
656 MIN(wnd->frame.width, buffer.width),
657 MIN(wnd->frame.height, buffer.height));
658 // TODO: Clear any area to sides and bottom.
660 ANativeWindow_unlockAndPost (rh->native_window);
667 Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
668 (int) ((fps1-fps0)*1000000),
669 (int) ((fps2-fps1)*1000000),
670 (int) ((fps3-fps2)*1000000),
671 (int) ((fps4-fps3)*1000000),
672 (int) ((double_time()-fps4)*1000000));
679 // Extracts the C structure that is stored in the jwxyz Java object.
680 static struct running_hack *
681 getRunningHack (JNIEnv *env, jobject thiz)
683 jlong result = (*env)->GetLongField (env, thiz, runningHackField);
684 struct running_hack *rh = (struct running_hack *)(intptr_t)result;
686 rh->jobject = thiz; // update this every time we call into C
690 // Look up a class and mark it global in the provided variable.
692 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
694 jclass clazz = (*env)->FindClass(env, className);
695 *globalRef = (*env)->NewGlobalRef(env, clazz);
700 /* Note: to find signature strings for native methods:
701 cd ./project/xscreensaver/build/intermediates/classes/debug/
702 javap -s -p org.jwz.xscreensaver.jwxyz
706 // Implementation of jwxyz's nativeInit Java method.
708 JNIEXPORT void JNICALL
709 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
710 jstring jhack, jobject defaults,
714 pthread_mutex_lock(&mutg);
716 struct running_hack *rh = calloc(1, sizeof(struct running_hack));
718 if ((*env)->ExceptionOccurred(env)) abort();
721 if (!classRefCount) {
722 jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
723 globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
724 runningHackField = (*env)->GetFieldID
725 (env, classjwxyz, "nativeRunningHackPtr", "J");
726 if ((*env)->ExceptionOccurred(env)) abort();
728 jclass classIterable =
729 acquireClass(env, "java/lang/Iterable", &globalRefIterable);
730 iterableIterator = (*env)->GetMethodID
731 (env, classIterable, "iterator", "()Ljava/util/Iterator;");
732 if ((*env)->ExceptionOccurred(env)) abort();
734 jclass classIterator =
735 acquireClass(env, "java/util/Iterator", &globalRefIterator);
736 iteratorHasNext = (*env)->GetMethodID
737 (env, classIterator, "hasNext", "()Z");
738 iteratorNext = (*env)->GetMethodID
739 (env, classIterator, "next", "()Ljava/lang/Object;");
740 if ((*env)->ExceptionOccurred(env)) abort();
742 jclass classMapEntry =
743 acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
744 entryGetKey = (*env)->GetMethodID
745 (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
746 entryGetValue = (*env)->GetMethodID
747 (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
748 if ((*env)->ExceptionOccurred(env)) abort();
753 // Store the C struct into the Java object.
754 (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
756 // TODO: Sort the list so binary search works.
757 const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
761 if (chosen == countof(function_table)) {
762 Log ("Hack not found: %s", hack);
765 if (!strcmp(function_table[chosen].progname, hack))
770 (*env)->ReleaseStringUTFChars(env, jhack, hack);
772 doinit (thiz, rh, env, &function_table[chosen], defaults, w, h,
775 pthread_mutex_unlock(&mutg);
779 JNIEXPORT void JNICALL
780 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
781 jint w, jint h, jdouble rot)
783 pthread_mutex_lock(&mutg);
784 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
786 current_rotation = rot;
788 Log ("native rotation: %f", current_rotation);
790 struct running_hack *rh = getRunningHack(env, thiz);
792 if (rh->jwxyz_gl_p) {
793 prepare_context (rh);
794 glViewport (0, 0, w, h);
796 int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
797 WINDOW_FORMAT_RGBX_8888);
799 Log ("failed to resize surface (%d)", result);
802 Window wnd = rh->window;
805 wnd->frame.width = w;
806 wnd->frame.height = h;
808 if (ignore_rotation_p(rh->dpy) &&
809 rot != 0 && rot != 180 && rot != -180) {
813 wnd->frame.width = w;
814 wnd->frame.height = h;
817 if (rh->jwxyz_gl_p) {
818 if (rh->frontbuffer_p) {
819 free_pixmap (rh, wnd);
820 create_pixmap (wnd, wnd);
822 rh->frontbuffer.frame = wnd->frame;
824 rh->frontbuffer.egl_surface = rh->egl_surface;
827 jwxyz_window_resized (rh->dpy);
829 free_pixmap (rh, wnd);
830 create_pixmap (wnd, wnd);
833 XClearWindow (rh->dpy, wnd); // TODO: This is lame.
836 rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
837 wnd->frame.width, wnd->frame.height);
839 if (rh->jwxyz_gl_p && rh->api == API_GL) {
840 glMatrixMode (GL_PROJECTION);
841 glRotatef (-rot, 0, 0, 1);
842 glMatrixMode (GL_MODELVIEW);
846 pthread_mutex_unlock(&mutg);
850 JNIEXPORT jlong JNICALL
851 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
853 pthread_mutex_lock(&mutg);
854 struct running_hack *rh = getRunningHack(env, thiz);
855 jlong result = drawXScreenSaver(env, rh);
856 pthread_mutex_unlock(&mutg);
861 // TODO: Check Java side is calling this properly
862 JNIEXPORT void JNICALL
863 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
865 pthread_mutex_lock(&mutg);
866 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
868 struct running_hack *rh = getRunningHack(env, thiz);
870 prepare_context (rh);
873 rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
875 XFreeGC (rh->dpy, rh->copy_gc);
876 jwzgles_free_state ();
878 if (rh->jwxyz_gl_p) {
879 jwxyz_gl_free_display(rh->dpy);
880 // eglDestroy* probably isn't necessary here.
881 eglMakeCurrent (rh->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
883 eglDestroySurface (rh->egl_display, rh->egl_surface);
884 eglDestroyContext (rh->egl_display, rh->egl_ctx);
885 eglTerminate (rh->egl_display);
887 jwxyz_image_free_display(rh->dpy);
888 if (rh->native_window)
889 ANativeWindow_release (rh->native_window);
893 (*env)->SetLongField(env, thiz, runningHackField, 0);
896 if (!classRefCount) {
897 (*env)->DeleteGlobalRef(env, globalRefjwxyz);
898 (*env)->DeleteGlobalRef(env, globalRefIterable);
899 (*env)->DeleteGlobalRef(env, globalRefIterator);
900 (*env)->DeleteGlobalRef(env, globalRefMapEntry);
904 pthread_mutex_unlock(&mutg);
909 send_event (struct running_hack *rh, XEvent *e)
911 // Assumes mutex is locked and context is prepared
913 int *xP = 0, *yP = 0;
914 switch (e->xany.type) {
915 case ButtonPress: case ButtonRelease:
925 // Rotate the coordinates in the events to match the pixels.
927 if (ignore_rotation_p (rh->dpy)) {
928 Window win = XRootWindow (rh->dpy, 0);
929 int w = win->frame.width;
930 int h = win->frame.height;
932 switch ((int) current_rotation) {
933 case 180: case -180: // #### untested
938 swap = *xP; *xP = *yP; *yP = swap;
941 case -90: case 270: // #### untested
942 swap = *xP; *xP = *yP; *yP = swap;
948 rh->window->window.last_mouse_x = *xP;
949 rh->window->window.last_mouse_y = *yP;
952 return (rh->xsft->event_cb
953 ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
959 send_queued_events (struct running_hack *rh)
961 struct event_queue *event, *next;
962 if (! rh->event_queue) return;
963 for (event = rh->event_queue, next = event->next;
965 event = next, next = (event ? event->next : 0)) {
966 if (! send_event (rh, &event->event)) {
967 // #### flash the screen or something
976 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
978 pthread_mutex_lock (&mutg);
979 struct running_hack *rh = getRunningHack (env, thiz);
980 struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
981 memcpy (&q->event, e, sizeof(*e));
984 // Put it at the end.
985 struct event_queue *oq;
986 for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
993 pthread_mutex_unlock (&mutg);
997 JNIEXPORT void JNICALL
998 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
999 int x, int y, jboolean down)
1002 memset (&e, 0, sizeof(e));
1003 e.xany.type = (down ? ButtonPress : ButtonRelease);
1004 e.xbutton.button = Button1;
1007 queue_event (env, thiz, &e);
1010 JNIEXPORT void JNICALL
1011 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
1015 memset (&e, 0, sizeof(e));
1016 e.xany.type = MotionNotify;
1019 queue_event (env, thiz, &e);
1022 JNIEXPORT void JNICALL
1023 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
1028 memset (&e, 0, sizeof(e));
1029 e.xkey.keycode = code;
1030 e.xkey.state = code;
1031 e.xany.type = (down_p ? KeyPress : KeyRelease);
1032 queue_event (env, thiz, &e);
1033 e.xany.type = KeyRelease;
1034 queue_event (env, thiz, &e);
1038 /***************************************************************************
1039 Backend functions for jwxyz-gl.c
1043 finish_bind_drawable (Display *dpy, Drawable dst)
1047 glViewport (0, 0, dst->frame.width, dst->frame.height);
1048 jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False);
1053 bind_drawable_fbo (struct running_hack *rh, Drawable d)
1055 rh->glBindFramebufferOES (GL_FRAMEBUFFER_OES,
1056 d->texture ? rh->fb_pixmap : rh->fb_default);
1058 rh->glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
1059 GL_TEXTURE_2D, d->texture, 0);
1065 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
1067 struct running_hack *rh = w->window.rh;
1068 JNIEnv *env = w->window.rh->jni_env;
1069 if ((*env)->ExceptionOccurred(env)) abort();
1070 if (rh->current_drawable != d) {
1072 bind_drawable_fbo (rh, d);
1074 eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx);
1076 finish_bind_drawable (dpy, d);
1077 rh->current_drawable = d;
1082 jwxyz_gl_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
1083 int src_x, int src_y,
1084 unsigned int width, unsigned int height,
1085 int dst_x, int dst_y)
1087 Window w = XRootWindow (dpy, 0);
1088 struct running_hack *rh = w->window.rh;
1090 jwxyz_gl_flush (dpy);
1092 if (rh->gl_fbo_p && src->texture && src != dst) {
1093 bind_drawable_fbo (rh, dst);
1094 finish_bind_drawable (dpy, dst);
1095 rh->current_drawable = NULL;
1097 jwxyz_gl_set_gc (dpy, gc);
1099 glBindTexture (GL_TEXTURE_2D, src->texture);
1101 jwxyz_gl_draw_image (GL_TEXTURE_2D, to_pow2(src->frame.width),
1102 to_pow2(src->frame.height),
1103 src_x, src->frame.height - src_y - height,
1104 width, height, dst_x, dst_y);
1110 // Hilarious display corruption ahoy! (Note to self: it's on the emulator.)
1111 // TODO for Dave: Recheck behavior on the emulator with the better Pixmap support.
1113 rh->current_drawable = NULL;
1115 bind_drawable_fbo (rh, src);
1117 eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx);
1119 jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y,
1120 width, height, dst_x, dst_y);
1123 bind_drawable_fbo (rh, dst);
1124 finish_bind_drawable (dpy, dst);
1126 jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y, width, height,
1131 jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
1132 width, height, dst_x, dst_y);
1139 jwxyz_assert_drawable (Window main_window, Drawable d)
1141 check_gl_error("jwxyz_assert_drawable");
1146 jwxyz_assert_gl (void)
1148 check_gl_error("jwxyz_assert_gl");
1152 /***************************************************************************
1153 Backend functions for jwxyz-image.c
1157 jwxyz_image_pitch (Drawable d)
1159 return d->frame.width * 4;
1163 jwxyz_image_data (Drawable d)
1165 return d->image_data;
1170 jwxyz_frame (Drawable d)
1177 jwxyz_drawable_depth (Drawable d)
1179 return (d->type == WINDOW
1180 ? visual_depth (NULL, NULL)
1186 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
1192 xp->x = w->window.last_mouse_x;
1193 xp->y = w->window.last_mouse_y;
1199 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
1201 fps_compute (fpst, 0, -1);
1207 XCreatePixmap (Display *dpy, Drawable d,
1208 unsigned int width, unsigned int height, unsigned int depth)
1210 Window win = XRootWindow(dpy, 0);
1212 Pixmap p = malloc(sizeof(*p));
1216 p->frame.width = width;
1217 p->frame.height = height;
1219 Assert(depth == 1 || depth == visual_depth(NULL, NULL),
1220 "XCreatePixmap: bad depth");
1221 p->pixmap.depth = depth;
1223 create_pixmap (win, p);
1225 /* For debugging. */
1227 jwxyz_bind_drawable (dpy, win, p);
1228 glClearColor (frand(1), frand(1), frand(1), 0);
1229 glClear (GL_COLOR_BUFFER_BIT);
1237 XFreePixmap (Display *d, Pixmap p)
1239 struct running_hack *rh = XRootWindow(d, 0)->window.rh;
1241 if (rh->jwxyz_gl_p) {
1244 if (rh->current_drawable == p)
1245 rh->current_drawable = NULL;
1248 free_pixmap (rh, p);
1255 current_device_rotation (void)
1257 return current_rotation;
1261 ignore_rotation_p (Display *dpy)
1263 struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
1264 return rh->ignore_rotation_p;
1269 jstring_dup (JNIEnv *env, jstring str)
1271 Assert (str, "expected jstring, not null");
1272 const char *cstr = (*env)->GetStringUTFChars (env, str, 0);
1273 size_t len = (*env)->GetStringUTFLength (env, str) + 1;
1274 char *result = malloc (len);
1276 memcpy (result, cstr, len);
1278 (*env)->ReleaseStringUTFChars (env, str, cstr);
1284 get_string_resource_window (Window window, char *name)
1286 JNIEnv *env = window->window.rh->jni_env;
1287 jobject obj = window->window.rh->jobject;
1289 if ((*env)->ExceptionOccurred(env)) abort();
1290 jstring jstr = (*env)->NewStringUTF (env, name);
1291 jclass c = (*env)->GetObjectClass (env, obj);
1292 jmethodID m = (*env)->GetMethodID (env, c, "getStringResource",
1293 "(Ljava/lang/String;)Ljava/lang/String;");
1294 if ((*env)->ExceptionOccurred(env)) abort();
1297 ? (*env)->CallObjectMethod (env, obj, m, jstr)
1299 (*env)->DeleteLocalRef (env, c);
1300 (*env)->DeleteLocalRef (env, jstr);
1303 ret = jstring_dup (env, jvalue);
1305 Log("pref %s = %s", name, (ret ? ret : "(null)"));
1311 get_string_resource (Display *dpy, char *name, char *class)
1313 return get_string_resource_window (RootWindow (dpy, 0), name);
1317 /* Returns the contents of the URL. */
1319 textclient_mobile_url_string (Display *dpy, const char *url)
1321 Window window = RootWindow (dpy, 0);
1322 JNIEnv *env = window->window.rh->jni_env;
1323 jobject obj = window->window.rh->jobject;
1325 jstring jstr = (*env)->NewStringUTF (env, url);
1326 jclass c = (*env)->GetObjectClass (env, obj);
1327 jmethodID m = (*env)->GetMethodID (env, c, "loadURL",
1328 "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
1329 if ((*env)->ExceptionOccurred(env)) abort();
1331 ? (*env)->CallObjectMethod (env, obj, m, jstr)
1333 (*env)->DeleteLocalRef (env, c);
1334 (*env)->DeleteLocalRef (env, jstr);
1336 char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1339 int L = (*env)->GetDirectBufferCapacity (env, buf);
1340 body2 = malloc (L + 1);
1341 memcpy (body2, body, L);
1344 body2 = strdup ("ERROR");
1348 (*env)->DeleteLocalRef (env, buf);
1355 jwxyz_scale (Window main_window)
1357 // TODO: Use the actual device resolution.
1363 jwxyz_default_font_family (int require)
1365 /* Font families in XLFDs are totally ignored (for now). */
1366 return "sans-serif";
1371 jwxyz_load_native_font (Window window,
1372 int traits_jwxyz, int mask_jwxyz,
1373 const char *font_name_ptr, size_t font_name_length,
1374 int font_name_type, float size,
1375 char **family_name_ret,
1376 int *ascent_ret, int *descent_ret)
1378 JNIEnv *env = window->window.rh->jni_env;
1379 jobject obj = window->window.rh->jobject;
1381 jstring jname = NULL;
1382 if (font_name_ptr) {
1383 char *name_nul = malloc(font_name_length + 1);
1384 memcpy(name_nul, font_name_ptr, font_name_length);
1385 name_nul[font_name_length] = 0;
1386 jname = (*env)->NewStringUTF (env, name_nul);
1390 jclass c = (*env)->GetObjectClass (env, obj);
1391 jmethodID m = (*env)->GetMethodID (env, c, "loadFont",
1392 "(IILjava/lang/String;IF)[Ljava/lang/Object;");
1393 if ((*env)->ExceptionOccurred(env)) abort();
1395 jobjectArray array = (m
1396 ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
1397 (jint)traits_jwxyz, jname,
1398 (jint)font_name_type, (jfloat)size)
1401 (*env)->DeleteLocalRef (env, c);
1404 jobject font = (*env)->GetObjectArrayElement (env, array, 0);
1405 jobject family_name =
1406 (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
1407 jobject asc = (*env)->GetObjectArrayElement (env, array, 2);
1408 jobject desc = (*env)->GetObjectArrayElement (env, array, 3);
1409 if ((*env)->ExceptionOccurred(env)) abort();
1411 if (family_name_ret)
1412 *family_name_ret = jstring_dup (env, family_name);
1414 jobject paint = (*env)->NewGlobalRef (env, font);
1415 if ((*env)->ExceptionOccurred(env)) abort();
1417 c = (*env)->GetObjectClass(env, asc);
1418 m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1419 if ((*env)->ExceptionOccurred(env)) abort();
1421 *ascent_ret = (int) (*env)->CallFloatMethod (env, asc, m);
1422 *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1424 return (void *) paint;
1432 jwxyz_release_native_font (Display *dpy, void *native_font)
1434 Window window = RootWindow (dpy, 0);
1435 JNIEnv *env = window->window.rh->jni_env;
1436 if ((*env)->ExceptionOccurred(env)) abort();
1437 (*env)->DeleteGlobalRef (env, (jobject) native_font);
1438 if ((*env)->ExceptionOccurred(env)) abort();
1442 /* If the local reference table fills up, use this to figure out where
1443 you missed a call to DeleteLocalRef. */
1445 static void dump_reference_tables(JNIEnv *env)
1447 jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1448 jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1450 (*env)->CallStaticVoidMethod (env, c, m);
1451 (*env)->DeleteLocalRef (env, c);
1456 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1457 // string. If pixmap_ret is provided, also renders the text.
1460 jwxyz_render_text (Display *dpy, void *native_font,
1461 const char *str, size_t len, Bool utf8, Bool antialias_p,
1462 XCharStruct *cs, char **pixmap_ret)
1464 Window window = RootWindow (dpy, 0);
1465 JNIEnv *env = window->window.rh->jni_env;
1466 jobject obj = window->window.rh->jobject;
1471 s2 = malloc (len + 1);
1472 memcpy (s2, str, len);
1474 } else { // Convert Latin1 to UTF8
1475 s2 = malloc (len * 2 + 1);
1476 unsigned char *s3 = (unsigned char *) s2;
1478 for (i = 0; i < len; i++) {
1479 unsigned char c = ((unsigned char *) str)[i];
1483 *s3++ = (0xC0 | (0x03 & (c >> 6)));
1484 *s3++ = (0x80 | (0x3F & c));
1490 jstring jstr = (*env)->NewStringUTF (env, s2);
1491 jclass c = (*env)->GetObjectClass (env, obj);
1492 jmethodID m = (*env)->GetMethodID (env, c, "renderText",
1493 "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;");
1494 if ((*env)->ExceptionOccurred(env)) abort();
1497 ? (*env)->CallObjectMethod (env, obj, m,
1498 (jobject) native_font,
1500 (pixmap_ret ? JNI_TRUE : JNI_FALSE),
1503 (*env)->DeleteLocalRef (env, c);
1504 (*env)->DeleteLocalRef (env, jstr);
1507 if ((*env)->ExceptionOccurred(env)) abort();
1508 unsigned char *bits = (unsigned char *)
1509 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1512 int L = (*env)->GetDirectBufferCapacity (env, buf);
1513 if (L < 10) abort();
1514 cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1515 cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1516 cs->width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1517 cs->ascent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1518 cs->descent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1521 char *pix = malloc (L - i);
1523 memcpy (pix, bits + i, L - i);
1527 memset (cs, 0, sizeof(*cs));
1533 (*env)->DeleteLocalRef (env, buf);
1538 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1540 JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1541 /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1543 c = (*env)->FindClass (env, "java/lang/Character"),
1544 c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
1546 if ((*env)->ExceptionOccurred(env)) abort();
1547 jmethodID m = (*env)->GetStaticMethodID (
1548 env, c, "getName", "(I)Ljava/lang/String;");
1549 jthrowable exc = (*env)->ExceptionOccurred(env);
1551 if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1552 (*env)->ExceptionClear (env);
1553 Assert (!m, "jwxyz_unicode_character_name: m?");
1562 jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1564 ret = jstring_dup (env, name);
1568 asprintf(&ret, "U+%.4lX", uc);
1575 /* Called from utils/grabclient.c */
1577 jwxyz_load_random_image (Display *dpy,
1578 int *width_ret, int *height_ret,
1582 /* TODO: This function needs to be implemented for Android */
1585 Window window = RootWindow (dpy, 0);
1586 struct running_hack *rh = window->window.rh;
1587 JNIEnv *env = rh->jni_env;
1588 jobject obj = rh->jobject;
1591 get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1593 get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1595 get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1597 if (!images_p && !grab_p)
1600 if (grab_p && images_p) {
1601 grab_p = !(random() & 5); /* if both, screenshot 1/5th of the time */
1605 jclass c = (*env)->GetObjectClass (env, obj);
1606 jmethodID m = (*env)->GetMethodID (env, c,
1609 : "loadRandomImage"),
1610 "(IIZ)Ljava/nio/ByteBuffer;");
1611 if ((*env)->ExceptionOccurred(env)) abort();
1613 ? (*env)->CallObjectMethod (env, obj, m,
1614 window->frame.width,
1615 window->frame.height,
1616 (rotate_p ? JNI_TRUE : JNI_FALSE))
1618 (*env)->DeleteLocalRef (env, c);
1620 unsigned char *bits = (unsigned char *)
1621 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1625 int L = (*env)->GetDirectBufferCapacity (env, buf);
1626 if (L < 100) abort();
1627 int width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1628 int height = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1629 char *name = (char *) bits + i;
1630 int L2 = strlen (name);
1632 if (width * height * 4 != L - i) abort();
1633 char *pix = malloc (L - i);
1635 memcpy (pix, bits + i, L - i);
1637 *height_ret = height;
1638 *name_ret = strdup (name);
1639 return (char *) pix;
1643 (*env)->DeleteLocalRef (env, buf);
1648 #endif /* HAVE_ANDROID */