From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / jwxyz / jwxyz-android.c
1 /* xscreensaver, Copyright (c) 2016-2017 Jamie Zawinski <jwz@jwz.org>
2  *
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 
9  * implied warranty.
10  *
11  * This file is three related things:
12  *
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.
17  */
18
19 #include "config.h"
20
21 #ifdef HAVE_ANDROID /* whole file */
22
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <time.h>
27 #include <math.h>
28 #include <setjmp.h>
29
30 #include <GLES/gl.h>
31 #include <jni.h>
32 #include <android/log.h>
33 #include <pthread.h>
34
35 #include "screenhackI.h"
36 #include "jwxyzI.h"
37 #include "jwzglesI.h"
38 #include "jwxyz-android.h"
39 #include "textclient.h"
40 #include "grabscreen.h"
41 #include "pow2.h"
42
43
44 #define countof(x) (sizeof(x)/sizeof(*(x)))
45
46 extern struct xscreensaver_function_table *xscreensaver_function_table;
47
48 struct function_table_entry {
49   const char *progname;
50   struct xscreensaver_function_table *xsft;
51   int api;
52 };
53
54 #include "gen/function-table.h"
55
56 struct event_queue {
57   XEvent event;
58   struct event_queue *next;
59 };
60
61 static void send_queued_events (struct running_hack *rh);
62
63 const char *progname;
64 const char *progclass;
65 int mono_p = 0;
66
67 static JavaVM *global_jvm;
68 static jmp_buf jmp_target;
69
70 static double current_rotation = 0;
71
72 extern void check_gl_error (const char *type);
73
74 void
75 jwxyz_logv(Bool error, const char *fmt, va_list args)
76 {
77   __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
78                        "xscreensaver", fmt, args);
79
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
84      significantly.
85   */
86 # if 0
87   struct timespec ts;
88   ts.tv_sec = 0;
89   ts.tv_nsec = 25 * 1000000;
90   nanosleep(&ts, NULL);
91 # endif
92 }
93
94 /* Handle an abort on Android
95    TODO: Test that Android handles aborts properly
96  */
97 void
98 jwxyz_abort (const char *fmt, ...)
99 {
100   /* Send error to Android device log */
101   if (!fmt || !*fmt)
102     fmt = "abort";
103
104   va_list args;
105   va_start (args, fmt);
106   jwxyz_logv(True, fmt, args);
107   va_end (args);
108
109   char buf[10240];
110   va_start (args, fmt);
111   vsprintf (buf, fmt, args);
112   va_end (args);
113
114   JNIEnv *env;
115   (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
116
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"),
121                       buf);
122   }
123
124   // Nonlocal exit out of the jwxyz code.
125   longjmp (jmp_target, 1);
126 }
127
128
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
131    IDs.
132    https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
133 */
134
135
136 // #### only need one var I think
137 static size_t classRefCount = 0;
138 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
139     globalRefMapEntry;
140
141 static jfieldID runningHackField;
142 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
143 static jmethodID entryGetKey, entryGetValue;
144
145 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
146
147 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
148 static char *get_string_resource_window (Window window, char *name);
149
150
151 /* Also creates double-buffered windows. */
152 static void
153 create_pixmap (Window win, Drawable p)
154 {
155   // See also:
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
159
160   Assert (p->frame.width, "p->frame.width");
161   Assert (p->frame.height, "p->frame.height");
162
163   struct running_hack *rh = win->window.rh;
164
165   if (rh->gl_fbo_p) {
166     glGenTextures (1, &p->texture);
167     glBindTexture (GL_TEXTURE_2D, p->texture);
168
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);
172
173     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
174     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
175   } else {
176     EGLint attribs[5];
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;
182
183     p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
184                                            attribs);
185     Assert (p->egl_surface != EGL_NO_SURFACE,
186             "XCreatePixmap: got EGL_NO_SURFACE");
187   }
188 }
189
190
191 static void
192 free_pixmap (struct running_hack *rh, Pixmap p)
193 {
194   if (rh->gl_fbo_p) {
195     glDeleteTextures (1, &p->texture);
196   } else {
197     eglDestroySurface(rh->egl_display, p->egl_surface);
198   }
199 }
200
201
202 static void
203 restore_surface (struct running_hack *rh)
204 {
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.
208    */
209   eglMakeCurrent (rh->egl_display, rh->egl_surface, rh->egl_surface,
210                   rh->egl_ctx);
211   rh->current_drawable = NULL;
212 }
213
214
215 static void
216 get_egl_surface (struct running_hack *rh)
217 {
218   rh->egl_surface = eglGetCurrentSurface(EGL_DRAW);
219   Assert(eglGetCurrentSurface(EGL_READ) == rh->egl_surface,
220          "doinit: EGL_READ != EGL_DRAW");
221 }
222
223
224 // Initialized OpenGL and runs the screenhack's init function.
225 //
226 static void
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)
230 {
231   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
232
233   progname = chosen->progname;
234   rh->xsft = chosen->xsft;
235   rh->api = chosen->api;
236   rh->jni_env = env;
237   rh->jobject = jwxyz_obj;  // update this every time we call into C
238
239   (*env)->GetJavaVM (env, &global_jvm);
240
241 # undef ya_rand_init  // This is the one and only place it is allowed
242   ya_rand_init (0);
243
244   Window wnd = (Window) calloc(1, sizeof(*wnd));
245   wnd->window.rh = rh;
246   wnd->frame.width = w;
247   wnd->frame.height = h;
248   wnd->type = WINDOW;
249
250   rh->window = wnd;
251   progclass = rh->xsft->progclass;
252
253   if ((*env)->ExceptionOccurred(env)) abort();
254
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);
258
259   if ((*env)->ExceptionOccurred(env)) abort();
260
261   // Load the defaults.
262   // Unceremoniously stolen from [PrefsReader defaultsToDict:].
263
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;");
267   if (! m) abort();
268   if ((*env)->ExceptionOccurred(env)) abort();
269
270   const struct { const char *key, *val; } default_defaults[] = {
271     { "doubleBuffer", "True" },
272     { "multiSample",  "False" },
273     { "texFontCacheSize", "30" },
274     { "textMode", "date" },
275     { "textURL",
276       "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
277     { "grabDesktopImages",  "True" },
278     { "chooseRandomImages", "True" },
279   };
280
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);
286     strcat (key2, "_");
287     strcat (key2, key);
288
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);
296     free (key2);
297   }
298
299   const char *const *defs = rh->xsft->defaults;
300   while (*defs) {
301     char *line = strdup (*defs);
302     char *key, *val;
303     key = line;
304     while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
305       key++;
306     val = key;
307     while (*val && *val != ':')
308       val++;
309     if (*val != ':') abort();
310     *val++ = 0;
311     while (*val == ' ' || *val == '\t')
312       val++;
313
314     unsigned long L = strlen(val);
315     while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
316       val[--L] = 0;
317
318     char *key2 = malloc (strlen(progname) + strlen(key) + 2);
319     strcpy (key2, progname);
320     strcat (key2, "_");
321     strcat (key2, key);
322
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);
330     free (key2);
331     free (line);
332     defs++;
333   }
334
335   (*env)->DeleteLocalRef (env, c);
336   if ((*env)->ExceptionOccurred(env)) abort();
337
338   // GL init. Must come after resource processing.
339
340   rh->egl_ctx = eglGetCurrentContext();
341   Assert(rh->egl_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
342
343   get_egl_surface (rh);
344
345   rh->egl_display = eglGetCurrentDisplay();
346   Assert(rh->egl_display != EGL_NO_DISPLAY, "doinit: EGL_NO_DISPLAY");
347
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)
351   {
352     egl_major = 1;
353     egl_minor = 0;
354   }
355
356   EGLint config_attribs[3];
357   config_attribs[0] = EGL_CONFIG_ID;
358   eglQueryContext(rh->egl_display, rh->egl_ctx, EGL_CONFIG_ID,
359                   &config_attribs[1]);
360   config_attribs[2] = EGL_NONE;
361
362   EGLint num_config;
363   eglChooseConfig(rh->egl_display, config_attribs,
364                   &rh->egl_config, 1, &num_config);
365   Assert(num_config == 1, "no EGL config chosen");
366
367   const GLubyte *extensions = glGetString (GL_EXTENSIONS);
368   rh->gl_fbo_p = jwzgles_gluCheckExtension (
369     (const GLubyte *)"GL_OES_framebuffer_object", extensions);
370
371   if (rh->gl_fbo_p) {
372     PFNGLGENFRAMEBUFFERSOESPROC glGenFramebuffersOES =
373       (PFNGLGENFRAMEBUFFERSOESPROC)
374         eglGetProcAddress ("glGenFramebuffersOES");
375
376     rh->glBindFramebufferOES = (PFNGLBINDFRAMEBUFFEROESPROC)
377         eglGetProcAddress ("glBindFramebufferOES");
378     rh->glFramebufferTexture2DOES = (PFNGLFRAMEBUFFERTEXTURE2DOESPROC)
379       eglGetProcAddress ("glFramebufferTexture2DOES");
380
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);
384     wnd->texture = 0;
385   } else {
386     wnd->egl_surface = rh->egl_surface;
387   }
388
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.
392    */
393   rh->frontbuffer_p = False;
394
395   if (rh->api == API_XLIB ||
396       (rh->api == API_GL &&
397        strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
398
399     rh->frontbuffer_p = True;
400
401 # if 0 /* Might need to be 0 for Adreno...? */
402     if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
403       EGLint surface_type;
404       eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
405                          &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;
410       }
411     }
412 # endif
413
414     if (rh->frontbuffer_p) {
415       /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
416       create_pixmap (wnd, wnd);
417
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);
422
423       if(rh->gl_fbo_p) {
424         rh->frontbuffer.texture = 0;
425       } else {
426         Assert (wnd->egl_surface != rh->egl_surface,
427                 "oops: wnd->egl_surface == rh->egl_surface");
428         rh->frontbuffer.egl_surface = rh->egl_surface;
429       }
430     }
431   }
432
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
436
437   /* Requires valid rh->dpy. */
438   rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
439  
440   rh->gles_state = jwzgles_make_state();
441   restore_surface(rh);
442  END: ;
443 }
444
445
446 #undef DEBUG_FPS
447
448 #ifdef DEBUG_FPS
449
450 static double
451 double_time (void)
452 {
453   struct timeval now;
454 # ifdef GETTIMEOFDAY_TWO_ARGS
455   struct timezone tzp;
456   gettimeofday(&now, &tzp);
457 # else
458   gettimeofday(&now);
459 # endif
460
461   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
462 }
463
464 #endif
465
466 // Animates a single frame of the current hack.
467 //
468 static jlong
469 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
470 {
471 # ifdef DEBUG_FPS
472   double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
473   fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
474 # endif
475
476   unsigned long delay = 0;
477
478   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
479
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.
484    */
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*. */
488     goto END;
489   }
490
491   prepare_context(rh);
492
493 # ifdef DEBUG_FPS
494   fps1 = double_time();
495 # endif
496
497   // The init function might do graphics (e.g. XClearWindow) so it has
498   // to be run from inside onDrawFrame, not onSurfaceChanged.
499
500   if (! rh->initted_p) {
501
502     void *(*init_cb) (Display *, Window, void *) =
503       (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
504
505     unsigned int bg =
506       get_pixel_resource (rh->dpy, 0, "background", "Background");
507     XSetWindowBackground (rh->dpy, rh->window, bg);
508     XClearWindow (rh->dpy, rh->window);
509
510     rh->closure = init_cb (rh->dpy, rh->window, rh->xsft->setup_arg);
511     rh->initted_p = True;
512
513     /* ignore_rotation_p doesn't quite work at the moment. */
514     rh->ignore_rotation_p = False;
515 /*
516       (rh->api == API_XLIB &&
517        get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
518 */
519
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;
523     } else {
524       rh->fpst = NULL;
525       rh->xsft->fps_cb = 0;
526     }
527
528     if ((*env)->ExceptionOccurred(env)) abort();
529   }
530
531 # ifdef DEBUG_FPS
532   fps2 = double_time();
533 # endif
534
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);
538
539 # ifdef DEBUG_FPS
540   fps3 = double_time();
541 # endif
542
543   delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure);
544
545 # ifdef DEBUG_FPS
546   fps4 = double_time();
547 # endif
548   if (rh->fpst && rh->xsft->fps_cb)
549     rh->xsft->fps_cb (rh->dpy, rh->window, rh->fpst, rh->closure);
550
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,
554                      0, 0);
555   }
556
557   restore_surface (rh);
558
559  END: ;
560
561 # ifdef DEBUG_FPS
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));
568 # endif
569
570   return delay;
571 }
572
573
574 // Extracts the C structure that is stored in the jwxyz Java object.
575 static struct running_hack *
576 getRunningHack (JNIEnv *env, jobject thiz)
577 {
578   jlong result = (*env)->GetLongField (env, thiz, runningHackField);
579   struct running_hack *rh = (struct running_hack *)(intptr_t)result;
580   if (rh)
581     rh->jobject = thiz;  // update this every time we call into C
582   return rh;
583 }
584
585 // Look up a class and mark it global in the provided variable.
586 static jclass
587 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
588 {
589   jclass clazz = (*env)->FindClass(env, className);
590   *globalRef = (*env)->NewGlobalRef(env, clazz);
591   return clazz;
592 }
593
594
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
598  */
599
600
601 // Implementation of jwxyz's nativeInit Java method.
602 //
603 JNIEXPORT void JNICALL
604 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
605                                             jstring jhack, jobject defaults,
606                                             jint w, jint h)
607 {
608   pthread_mutex_lock(&mutg);
609
610   struct running_hack *rh = calloc(1, sizeof(struct running_hack));
611
612   if ((*env)->ExceptionOccurred(env)) abort();
613
614   // #### simplify
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();
621
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();
627
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();
635
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();
643   }
644
645   ++classRefCount;
646
647   // Store the C struct into the Java object.
648   (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
649
650   // TODO: Sort the list so binary search works.
651   const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
652
653   int chosen = 0;
654   for (;;) {
655     if (chosen == countof(function_table)) {
656       Log ("Hack not found: %s", hack);
657       abort();
658     }
659     if (!strcmp(function_table[chosen].progname, hack))
660       break;
661     chosen++;
662   }
663
664   (*env)->ReleaseStringUTFChars(env, jhack, hack);
665
666   doinit (thiz, rh, env, &function_table[chosen], defaults, w, h);
667
668   pthread_mutex_unlock(&mutg);
669 }
670
671
672 JNIEXPORT void JNICALL
673 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
674                                               jint w, jint h, jdouble rot)
675 {
676   pthread_mutex_lock(&mutg);
677   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
678
679   current_rotation = rot;
680
681   Log ("native rotation: %f", current_rotation);
682
683   struct running_hack *rh = getRunningHack(env, thiz);
684
685   Window wnd = rh->window;
686   wnd->frame.x = 0;
687   wnd->frame.y = 0;
688   wnd->frame.width  = w;
689   wnd->frame.height = h;
690
691   glViewport (0, 0, w, h);
692
693   if (ignore_rotation_p(rh->dpy) &&
694       rot != 0 && rot != 180 && rot != -180) {
695     int swap = w;
696     w = h;
697     h = swap;
698     wnd->frame.width  = w;
699     wnd->frame.height = h;
700   }
701
702   get_egl_surface (rh);
703   if (rh->frontbuffer_p) {
704     free_pixmap (rh, wnd);
705     create_pixmap (wnd, wnd);
706
707     rh->frontbuffer.frame = wnd->frame;
708     if (!rh->gl_fbo_p)
709       rh->frontbuffer.egl_surface = rh->egl_surface;
710   }
711
712   jwxyz_window_resized (rh->dpy);
713   if (rh->initted_p)
714     rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
715                           wnd->frame.width, wnd->frame.height);
716
717   if (rh->api == API_GL) {
718     glMatrixMode (GL_PROJECTION);
719     glRotatef (-rot, 0, 0, 1);
720     glMatrixMode (GL_MODELVIEW);
721   }
722
723   restore_surface (rh);
724
725  END:
726   pthread_mutex_unlock(&mutg);
727 }
728
729
730 JNIEXPORT jlong JNICALL
731 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
732 {
733   pthread_mutex_lock(&mutg);
734   struct running_hack *rh = getRunningHack(env, thiz);
735   jlong result = drawXScreenSaver(env, rh);
736   pthread_mutex_unlock(&mutg);
737   return result;
738 }
739
740
741 // TODO: Check Java side is calling this properly
742 JNIEXPORT void JNICALL
743 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
744 {
745   pthread_mutex_lock(&mutg);
746   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
747
748   struct running_hack *rh = getRunningHack(env, thiz);
749
750   prepare_context (rh);
751
752   if (rh->initted_p)
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);
757
758   free(rh);
759   (*env)->SetLongField(env, thiz, runningHackField, 0);
760
761   --classRefCount;
762   if (!classRefCount) {
763     (*env)->DeleteGlobalRef(env, globalRefjwxyz);
764     (*env)->DeleteGlobalRef(env, globalRefIterable);
765     (*env)->DeleteGlobalRef(env, globalRefIterator);
766     (*env)->DeleteGlobalRef(env, globalRefMapEntry);
767   }
768
769  END:
770   pthread_mutex_unlock(&mutg);
771 }
772
773
774 static int
775 send_event (struct running_hack *rh, XEvent *e)
776 {
777   // Assumes mutex is locked and context is prepared
778
779   int *xP = 0, *yP = 0;
780   switch (e->xany.type) {
781   case ButtonPress: case ButtonRelease:
782     xP = &e->xbutton.x;
783     yP = &e->xbutton.y;
784     break;
785   case MotionNotify:
786     xP = &e->xmotion.x;
787     yP = &e->xmotion.y;
788     break;
789   }
790
791   // Rotate the coordinates in the events to match the pixels.
792   if (xP) {
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;
797       int swap;
798       switch ((int) current_rotation) {
799       case 180: case -180:                              // #### untested
800         *xP = w - *xP;
801         *yP = h - *yP;
802         break;
803       case 90: case -270:
804         swap = *xP; *xP = *yP; *yP = swap;
805         *yP = h - *yP;
806         break;
807       case -90: case 270:                               // #### untested
808         swap = *xP; *xP = *yP; *yP = swap;
809         *xP = w - *xP;
810         break;
811       }
812     }
813
814     rh->window->window.last_mouse_x = *xP;
815     rh->window->window.last_mouse_y = *yP;
816   }
817
818   return (rh->xsft->event_cb
819           ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
820           : 0);
821 }
822
823
824 static void
825 send_queued_events (struct running_hack *rh)
826 {
827   struct event_queue *event, *next;
828   if (! rh->event_queue) return;
829   for (event = rh->event_queue, next = event->next;
830        event;
831        event = next, next = (event ? event->next : 0)) {
832     if (! send_event (rh, &event->event)) {
833       // #### flash the screen or something
834     }
835     free (event);
836   }
837   rh->event_queue = 0;
838 }
839
840
841 static void
842 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
843 {
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));
848   q->next = 0;
849
850   // Put it at the end.
851   struct event_queue *oq;
852   for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
853     ;
854   if (oq)
855     oq->next = q;
856   else
857     rh->event_queue = q;
858
859   pthread_mutex_unlock (&mutg);
860 }
861
862
863 JNIEXPORT void JNICALL
864 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
865                                                  int x, int y, jboolean down)
866 {
867   XEvent e;
868   memset (&e, 0, sizeof(e));
869   e.xany.type = (down ? ButtonPress : ButtonRelease);
870   e.xbutton.button = Button1;
871   e.xbutton.x = x;
872   e.xbutton.y = y;
873   queue_event (env, thiz, &e);
874 }
875
876 JNIEXPORT void JNICALL
877 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
878                                                  int x, int y)
879 {
880   XEvent e;
881   memset (&e, 0, sizeof(e));
882   e.xany.type = MotionNotify;
883   e.xmotion.x = x;
884   e.xmotion.y = y;
885   queue_event (env, thiz, &e);
886 }
887
888 JNIEXPORT void JNICALL
889 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
890                                               jboolean down_p, 
891                                               int code, int mods)
892 {
893   XEvent e;
894   memset (&e, 0, sizeof(e));
895   e.xkey.keycode = code;
896   e.xkey.state = 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);
901 }
902
903
904
905 /***************************************************************************
906   Backend functions for jwxyz-gl.c
907  */
908
909 void
910 prepare_context (struct running_hack *rh)
911 {
912   /* Don't set matrices here; set them when an Xlib call triggers
913      jwxyz_bind_drawable/jwxyz_set_matrices.
914    */
915   rh->current_drawable = NULL;
916   jwzgles_make_current (rh->gles_state);
917 }
918
919 static void
920 finish_bind_drawable (Display *dpy, Drawable dst)
921 {
922   jwxyz_assert_gl ();
923
924   glViewport (0, 0, dst->frame.width, dst->frame.height);
925   jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False);
926 }
927
928
929 static void
930 bind_drawable_fbo (struct running_hack *rh, Drawable d)
931 {
932   rh->glBindFramebufferOES (GL_FRAMEBUFFER_OES,
933                             d->texture ? rh->fb_pixmap : rh->fb_default);
934   if (d->texture) {
935     rh->glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
936                                    GL_TEXTURE_2D, d->texture, 0);
937   }
938 }
939
940
941 void
942 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
943 {
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) {
948     if (rh->gl_fbo_p) {
949       bind_drawable_fbo (rh, d);
950     } else {
951       eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx);
952     }
953     finish_bind_drawable (dpy, d);
954     rh->current_drawable = d;
955   }
956 }
957
958
959 const XRectangle *
960 jwxyz_frame (Drawable d)
961 {
962   return &d->frame;
963 }
964
965
966 unsigned int
967 jwxyz_drawable_depth (Drawable d)
968 {
969   return (d->type == WINDOW
970           ? visual_depth (NULL, NULL)
971           : d->pixmap.depth);
972 }
973
974
975 void
976 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
977 {
978   xvpos->x = 0;
979   xvpos->y = 0;
980
981   if (xp) {
982     xp->x = w->window.last_mouse_x;
983     xp->y = w->window.last_mouse_y;
984   }
985 }
986
987
988 static void
989 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
990 {
991   fps_compute (fpst, 0, -1);
992   fps_draw (fpst);
993 }
994
995
996 void
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)
1000 {
1001   Window w = XRootWindow (dpy, 0);
1002   struct running_hack *rh = w->window.rh;
1003
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;
1008
1009     glBindTexture (GL_TEXTURE_2D, src->texture);
1010
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);
1015     return;
1016   }
1017
1018 #if 1
1019   // Kumppa: 0.24 FPS
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.
1022
1023   rh->current_drawable = NULL;
1024   if (rh->gl_fbo_p)
1025     bind_drawable_fbo (rh, src);
1026   else
1027     eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx);
1028
1029   jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y,
1030                                      width, height, dst_x, dst_y);
1031
1032   if (rh->gl_fbo_p)
1033     bind_drawable_fbo (rh, dst);
1034   finish_bind_drawable (dpy, dst);
1035
1036   jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y, width, height,
1037                                       dst_x, dst_y);
1038
1039 #else
1040   // Kumppa: 0.17 FPS
1041   jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
1042                                   width, height, dst_x, dst_y);
1043 #endif
1044   jwxyz_assert_gl ();
1045 }
1046
1047
1048 void
1049 jwxyz_assert_drawable (Window main_window, Drawable d)
1050 {
1051   check_gl_error("jwxyz_assert_drawable");
1052 }
1053
1054
1055 void
1056 jwxyz_assert_gl (void)
1057 {
1058   check_gl_error("jwxyz_assert_gl");
1059 }
1060
1061
1062 Pixmap
1063 XCreatePixmap (Display *dpy, Drawable d,
1064                unsigned int width, unsigned int height, unsigned int depth)
1065 {
1066   Window win = XRootWindow(dpy, 0);
1067
1068   Pixmap p = malloc(sizeof(*p));
1069   p->type = PIXMAP;
1070   p->frame.x = 0;
1071   p->frame.y = 0;
1072   p->frame.width = width;
1073   p->frame.height = height;
1074
1075   Assert(depth == 1 || depth == visual_depth(NULL, NULL),
1076          "XCreatePixmap: bad depth");
1077   p->pixmap.depth = depth;
1078
1079   create_pixmap (win, p);
1080
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);
1085
1086   return p;
1087 }
1088
1089
1090 int
1091 XFreePixmap (Display *d, Pixmap p)
1092 {
1093   struct running_hack *rh = XRootWindow(d, 0)->window.rh;
1094   if (rh->current_drawable == p)
1095     rh->current_drawable = NULL;
1096
1097   free_pixmap (rh, p);
1098   free (p);
1099   return 0;
1100 }
1101
1102
1103 double
1104 current_device_rotation (void)
1105 {
1106   return current_rotation;
1107 }
1108
1109 Bool
1110 ignore_rotation_p (Display *dpy)
1111 {
1112   struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
1113   return rh->ignore_rotation_p;
1114 }
1115
1116
1117 static char *
1118 jstring_dup (JNIEnv *env, jstring str)
1119 {
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);
1124   if (result) {
1125     memcpy (result, cstr, len);
1126   }
1127   (*env)->ReleaseStringUTFChars (env, str, cstr);
1128   return result;
1129 }
1130
1131
1132 static char *
1133 get_string_resource_window (Window window, char *name)
1134 {
1135   JNIEnv *env = window->window.rh->jni_env;
1136   jobject obj = window->window.rh->jobject;
1137
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();
1144
1145   jstring jvalue = (m
1146                   ? (*env)->CallObjectMethod (env, obj, m, jstr)
1147                   : NULL);
1148   (*env)->DeleteLocalRef (env, c);
1149   (*env)->DeleteLocalRef (env, jstr);
1150   char *ret = 0;
1151   if (jvalue)
1152     ret = jstring_dup (env, jvalue);
1153
1154   Log("pref %s = %s", name, (ret ? ret : "(null)"));
1155   return ret;
1156 }
1157
1158
1159 char *
1160 get_string_resource (Display *dpy, char *name, char *class)
1161 {
1162   return get_string_resource_window (RootWindow (dpy, 0), name);
1163 }
1164
1165
1166 /* Returns the contents of the URL. */
1167 char *
1168 textclient_mobile_url_string (Display *dpy, const char *url)
1169 {
1170   Window window = RootWindow (dpy, 0);
1171   JNIEnv *env = window->window.rh->jni_env;
1172   jobject obj = window->window.rh->jobject;
1173
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();
1179   jobject buf = (m
1180                  ? (*env)->CallObjectMethod (env, obj, m, jstr)
1181                  : NULL);
1182   (*env)->DeleteLocalRef (env, c);
1183   (*env)->DeleteLocalRef (env, jstr);
1184
1185   char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1186   char *body2;
1187   if (body) {
1188     int L = (*env)->GetDirectBufferCapacity (env, buf);
1189     body2 = malloc (L + 1);
1190     memcpy (body2, body, L);
1191     body2[L] = 0;
1192   } else {
1193     body2 = strdup ("ERROR");
1194   }
1195
1196   if (buf)
1197     (*env)->DeleteLocalRef (env, buf);
1198
1199   return body2;
1200 }
1201
1202
1203 float
1204 jwxyz_scale (Window main_window)
1205 {
1206   return 2;
1207 }
1208
1209
1210 const char *
1211 jwxyz_default_font_family (int require)
1212 {
1213   /* Font families in XLFDs are totally ignored (for now). */
1214   return "sans-serif";
1215 }
1216
1217
1218 void *
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)
1225 {
1226   JNIEnv *env = window->window.rh->jni_env;
1227   jobject obj = window->window.rh->jobject;
1228
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);
1235     free(name_nul);
1236   }
1237
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();
1242
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)
1247                         : NULL);
1248
1249   (*env)->DeleteLocalRef (env, c);
1250
1251   if (array) {
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();
1258
1259     if (family_name_ret)
1260       *family_name_ret = jstring_dup (env, family_name);
1261
1262     jobject paint = (*env)->NewGlobalRef (env, font);
1263     if ((*env)->ExceptionOccurred(env)) abort();
1264
1265     c = (*env)->GetObjectClass(env, asc);
1266     m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1267     if ((*env)->ExceptionOccurred(env)) abort();
1268
1269     *ascent_ret  = (int) (*env)->CallFloatMethod (env, asc,  m);
1270     *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1271
1272     return (void *) paint;
1273   } else {
1274     return 0;
1275   }
1276 }
1277
1278
1279 void
1280 jwxyz_release_native_font (Display *dpy, void *native_font)
1281 {
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();
1287 }
1288
1289
1290 /* If the local reference table fills up, use this to figure out where
1291    you missed a call to DeleteLocalRef. */
1292 /*
1293 static void dump_reference_tables(JNIEnv *env)
1294 {
1295   jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1296   jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1297                                            "()V");
1298   (*env)->CallStaticVoidMethod (env, c, m);
1299   (*env)->DeleteLocalRef (env, c);
1300 }
1301 */
1302
1303
1304 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1305 // string.  If pixmap_ret is provided, also renders the text.
1306 //
1307 void
1308 jwxyz_render_text (Display *dpy, void *native_font,
1309                    const char *str, size_t len, int utf8,
1310                    XCharStruct *cs, char **pixmap_ret)
1311 {
1312   Window window = RootWindow (dpy, 0);
1313   JNIEnv *env = window->window.rh->jni_env;
1314   jobject obj = window->window.rh->jobject;
1315
1316   char *s2;
1317
1318   if (utf8) {
1319     s2 = malloc (len + 1);
1320     memcpy (s2, str, len);
1321     s2[len] = 0;
1322   } else {      // Convert Latin1 to UTF8
1323     s2 = malloc (len * 2 + 1);
1324     unsigned char *s3 = (unsigned char *) s2;
1325     int i;
1326     for (i = 0; i < len; i++) {
1327       unsigned char c = ((unsigned char *) str)[i];
1328       if (! (c & 0x80)) {
1329         *s3++ = c;
1330       } else {
1331         *s3++ = (0xC0 | (0x03 & (c >> 6)));
1332         *s3++ = (0x80 | (0x3F & c));
1333       }
1334     }
1335     *s3 = 0;
1336   }
1337
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();
1343   jobject buf =
1344     (m
1345      ? (*env)->CallObjectMethod (env, obj, m,
1346                                  (jobject) native_font,
1347                                  jstr,
1348                                  (pixmap_ret ? JNI_TRUE : JNI_FALSE))
1349      : NULL);
1350   (*env)->DeleteLocalRef (env, c);
1351   (*env)->DeleteLocalRef (env, jstr);
1352   free (s2);
1353
1354   if ((*env)->ExceptionOccurred(env)) abort();
1355   unsigned char *bits = (unsigned char *)
1356     (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1357   if (bits) {
1358     int i = 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;
1366
1367     if (pixmap_ret) {
1368       char *pix = malloc (L - i);
1369       if (! pix) abort();
1370       memcpy (pix, bits + i, L - i);
1371       *pixmap_ret = pix;
1372     }
1373   } else {
1374     memset (cs, 0, sizeof(*cs));
1375     if (pixmap_ret)
1376       *pixmap_ret = 0;
1377   }
1378
1379   if (buf)
1380     (*env)->DeleteLocalRef (env, buf);
1381 }
1382
1383
1384 char *
1385 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1386 {
1387   JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1388   /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1389   jclass
1390     c = (*env)->FindClass (env, "java/lang/Character"),
1391     c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
1392
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);
1397   if (exc) {
1398     if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1399       (*env)->ExceptionClear (env);
1400       Assert (!m, "jwxyz_unicode_character_name: m?");
1401     } else {
1402       abort();
1403     }
1404   }
1405
1406   char *ret = NULL;
1407
1408   if (m) {
1409     jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1410     if (name)
1411      ret = jstring_dup (env, name);
1412   }
1413
1414   if (!ret) {
1415     asprintf(&ret, "U+%.4lX", uc);
1416   }
1417
1418   return ret;
1419 }
1420
1421
1422 /* Called from utils/grabclient.c */
1423 char *
1424 jwxyz_load_random_image (Display *dpy,
1425                          int *width_ret, int *height_ret,
1426                          char **name_ret)
1427 {
1428
1429   /* This function needs to be implemented for Android */
1430   return 0;
1431
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;
1436
1437   Bool images_p =
1438     get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1439   Bool grab_p =
1440     get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1441   Bool rotate_p =
1442     get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1443
1444   if (!images_p && !grab_p)
1445     return 0;
1446
1447   if (grab_p && images_p) {
1448     grab_p = !(random() & 5);    /* if both, screenshot 1/5th of the time */
1449     images_p = !grab_p;
1450   }
1451
1452   jclass      c = (*env)->GetObjectClass (env, obj);
1453   jmethodID   m = (*env)->GetMethodID (env, c, 
1454                                        (grab_p
1455                                         ? "getScreenshot"
1456                                         : "loadRandomImage"),
1457                                        "(IIZ)Ljava/nio/ByteBuffer;");
1458   if ((*env)->ExceptionOccurred(env)) abort();
1459   jobject buf = (m
1460                  ? (*env)->CallObjectMethod (env, obj, m,
1461                                              window->frame.width,
1462                                              window->frame.height,
1463                                              (rotate_p ? JNI_TRUE : JNI_FALSE))
1464                  : NULL);
1465   (*env)->DeleteLocalRef (env, c);
1466
1467   unsigned char *bits = (unsigned char *)
1468     (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1469
1470   if (bits) {
1471     int i = 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);
1478     i += L2 + 1;
1479     if (width * height * 4 != L - i) abort();
1480     char *pix = malloc (L - i);
1481     if (! pix) abort();
1482     memcpy (pix, bits + i, L - i);
1483     *width_ret  = width;
1484     *height_ret = height;
1485     *name_ret   = strdup (name);
1486     return (char *) pix;
1487   }
1488
1489   if (buf)
1490     (*env)->DeleteLocalRef (env, buf);
1491
1492   return 0;
1493 }
1494
1495 #endif /* HAVE_ANDROID */