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.
21 #ifdef HAVE_ANDROID /* whole file */
32 #include <android/log.h>
35 #include "screenhackI.h"
38 #include "jwxyz-android.h"
39 #include "textclient.h"
40 #include "grabscreen.h"
44 #define countof(x) (sizeof(x)/sizeof(*(x)))
46 extern struct xscreensaver_function_table *xscreensaver_function_table;
48 struct function_table_entry {
50 struct xscreensaver_function_table *xsft;
54 #include "gen/function-table.h"
58 struct event_queue *next;
61 static void send_queued_events (struct running_hack *rh);
64 const char *progclass;
67 static JavaVM *global_jvm;
68 static jmp_buf jmp_target;
70 static double current_rotation = 0;
72 extern void check_gl_error (const char *type);
75 jwxyz_logv(Bool error, const char *fmt, va_list args)
77 __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
78 "xscreensaver", fmt, args);
80 /* The idea here is that if the device/emulator dies shortly after a log
81 message, then waiting here for a short while should increase the odds
82 that adb logcat can pick up the message before everything blows up. Of
83 course, doing this means dumping a ton of messages will slow things down
89 ts.tv_nsec = 25 * 1000000;
94 /* Handle an abort on Android
95 TODO: Test that Android handles aborts properly
98 jwxyz_abort (const char *fmt, ...)
100 /* Send error to Android device log */
105 va_start (args, fmt);
106 jwxyz_logv(True, fmt, args);
110 va_start (args, fmt);
111 vsprintf (buf, fmt, args);
115 (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
117 if (! (*env)->ExceptionOccurred(env)) {
118 // If there's already an exception queued, let's just go with that one.
119 // Else, queue a Java exception to be thrown.
120 (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"),
124 // Nonlocal exit out of the jwxyz code.
125 longjmp (jmp_target, 1);
129 /* We get to keep live references to Java classes in use because the VM can
130 unload a class that isn't being used, which invalidates field and method
132 https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
136 // #### only need one var I think
137 static size_t classRefCount = 0;
138 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
141 static jfieldID runningHackField;
142 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
143 static jmethodID entryGetKey, entryGetValue;
145 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
147 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
148 static char *get_string_resource_window (Window window, char *name);
151 /* Also creates double-buffered windows. */
153 create_pixmap (Window win, Drawable p)
156 // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
157 // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
158 // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
160 Assert (p->frame.width, "p->frame.width");
161 Assert (p->frame.height, "p->frame.height");
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");
192 free_pixmap (struct running_hack *rh, Pixmap p)
195 glDeleteTextures (1, &p->texture);
197 eglDestroySurface(rh->egl_display, p->egl_surface);
203 restore_surface (struct running_hack *rh)
205 /* This is necessary because GLSurfaceView (in Java) will call
206 eglSwapBuffers under the (ordinarily reasonable) assumption that the EGL
207 surface associated with the EGL context hasn't been changed.
209 eglMakeCurrent (rh->egl_display, rh->egl_surface, rh->egl_surface,
211 rh->current_drawable = NULL;
216 get_egl_surface (struct running_hack *rh)
218 rh->egl_surface = eglGetCurrentSurface(EGL_DRAW);
219 Assert(eglGetCurrentSurface(EGL_READ) == rh->egl_surface,
220 "doinit: EGL_READ != EGL_DRAW");
224 // Initialized OpenGL and runs the screenhack's init function.
227 doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
228 const struct function_table_entry *chosen,
229 jobject defaults, jint w, jint h)
231 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
233 progname = chosen->progname;
234 rh->xsft = chosen->xsft;
235 rh->api = chosen->api;
237 rh->jobject = jwxyz_obj; // update this every time we call into C
239 (*env)->GetJavaVM (env, &global_jvm);
241 # undef ya_rand_init // This is the one and only place it is allowed
244 Window wnd = (Window) calloc(1, sizeof(*wnd));
246 wnd->frame.width = w;
247 wnd->frame.height = h;
251 progclass = rh->xsft->progclass;
253 if ((*env)->ExceptionOccurred(env)) abort();
255 // This has to come before resource processing. It does not do graphics.
256 if (rh->xsft->setup_cb)
257 rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);
259 if ((*env)->ExceptionOccurred(env)) abort();
261 // Load the defaults.
262 // Unceremoniously stolen from [PrefsReader defaultsToDict:].
264 jclass c = (*env)->GetObjectClass (env, defaults);
265 jmethodID m = (*env)->GetMethodID (env, c, "put",
266 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
268 if ((*env)->ExceptionOccurred(env)) abort();
270 const struct { const char *key, *val; } default_defaults[] = {
271 { "doubleBuffer", "True" },
272 { "multiSample", "False" },
273 { "texFontCacheSize", "30" },
274 { "textMode", "date" },
276 "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
277 { "grabDesktopImages", "True" },
278 { "chooseRandomImages", "True" },
281 for (int i = 0; i < countof(default_defaults); i++) {
282 const char *key = default_defaults[i].key;
283 const char *val = default_defaults[i].val;
284 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
285 strcpy (key2, progname);
289 // defaults.put(key2, val);
290 jstring jkey = (*env)->NewStringUTF (env, key2);
291 jstring jval = (*env)->NewStringUTF (env, val);
292 (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
293 (*env)->DeleteLocalRef (env, jkey);
294 (*env)->DeleteLocalRef (env, jval);
295 // Log ("default0: \"%s\" = \"%s\"", key2, val);
299 const char *const *defs = rh->xsft->defaults;
301 char *line = strdup (*defs);
304 while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
307 while (*val && *val != ':')
309 if (*val != ':') abort();
311 while (*val == ' ' || *val == '\t')
314 unsigned long L = strlen(val);
315 while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
318 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
319 strcpy (key2, progname);
323 // defaults.put(key2, val);
324 jstring jkey = (*env)->NewStringUTF (env, key2);
325 jstring jval = (*env)->NewStringUTF (env, val);
326 (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
327 (*env)->DeleteLocalRef (env, jkey);
328 (*env)->DeleteLocalRef (env, jval);
329 // Log ("default: \"%s\" = \"%s\"", key2, val);
335 (*env)->DeleteLocalRef (env, c);
336 if ((*env)->ExceptionOccurred(env)) abort();
338 // GL init. Must come after resource processing.
340 rh->egl_ctx = eglGetCurrentContext();
341 Assert(rh->egl_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
343 get_egl_surface (rh);
345 rh->egl_display = eglGetCurrentDisplay();
346 Assert(rh->egl_display != EGL_NO_DISPLAY, "doinit: EGL_NO_DISPLAY");
348 unsigned egl_major, egl_minor;
349 if (sscanf ((const char *)eglQueryString(rh->egl_display, EGL_VERSION),
350 "%u.%u", &egl_major, &egl_minor) < 2)
356 EGLint config_attribs[3];
357 config_attribs[0] = EGL_CONFIG_ID;
358 eglQueryContext(rh->egl_display, rh->egl_ctx, EGL_CONFIG_ID,
360 config_attribs[2] = EGL_NONE;
363 eglChooseConfig(rh->egl_display, config_attribs,
364 &rh->egl_config, 1, &num_config);
365 Assert(num_config == 1, "no EGL config chosen");
367 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
368 rh->gl_fbo_p = jwzgles_gluCheckExtension (
369 (const GLubyte *)"GL_OES_framebuffer_object", extensions);
372 PFNGLGENFRAMEBUFFERSOESPROC glGenFramebuffersOES =
373 (PFNGLGENFRAMEBUFFERSOESPROC)
374 eglGetProcAddress ("glGenFramebuffersOES");
376 rh->glBindFramebufferOES = (PFNGLBINDFRAMEBUFFEROESPROC)
377 eglGetProcAddress ("glBindFramebufferOES");
378 rh->glFramebufferTexture2DOES = (PFNGLFRAMEBUFFERTEXTURE2DOESPROC)
379 eglGetProcAddress ("glFramebufferTexture2DOES");
381 glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default);
382 Assert (!rh->fb_default, "default framebuffer not current framebuffer");
383 glGenFramebuffersOES (1, &rh->fb_pixmap);
386 wnd->egl_surface = rh->egl_surface;
389 /* TODO: Maybe ask for EGL_SWAP_BEHAVIOR_PRESERVED_BIT on the Java side of
390 things via EGLConfigChooser. I (Dave) seem to automatically get
391 this (n = 1), but maybe other devices won't.
393 rh->frontbuffer_p = False;
395 if (rh->api == API_XLIB ||
396 (rh->api == API_GL &&
397 strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
399 rh->frontbuffer_p = True;
401 # if 0 /* Might need to be 0 for Adreno...? */
402 if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
404 eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
406 if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
407 eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR,
408 EGL_BUFFER_PRESERVED);
409 rh->frontbuffer_p = False;
414 if (rh->frontbuffer_p) {
415 /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
416 create_pixmap (wnd, wnd);
418 /* No preserving backbuffers, so manual blit from back to "front". */
419 rh->frontbuffer.type = PIXMAP;
420 rh->frontbuffer.frame = wnd->frame;
421 rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL);
424 rh->frontbuffer.texture = 0;
426 Assert (wnd->egl_surface != rh->egl_surface,
427 "oops: wnd->egl_surface == rh->egl_surface");
428 rh->frontbuffer.egl_surface = rh->egl_surface;
433 rh->dpy = jwxyz_make_display(wnd);
434 Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
435 // TODO: Zero looks right, but double-check that is the right number
437 /* Requires valid rh->dpy. */
438 rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
440 rh->gles_state = jwzgles_make_state();
454 # ifdef GETTIMEOFDAY_TWO_ARGS
456 gettimeofday(&now, &tzp);
461 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
466 // Animates a single frame of the current hack.
469 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
472 double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
473 fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
476 unsigned long delay = 0;
478 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
480 /* There is some kind of weird redisplay race condition between Settings
481 and the launching hack: e.g., Abstractile does XClearWindow at init,
482 but the screen is getting filled with random bits. So let's wait a
483 few frames before really starting up.
485 if (++rh->frame_count < 8) {
486 /* glClearColor (1.0, 0.0, 1.0, 0.0); */
487 glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */
494 fps1 = double_time();
497 // The init function might do graphics (e.g. XClearWindow) so it has
498 // to be run from inside onDrawFrame, not onSurfaceChanged.
500 if (! rh->initted_p) {
502 void *(*init_cb) (Display *, Window, void *) =
503 (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
506 get_pixel_resource (rh->dpy, 0, "background", "Background");
507 XSetWindowBackground (rh->dpy, rh->window, bg);
508 XClearWindow (rh->dpy, rh->window);
510 rh->closure = init_cb (rh->dpy, rh->window, rh->xsft->setup_arg);
511 rh->initted_p = True;
513 /* ignore_rotation_p doesn't quite work at the moment. */
514 rh->ignore_rotation_p = False;
516 (rh->api == API_XLIB &&
517 get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
520 if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
521 rh->fpst = fps_init (rh->dpy, rh->window);
522 if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
525 rh->xsft->fps_cb = 0;
528 if ((*env)->ExceptionOccurred(env)) abort();
532 fps2 = double_time();
535 // Apparently events don't come in on the drawing thread, and JNI flips
536 // out. So we queue them there and run them here.
537 send_queued_events (rh);
540 fps3 = double_time();
543 delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure);
546 fps4 = double_time();
548 if (rh->fpst && rh->xsft->fps_cb)
549 rh->xsft->fps_cb (rh->dpy, rh->window, rh->fpst, rh->closure);
551 if (rh->frontbuffer_p) {
552 jwxyz_copy_area (rh->dpy, rh->window, &rh->frontbuffer, rh->copy_gc,
553 0, 0, rh->window->frame.width, rh->window->frame.height,
557 restore_surface (rh);
562 Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
563 (int) ((fps1-fps0)*1000000),
564 (int) ((fps2-fps1)*1000000),
565 (int) ((fps3-fps2)*1000000),
566 (int) ((fps4-fps3)*1000000),
567 (int) ((double_time()-fps4)*1000000));
574 // Extracts the C structure that is stored in the jwxyz Java object.
575 static struct running_hack *
576 getRunningHack (JNIEnv *env, jobject thiz)
578 jlong result = (*env)->GetLongField (env, thiz, runningHackField);
579 struct running_hack *rh = (struct running_hack *)(intptr_t)result;
581 rh->jobject = thiz; // update this every time we call into C
585 // Look up a class and mark it global in the provided variable.
587 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
589 jclass clazz = (*env)->FindClass(env, className);
590 *globalRef = (*env)->NewGlobalRef(env, clazz);
595 /* Note: to find signature strings for native methods:
596 cd ./project/xscreensaver/build/intermediates/classes/debug/
597 javap -s -p org.jwz.xscreensaver.jwxyz
601 // Implementation of jwxyz's nativeInit Java method.
603 JNIEXPORT void JNICALL
604 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
605 jstring jhack, jobject defaults,
608 pthread_mutex_lock(&mutg);
610 struct running_hack *rh = calloc(1, sizeof(struct running_hack));
612 if ((*env)->ExceptionOccurred(env)) abort();
615 if (!classRefCount) {
616 jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
617 globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
618 runningHackField = (*env)->GetFieldID
619 (env, classjwxyz, "nativeRunningHackPtr", "J");
620 if ((*env)->ExceptionOccurred(env)) abort();
622 jclass classIterable =
623 acquireClass(env, "java/lang/Iterable", &globalRefIterable);
624 iterableIterator = (*env)->GetMethodID
625 (env, classIterable, "iterator", "()Ljava/util/Iterator;");
626 if ((*env)->ExceptionOccurred(env)) abort();
628 jclass classIterator =
629 acquireClass(env, "java/util/Iterator", &globalRefIterator);
630 iteratorHasNext = (*env)->GetMethodID
631 (env, classIterator, "hasNext", "()Z");
632 iteratorNext = (*env)->GetMethodID
633 (env, classIterator, "next", "()Ljava/lang/Object;");
634 if ((*env)->ExceptionOccurred(env)) abort();
636 jclass classMapEntry =
637 acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
638 entryGetKey = (*env)->GetMethodID
639 (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
640 entryGetValue = (*env)->GetMethodID
641 (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
642 if ((*env)->ExceptionOccurred(env)) abort();
647 // Store the C struct into the Java object.
648 (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
650 // TODO: Sort the list so binary search works.
651 const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
655 if (chosen == countof(function_table)) {
656 Log ("Hack not found: %s", hack);
659 if (!strcmp(function_table[chosen].progname, hack))
664 (*env)->ReleaseStringUTFChars(env, jhack, hack);
666 doinit (thiz, rh, env, &function_table[chosen], defaults, w, h);
668 pthread_mutex_unlock(&mutg);
672 JNIEXPORT void JNICALL
673 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
674 jint w, jint h, jdouble rot)
676 pthread_mutex_lock(&mutg);
677 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
679 current_rotation = rot;
681 Log ("native rotation: %f", current_rotation);
683 struct running_hack *rh = getRunningHack(env, thiz);
685 Window wnd = rh->window;
688 wnd->frame.width = w;
689 wnd->frame.height = h;
691 glViewport (0, 0, w, h);
693 if (ignore_rotation_p(rh->dpy) &&
694 rot != 0 && rot != 180 && rot != -180) {
698 wnd->frame.width = w;
699 wnd->frame.height = h;
702 get_egl_surface (rh);
703 if (rh->frontbuffer_p) {
704 free_pixmap (rh, wnd);
705 create_pixmap (wnd, wnd);
707 rh->frontbuffer.frame = wnd->frame;
709 rh->frontbuffer.egl_surface = rh->egl_surface;
712 jwxyz_window_resized (rh->dpy);
714 rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
715 wnd->frame.width, wnd->frame.height);
717 if (rh->api == API_GL) {
718 glMatrixMode (GL_PROJECTION);
719 glRotatef (-rot, 0, 0, 1);
720 glMatrixMode (GL_MODELVIEW);
723 restore_surface (rh);
726 pthread_mutex_unlock(&mutg);
730 JNIEXPORT jlong JNICALL
731 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
733 pthread_mutex_lock(&mutg);
734 struct running_hack *rh = getRunningHack(env, thiz);
735 jlong result = drawXScreenSaver(env, rh);
736 pthread_mutex_unlock(&mutg);
741 // TODO: Check Java side is calling this properly
742 JNIEXPORT void JNICALL
743 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
745 pthread_mutex_lock(&mutg);
746 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
748 struct running_hack *rh = getRunningHack(env, thiz);
750 prepare_context (rh);
753 rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
754 XFreeGC (rh->dpy, rh->copy_gc);
755 jwzgles_free_state ();
756 jwxyz_free_display(rh->dpy);
759 (*env)->SetLongField(env, thiz, runningHackField, 0);
762 if (!classRefCount) {
763 (*env)->DeleteGlobalRef(env, globalRefjwxyz);
764 (*env)->DeleteGlobalRef(env, globalRefIterable);
765 (*env)->DeleteGlobalRef(env, globalRefIterator);
766 (*env)->DeleteGlobalRef(env, globalRefMapEntry);
770 pthread_mutex_unlock(&mutg);
775 send_event (struct running_hack *rh, XEvent *e)
777 // Assumes mutex is locked and context is prepared
779 int *xP = 0, *yP = 0;
780 switch (e->xany.type) {
781 case ButtonPress: case ButtonRelease:
791 // Rotate the coordinates in the events to match the pixels.
793 if (ignore_rotation_p (rh->dpy)) {
794 Window win = XRootWindow (rh->dpy, 0);
795 int w = win->frame.width;
796 int h = win->frame.height;
798 switch ((int) current_rotation) {
799 case 180: case -180: // #### untested
804 swap = *xP; *xP = *yP; *yP = swap;
807 case -90: case 270: // #### untested
808 swap = *xP; *xP = *yP; *yP = swap;
814 rh->window->window.last_mouse_x = *xP;
815 rh->window->window.last_mouse_y = *yP;
818 return (rh->xsft->event_cb
819 ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
825 send_queued_events (struct running_hack *rh)
827 struct event_queue *event, *next;
828 if (! rh->event_queue) return;
829 for (event = rh->event_queue, next = event->next;
831 event = next, next = (event ? event->next : 0)) {
832 if (! send_event (rh, &event->event)) {
833 // #### flash the screen or something
842 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
844 pthread_mutex_lock (&mutg);
845 struct running_hack *rh = getRunningHack (env, thiz);
846 struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
847 memcpy (&q->event, e, sizeof(*e));
850 // Put it at the end.
851 struct event_queue *oq;
852 for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
859 pthread_mutex_unlock (&mutg);
863 JNIEXPORT void JNICALL
864 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
865 int x, int y, jboolean down)
868 memset (&e, 0, sizeof(e));
869 e.xany.type = (down ? ButtonPress : ButtonRelease);
870 e.xbutton.button = Button1;
873 queue_event (env, thiz, &e);
876 JNIEXPORT void JNICALL
877 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
881 memset (&e, 0, sizeof(e));
882 e.xany.type = MotionNotify;
885 queue_event (env, thiz, &e);
888 JNIEXPORT void JNICALL
889 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
894 memset (&e, 0, sizeof(e));
895 e.xkey.keycode = code;
897 e.xany.type = (down_p ? KeyPress : KeyRelease);
898 queue_event (env, thiz, &e);
899 e.xany.type = KeyRelease;
900 queue_event (env, thiz, &e);
905 /***************************************************************************
906 Backend functions for jwxyz-gl.c
910 prepare_context (struct running_hack *rh)
912 /* Don't set matrices here; set them when an Xlib call triggers
913 jwxyz_bind_drawable/jwxyz_set_matrices.
915 rh->current_drawable = NULL;
916 jwzgles_make_current (rh->gles_state);
920 finish_bind_drawable (Display *dpy, Drawable dst)
924 glViewport (0, 0, dst->frame.width, dst->frame.height);
925 jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False);
930 bind_drawable_fbo (struct running_hack *rh, Drawable d)
932 rh->glBindFramebufferOES (GL_FRAMEBUFFER_OES,
933 d->texture ? rh->fb_pixmap : rh->fb_default);
935 rh->glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
936 GL_TEXTURE_2D, d->texture, 0);
942 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
944 struct running_hack *rh = w->window.rh;
945 JNIEnv *env = w->window.rh->jni_env;
946 if ((*env)->ExceptionOccurred(env)) abort();
947 if (rh->current_drawable != d) {
949 bind_drawable_fbo (rh, d);
951 eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx);
953 finish_bind_drawable (dpy, d);
954 rh->current_drawable = d;
960 jwxyz_frame (Drawable d)
967 jwxyz_drawable_depth (Drawable d)
969 return (d->type == WINDOW
970 ? visual_depth (NULL, NULL)
976 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
982 xp->x = w->window.last_mouse_x;
983 xp->y = w->window.last_mouse_y;
989 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
991 fps_compute (fpst, 0, -1);
997 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
998 int src_x, int src_y, unsigned int width, unsigned int height,
999 int dst_x, int dst_y)
1001 Window w = XRootWindow (dpy, 0);
1002 struct running_hack *rh = w->window.rh;
1004 if (rh->gl_fbo_p && src->texture && src != dst) {
1005 bind_drawable_fbo (rh, dst);
1006 finish_bind_drawable (dpy, dst);
1007 rh->current_drawable = NULL;
1009 glBindTexture (GL_TEXTURE_2D, src->texture);
1011 jwxyz_gl_draw_image (GL_TEXTURE_2D, to_pow2(src->frame.width),
1012 to_pow2(src->frame.height),
1013 src_x, src->frame.height - src_y - height,
1014 width, height, dst_x, dst_y);
1020 // Hilarious display corruption ahoy! (Note to self: it's on the emulator.)
1021 // TODO for Dave: Recheck behavior on the emulator with the better Pixmap support.
1023 rh->current_drawable = NULL;
1025 bind_drawable_fbo (rh, src);
1027 eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx);
1029 jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y,
1030 width, height, dst_x, dst_y);
1033 bind_drawable_fbo (rh, dst);
1034 finish_bind_drawable (dpy, dst);
1036 jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y, width, height,
1041 jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
1042 width, height, dst_x, dst_y);
1049 jwxyz_assert_drawable (Window main_window, Drawable d)
1051 check_gl_error("jwxyz_assert_drawable");
1056 jwxyz_assert_gl (void)
1058 check_gl_error("jwxyz_assert_gl");
1063 XCreatePixmap (Display *dpy, Drawable d,
1064 unsigned int width, unsigned int height, unsigned int depth)
1066 Window win = XRootWindow(dpy, 0);
1068 Pixmap p = malloc(sizeof(*p));
1072 p->frame.width = width;
1073 p->frame.height = height;
1075 Assert(depth == 1 || depth == visual_depth(NULL, NULL),
1076 "XCreatePixmap: bad depth");
1077 p->pixmap.depth = depth;
1079 create_pixmap (win, p);
1081 /* For debugging. */
1082 jwxyz_bind_drawable (dpy, win, p);
1083 glClearColor (frand(1), frand(1), frand(1), 0);
1084 glClear (GL_COLOR_BUFFER_BIT);
1091 XFreePixmap (Display *d, Pixmap p)
1093 struct running_hack *rh = XRootWindow(d, 0)->window.rh;
1094 if (rh->current_drawable == p)
1095 rh->current_drawable = NULL;
1097 free_pixmap (rh, p);
1104 current_device_rotation (void)
1106 return current_rotation;
1110 ignore_rotation_p (Display *dpy)
1112 struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
1113 return rh->ignore_rotation_p;
1118 jstring_dup (JNIEnv *env, jstring str)
1120 Assert (str, "expected jstring, not null");
1121 const char *cstr = (*env)->GetStringUTFChars (env, str, 0);
1122 size_t len = (*env)->GetStringUTFLength (env, str) + 1;
1123 char *result = malloc (len);
1125 memcpy (result, cstr, len);
1127 (*env)->ReleaseStringUTFChars (env, str, cstr);
1133 get_string_resource_window (Window window, char *name)
1135 JNIEnv *env = window->window.rh->jni_env;
1136 jobject obj = window->window.rh->jobject;
1138 if ((*env)->ExceptionOccurred(env)) abort();
1139 jstring jstr = (*env)->NewStringUTF (env, name);
1140 jclass c = (*env)->GetObjectClass (env, obj);
1141 jmethodID m = (*env)->GetMethodID (env, c, "getStringResource",
1142 "(Ljava/lang/String;)Ljava/lang/String;");
1143 if ((*env)->ExceptionOccurred(env)) abort();
1146 ? (*env)->CallObjectMethod (env, obj, m, jstr)
1148 (*env)->DeleteLocalRef (env, c);
1149 (*env)->DeleteLocalRef (env, jstr);
1152 ret = jstring_dup (env, jvalue);
1154 Log("pref %s = %s", name, (ret ? ret : "(null)"));
1160 get_string_resource (Display *dpy, char *name, char *class)
1162 return get_string_resource_window (RootWindow (dpy, 0), name);
1166 /* Returns the contents of the URL. */
1168 textclient_mobile_url_string (Display *dpy, const char *url)
1170 Window window = RootWindow (dpy, 0);
1171 JNIEnv *env = window->window.rh->jni_env;
1172 jobject obj = window->window.rh->jobject;
1174 jstring jstr = (*env)->NewStringUTF (env, url);
1175 jclass c = (*env)->GetObjectClass (env, obj);
1176 jmethodID m = (*env)->GetMethodID (env, c, "loadURL",
1177 "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
1178 if ((*env)->ExceptionOccurred(env)) abort();
1180 ? (*env)->CallObjectMethod (env, obj, m, jstr)
1182 (*env)->DeleteLocalRef (env, c);
1183 (*env)->DeleteLocalRef (env, jstr);
1185 char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1188 int L = (*env)->GetDirectBufferCapacity (env, buf);
1189 body2 = malloc (L + 1);
1190 memcpy (body2, body, L);
1193 body2 = strdup ("ERROR");
1197 (*env)->DeleteLocalRef (env, buf);
1204 jwxyz_scale (Window main_window)
1211 jwxyz_default_font_family (int require)
1213 /* Font families in XLFDs are totally ignored (for now). */
1214 return "sans-serif";
1219 jwxyz_load_native_font (Window window,
1220 int traits_jwxyz, int mask_jwxyz,
1221 const char *font_name_ptr, size_t font_name_length,
1222 int font_name_type, float size,
1223 char **family_name_ret,
1224 int *ascent_ret, int *descent_ret)
1226 JNIEnv *env = window->window.rh->jni_env;
1227 jobject obj = window->window.rh->jobject;
1229 jstring jname = NULL;
1230 if (font_name_ptr) {
1231 char *name_nul = malloc(font_name_length + 1);
1232 memcpy(name_nul, font_name_ptr, font_name_length);
1233 name_nul[font_name_length] = 0;
1234 jname = (*env)->NewStringUTF (env, name_nul);
1238 jclass c = (*env)->GetObjectClass (env, obj);
1239 jmethodID m = (*env)->GetMethodID (env, c, "loadFont",
1240 "(IILjava/lang/String;IF)[Ljava/lang/Object;");
1241 if ((*env)->ExceptionOccurred(env)) abort();
1243 jobjectArray array = (m
1244 ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
1245 (jint)traits_jwxyz, jname,
1246 (jint)font_name_type, (jfloat)size)
1249 (*env)->DeleteLocalRef (env, c);
1252 jobject font = (*env)->GetObjectArrayElement (env, array, 0);
1253 jobject family_name =
1254 (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
1255 jobject asc = (*env)->GetObjectArrayElement (env, array, 2);
1256 jobject desc = (*env)->GetObjectArrayElement (env, array, 3);
1257 if ((*env)->ExceptionOccurred(env)) abort();
1259 if (family_name_ret)
1260 *family_name_ret = jstring_dup (env, family_name);
1262 jobject paint = (*env)->NewGlobalRef (env, font);
1263 if ((*env)->ExceptionOccurred(env)) abort();
1265 c = (*env)->GetObjectClass(env, asc);
1266 m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1267 if ((*env)->ExceptionOccurred(env)) abort();
1269 *ascent_ret = (int) (*env)->CallFloatMethod (env, asc, m);
1270 *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1272 return (void *) paint;
1280 jwxyz_release_native_font (Display *dpy, void *native_font)
1282 Window window = RootWindow (dpy, 0);
1283 JNIEnv *env = window->window.rh->jni_env;
1284 if ((*env)->ExceptionOccurred(env)) abort();
1285 (*env)->DeleteGlobalRef (env, (jobject) native_font);
1286 if ((*env)->ExceptionOccurred(env)) abort();
1290 /* If the local reference table fills up, use this to figure out where
1291 you missed a call to DeleteLocalRef. */
1293 static void dump_reference_tables(JNIEnv *env)
1295 jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1296 jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1298 (*env)->CallStaticVoidMethod (env, c, m);
1299 (*env)->DeleteLocalRef (env, c);
1304 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1305 // string. If pixmap_ret is provided, also renders the text.
1308 jwxyz_render_text (Display *dpy, void *native_font,
1309 const char *str, size_t len, int utf8,
1310 XCharStruct *cs, char **pixmap_ret)
1312 Window window = RootWindow (dpy, 0);
1313 JNIEnv *env = window->window.rh->jni_env;
1314 jobject obj = window->window.rh->jobject;
1319 s2 = malloc (len + 1);
1320 memcpy (s2, str, len);
1322 } else { // Convert Latin1 to UTF8
1323 s2 = malloc (len * 2 + 1);
1324 unsigned char *s3 = (unsigned char *) s2;
1326 for (i = 0; i < len; i++) {
1327 unsigned char c = ((unsigned char *) str)[i];
1331 *s3++ = (0xC0 | (0x03 & (c >> 6)));
1332 *s3++ = (0x80 | (0x3F & c));
1338 jstring jstr = (*env)->NewStringUTF (env, s2);
1339 jclass c = (*env)->GetObjectClass (env, obj);
1340 jmethodID m = (*env)->GetMethodID (env, c, "renderText",
1341 "(Landroid/graphics/Paint;Ljava/lang/String;Z)Ljava/nio/ByteBuffer;");
1342 if ((*env)->ExceptionOccurred(env)) abort();
1345 ? (*env)->CallObjectMethod (env, obj, m,
1346 (jobject) native_font,
1348 (pixmap_ret ? JNI_TRUE : JNI_FALSE))
1350 (*env)->DeleteLocalRef (env, c);
1351 (*env)->DeleteLocalRef (env, jstr);
1354 if ((*env)->ExceptionOccurred(env)) abort();
1355 unsigned char *bits = (unsigned char *)
1356 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1359 int L = (*env)->GetDirectBufferCapacity (env, buf);
1360 if (L < 10) abort();
1361 cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1362 cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1363 cs->width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1364 cs->ascent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1365 cs->descent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1368 char *pix = malloc (L - i);
1370 memcpy (pix, bits + i, L - i);
1374 memset (cs, 0, sizeof(*cs));
1380 (*env)->DeleteLocalRef (env, buf);
1385 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1387 JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1388 /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1390 c = (*env)->FindClass (env, "java/lang/Character"),
1391 c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
1393 if ((*env)->ExceptionOccurred(env)) abort();
1394 jmethodID m = (*env)->GetStaticMethodID (
1395 env, c, "getName", "(I)Ljava/lang/String;");
1396 jthrowable exc = (*env)->ExceptionOccurred(env);
1398 if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1399 (*env)->ExceptionClear (env);
1400 Assert (!m, "jwxyz_unicode_character_name: m?");
1409 jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1411 ret = jstring_dup (env, name);
1415 asprintf(&ret, "U+%.4lX", uc);
1422 /* Called from utils/grabclient.c */
1424 jwxyz_load_random_image (Display *dpy,
1425 int *width_ret, int *height_ret,
1429 /* This function needs to be implemented for Android */
1432 Window window = RootWindow (dpy, 0);
1433 struct running_hack *rh = window->window.rh;
1434 JNIEnv *env = rh->jni_env;
1435 jobject obj = rh->jobject;
1438 get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1440 get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1442 get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1444 if (!images_p && !grab_p)
1447 if (grab_p && images_p) {
1448 grab_p = !(random() & 5); /* if both, screenshot 1/5th of the time */
1452 jclass c = (*env)->GetObjectClass (env, obj);
1453 jmethodID m = (*env)->GetMethodID (env, c,
1456 : "loadRandomImage"),
1457 "(IIZ)Ljava/nio/ByteBuffer;");
1458 if ((*env)->ExceptionOccurred(env)) abort();
1460 ? (*env)->CallObjectMethod (env, obj, m,
1461 window->frame.width,
1462 window->frame.height,
1463 (rotate_p ? JNI_TRUE : JNI_FALSE))
1465 (*env)->DeleteLocalRef (env, c);
1467 unsigned char *bits = (unsigned char *)
1468 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1472 int L = (*env)->GetDirectBufferCapacity (env, buf);
1473 if (L < 100) abort();
1474 int width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1475 int height = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1476 char *name = (char *) bits + i;
1477 int L2 = strlen (name);
1479 if (width * height * 4 != L - i) abort();
1480 char *pix = malloc (L - i);
1482 memcpy (pix, bits + i, L - i);
1484 *height_ret = height;
1485 *name_ret = strdup (name);
1486 return (char *) pix;
1490 (*env)->DeleteLocalRef (env, buf);
1495 #endif /* HAVE_ANDROID */