From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / jwxyz / jwxyz-android.c
1 /* xscreensaver, Copyright (c) 2016-2018 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 #ifdef HAVE_ANDROID /* whole file */
20
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <time.h>
25 #include <math.h>
26 #include <setjmp.h>
27
28 #include <GLES/gl.h>
29 #include <GLES/glext.h>
30 #include <jni.h>
31 #include <android/bitmap.h>
32 #include <android/log.h>
33 #include <android/native_window_jni.h>
34 #include <pthread.h>
35
36 #include "screenhackI.h"
37 #include "jwxyzI.h"
38 #include "jwzglesI.h"
39 #include "jwxyz-android.h"
40 #include "textclient.h"
41 #include "grabscreen.h"
42 #include "pow2.h"
43
44
45 #define countof(x) (sizeof(x)/sizeof(*(x)))
46
47 extern struct xscreensaver_function_table *xscreensaver_function_table;
48
49 struct function_table_entry {
50   const char *progname;
51   struct xscreensaver_function_table *xsft;
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   if (win->window.rh->jwxyz_gl_p) {
164     struct running_hack *rh = win->window.rh;
165
166     if (rh->gl_fbo_p) {
167       glGenTextures (1, &p->texture);
168       glBindTexture (GL_TEXTURE_2D, p->texture);
169
170       glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
171                     to_pow2(p->frame.width), to_pow2(p->frame.height),
172                     0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
173
174       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
175       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
176     } else {
177       EGLint attribs[5];
178       attribs[0] = EGL_WIDTH;
179       attribs[1] = p->frame.width;
180       attribs[2] = EGL_HEIGHT;
181       attribs[3] = p->frame.height;
182       attribs[4] = EGL_NONE;
183
184       p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
185                                              attribs);
186       Assert (p->egl_surface != EGL_NO_SURFACE,
187               "XCreatePixmap: got EGL_NO_SURFACE");
188     }
189   } else {
190     p->image_data = malloc (p->frame.width * p->frame.height * 4);
191   }
192 }
193
194
195 static void
196 free_pixmap (struct running_hack *rh, Pixmap p)
197 {
198   if (rh->jwxyz_gl_p) {
199     if (rh->gl_fbo_p) {
200       glDeleteTextures (1, &p->texture);
201     } else {
202       eglDestroySurface(rh->egl_display, p->egl_surface);
203     }
204   } else {
205     free (p->image_data);
206   }
207 }
208
209
210 static void
211 prepare_context (struct running_hack *rh)
212 {
213   if (rh->egl_p) {
214     /* TODO: Adreno recommends against doing this every frame. */
215     Assert (eglMakeCurrent(rh->egl_display, rh->egl_surface, rh->egl_surface,
216                            rh->egl_ctx),
217             "eglMakeCurrent failed");
218   }
219
220     /* Don't set matrices here; set them when an Xlib call triggers
221        jwxyz_bind_drawable/jwxyz_set_matrices.
222      */
223   if (rh->jwxyz_gl_p)
224     rh->current_drawable = NULL;
225
226   if (rh->xsft->visual == GL_VISUAL)
227     jwzgles_make_current (rh->gles_state);
228 }
229
230
231 // Initialized OpenGL and runs the screenhack's init function.
232 //
233 static void
234 doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
235         const struct function_table_entry *chosen,
236         jobject defaults, jint w, jint h, jobject jni_surface)
237 {
238   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
239
240   progname = chosen->progname;
241   rh->xsft = chosen->xsft;
242   rh->jni_env = env;
243   rh->jobject = jwxyz_obj;  // update this every time we call into C
244
245   (*env)->GetJavaVM (env, &global_jvm);
246
247 # undef ya_rand_init  // This is the one and only place it is allowed
248   ya_rand_init (0);
249
250   Window wnd = (Window) calloc(1, sizeof(*wnd));
251   wnd->window.rh = rh;
252   wnd->frame.width = w;
253   wnd->frame.height = h;
254   wnd->type = WINDOW;
255
256   rh->window = wnd;
257   progclass = rh->xsft->progclass;
258
259   if ((*env)->ExceptionOccurred(env)) abort();
260
261   // This has to come before resource processing. It does not do graphics.
262   if (rh->xsft->setup_cb)
263     rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);
264
265   if ((*env)->ExceptionOccurred(env)) abort();
266
267   // Load the defaults.
268   // Unceremoniously stolen from [PrefsReader defaultsToDict:].
269
270   jclass     c = (*env)->GetObjectClass (env, defaults);
271   jmethodID  m = (*env)->GetMethodID (env, c, "put",
272                  "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
273   if (! m) abort();
274   if ((*env)->ExceptionOccurred(env)) abort();
275
276   const struct { const char *key, *val; } default_defaults[] = {
277     { "doubleBuffer", "True" },
278     { "multiSample",  "False" },
279     { "texFontCacheSize", "30" },
280     { "textMode", "date" },
281     { "textURL",
282       "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
283     { "grabDesktopImages",  "True" },
284     { "chooseRandomImages", "True" },
285   };
286
287   for (int i = 0; i < countof(default_defaults); i++) {
288     const char *key = default_defaults[i].key;
289     const char *val = default_defaults[i].val;
290     char *key2 = malloc (strlen(progname) + strlen(key) + 2);
291     strcpy (key2, progname);
292     strcat (key2, "_");
293     strcat (key2, key);
294
295     // defaults.put(key2, val);
296     jstring jkey = (*env)->NewStringUTF (env, key2);
297     jstring jval = (*env)->NewStringUTF (env, val);
298     (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
299     (*env)->DeleteLocalRef (env, jkey);
300     (*env)->DeleteLocalRef (env, jval);
301     // Log ("default0: \"%s\" = \"%s\"", key2, val);
302     free (key2);
303   }
304
305   const char *const *defs = rh->xsft->defaults;
306   while (*defs) {
307     char *line = strdup (*defs);
308     char *key, *val;
309     key = line;
310     while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
311       key++;
312     val = key;
313     while (*val && *val != ':')
314       val++;
315     if (*val != ':') abort();
316     *val++ = 0;
317     while (*val == ' ' || *val == '\t')
318       val++;
319
320     unsigned long L = strlen(val);
321     while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
322       val[--L] = 0;
323
324     char *key2 = malloc (strlen(progname) + strlen(key) + 2);
325     strcpy (key2, progname);
326     strcat (key2, "_");
327     strcat (key2, key);
328
329     // defaults.put(key2, val);
330     jstring jkey = (*env)->NewStringUTF (env, key2);
331     jstring jval = (*env)->NewStringUTF (env, val);
332     (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
333     (*env)->DeleteLocalRef (env, jkey);
334     (*env)->DeleteLocalRef (env, jval);
335     // Log ("default: \"%s\" = \"%s\"", key2, val);
336     free (key2);
337     free (line);
338     defs++;
339   }
340
341   (*env)->DeleteLocalRef (env, c);
342   if ((*env)->ExceptionOccurred(env)) abort();
343
344
345   /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */
346
347   /* ####: This is lame, use a resource. */
348   rh->jwxyz_gl_p =
349     rh->xsft->visual == DEFAULT_VISUAL &&
350     strcmp (progname, "kumppa") &&
351     strcmp (progname, "petri") &&
352     strcmp (progname, "slip") &&
353     strcmp (progname, "testx11");
354
355   Log ("init: %s @ %dx%d: using JWXYZ_%s", progname, w, h,
356        rh->jwxyz_gl_p ? "GL" : "IMAGE");
357
358   rh->egl_p = rh->jwxyz_gl_p || rh->xsft->visual == GL_VISUAL;
359
360   if (rh->egl_p) {
361   // GL init. Must come after resource processing.
362
363     rh->egl_display = eglGetDisplay (EGL_DEFAULT_DISPLAY);
364     Assert (rh->egl_display != EGL_NO_DISPLAY, "init: EGL_NO_DISPLAY");
365
366     int egl_major, egl_minor;
367     Assert (eglInitialize (rh->egl_display, &egl_major, &egl_minor),
368             "eglInitialize failed");
369
370     // TODO: Skip depth and (probably) alpha for Xlib.
371     // TODO: Could ask for EGL_SWAP_BEHAVIOR_PRESERVED_BIT here...maybe?
372     // TODO: Probably should try to ask for EGL_PBUFFER_BIT.
373     // TODO: Do like visual-gl.c and work from a list of configs.
374     /* Probably don't need EGL_FRAMEBUFFER_TARGET_ANDROID here if GLSurfaceView
375        doesn't use it.
376      */
377     EGLint config_attribs[] = {
378        EGL_RED_SIZE, 8,
379        EGL_GREEN_SIZE, 8,
380        EGL_BLUE_SIZE, 8,
381        EGL_ALPHA_SIZE, 8,
382        EGL_DEPTH_SIZE, 16,
383        EGL_NONE
384     };
385
386     EGLint num_config;
387     Assert (eglChooseConfig (rh->egl_display, config_attribs,
388                              &rh->egl_config, 1, &num_config),
389             "eglChooseConfig failed");
390     Assert (num_config == 1, "no EGL config chosen");
391
392     EGLint no_attribs[] = {EGL_NONE};
393     rh->egl_ctx = eglCreateContext (rh->egl_display, rh->egl_config,
394                                     EGL_NO_CONTEXT, no_attribs);
395     Assert (rh->egl_ctx != EGL_NO_CONTEXT, "init: EGL_NO_CONTEXT");
396
397     ANativeWindow *native_window =
398       ANativeWindow_fromSurface (env, jni_surface);
399
400     rh->egl_surface = eglCreateWindowSurface (rh->egl_display, rh->egl_config,
401                                               native_window, no_attribs);
402     Assert (rh->egl_surface != EGL_NO_SURFACE, "init: EGL_NO_SURFACE");
403
404     ANativeWindow_release (native_window);
405   } else {
406     rh->native_window = ANativeWindow_fromSurface (env, jni_surface);
407
408     int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
409                                                    WINDOW_FORMAT_RGBX_8888);
410     if (result < 0) {
411       // Maybe check this earlier?
412       Log ("can't set format (%d), surface may be invalid.", result);
413       (*env)->ThrowNew (env,
414         (*env)->FindClass(env, "org/jwz/xscreensaver/jwxyz$SurfaceLost"),
415         "Surface lost");
416
417       ANativeWindow_release (rh->native_window);
418       rh->native_window = NULL;
419       return;
420     }
421   }
422
423   prepare_context (rh);
424
425   if (rh->egl_p) {
426     Log ("init %s / %s / %s",
427          glGetString (GL_VENDOR),
428          glGetString (GL_RENDERER),
429          glGetString (GL_VERSION));
430   }
431
432   if (rh->jwxyz_gl_p) {
433     const GLubyte *extensions = glGetString (GL_EXTENSIONS);
434     rh->gl_fbo_p = jwzgles_gluCheckExtension (
435       (const GLubyte *)"GL_OES_framebuffer_object", extensions);
436
437     if (rh->gl_fbo_p) {
438       glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default);
439       Assert (!rh->fb_default, "default framebuffer not current framebuffer");
440       glGenFramebuffersOES (1, &rh->fb_pixmap);
441       wnd->texture = 0;
442     } else {
443       wnd->egl_surface = rh->egl_surface;
444     }
445
446     rh->frontbuffer_p = False;
447
448     if (rh->xsft->visual == DEFAULT_VISUAL ||
449         (rh->xsft->visual == GL_VISUAL &&
450          strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
451
452       rh->frontbuffer_p = True;
453
454 # if 0 /* Might need to be 0 for Adreno...? */
455       if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
456         EGLint surface_type;
457         eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
458                            &surface_type);
459         if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
460           eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR,
461                            EGL_BUFFER_PRESERVED);
462           rh->frontbuffer_p = False;
463         }
464       }
465 # endif
466
467       if (rh->frontbuffer_p) {
468         /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
469         create_pixmap (wnd, wnd);
470
471         /* No preserving backbuffers, so manual blit from back to "front". */
472         rh->frontbuffer.type = PIXMAP;
473         rh->frontbuffer.frame = wnd->frame;
474         rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL);
475
476         if(rh->gl_fbo_p) {
477           rh->frontbuffer.texture = 0;
478         } else {
479           Assert (wnd->egl_surface != rh->egl_surface,
480                   "oops: wnd->egl_surface == rh->egl_surface");
481           rh->frontbuffer.egl_surface = rh->egl_surface;
482         }
483       }
484     }
485
486     rh->dpy = jwxyz_gl_make_display(wnd);
487
488   } else {
489
490     if (rh->xsft->visual == DEFAULT_VISUAL)
491       create_pixmap (wnd, wnd);
492     else
493       wnd->image_data = NULL;
494
495     static const unsigned char rgba_bytes[] = {0, 1, 2, 3};
496     rh->dpy = jwxyz_image_make_display(wnd, rgba_bytes);
497
498   }
499
500   Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
501   // TODO: Zero looks right, but double-check that is the right number
502
503   /* Requires valid rh->dpy. */
504   if (rh->jwxyz_gl_p)
505     rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
506
507   if (rh->xsft->visual == GL_VISUAL)
508     rh->gles_state = jwzgles_make_state();
509  END: ;
510 }
511
512
513 #undef DEBUG_FPS
514
515 #ifdef DEBUG_FPS
516
517 static double
518 double_time (void)
519 {
520   struct timeval now;
521 # ifdef GETTIMEOFDAY_TWO_ARGS
522   struct timezone tzp;
523   gettimeofday(&now, &tzp);
524 # else
525   gettimeofday(&now);
526 # endif
527
528   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
529 }
530
531 #endif
532
533 // Animates a single frame of the current hack.
534 //
535 static jlong
536 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
537 {
538 # ifdef DEBUG_FPS
539   double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
540   fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
541 # endif
542
543   unsigned long delay = 0;
544
545   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
546
547   Window wnd = rh->window;
548
549   prepare_context (rh);
550
551   if (rh->egl_p) {
552     /* There is some kind of weird redisplay race condition between Settings
553        and the launching hack: e.g., Abstractile does XClearWindow at init,
554        but the screen is getting filled with random bits.  So let's wait a
555        few frames before really starting up.
556
557        TODO: Is this still true?
558      */
559     if (++rh->frame_count < 8) {
560       /* glClearColor (1.0, 0.0, 1.0, 0.0); */
561       glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */
562       goto END;
563     }
564   }
565
566 # ifdef DEBUG_FPS
567   fps1 = double_time();
568 # endif
569
570   // The init function might do graphics (e.g. XClearWindow) so it has
571   // to be run from inside onDrawFrame, not onSurfaceChanged.
572
573   if (! rh->initted_p) {
574
575     void *(*init_cb) (Display *, Window, void *) =
576       (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
577
578     if (rh->xsft->visual == DEFAULT_VISUAL) {
579       unsigned int bg =
580         get_pixel_resource (rh->dpy, 0, "background", "Background");
581       XSetWindowBackground (rh->dpy, wnd, bg);
582       XClearWindow (rh->dpy, wnd);
583     }
584
585     rh->closure = init_cb (rh->dpy, wnd, rh->xsft->setup_arg);
586     rh->initted_p = True;
587
588     /* ignore_rotation_p doesn't quite work at the moment. */
589     rh->ignore_rotation_p = False;
590 /*
591       (rh->xsft->visual == DEFAULT_VISUAL &&
592        get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
593 */
594
595     if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
596       rh->fpst = fps_init (rh->dpy, wnd);
597       if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
598     } else {
599       rh->fpst = NULL;
600       rh->xsft->fps_cb = 0;
601     }
602
603     if ((*env)->ExceptionOccurred(env)) abort();
604   }
605
606 # ifdef DEBUG_FPS
607   fps2 = double_time();
608 # endif
609
610   // Apparently events don't come in on the drawing thread, and JNI flips
611   // out.  So we queue them there and run them here.
612   // TODO: Events should be coming in on the drawing thread now, so dump this.
613   send_queued_events (rh);
614
615 # ifdef DEBUG_FPS
616   fps3 = double_time();
617 # endif
618
619   delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure);
620
621   if (rh->jwxyz_gl_p)
622     jwxyz_gl_flush (rh->dpy);
623
624 # ifdef DEBUG_FPS
625   fps4 = double_time();
626 # endif
627   if (rh->fpst && rh->xsft->fps_cb)
628     rh->xsft->fps_cb (rh->dpy, wnd, rh->fpst, rh->closure);
629
630   if (rh->egl_p) {
631     if (rh->jwxyz_gl_p && rh->frontbuffer_p) {
632       jwxyz_gl_copy_area (rh->dpy, wnd, &rh->frontbuffer, rh->copy_gc,
633                           0, 0, wnd->frame.width, wnd->frame.height,
634                           0, 0);
635     }
636
637     // Getting failure here before/during/after resize, sometimes. Log sez:
638     // W/Adreno-EGLSUB(18428): <DequeueBuffer:607>: dequeue native buffer fail: No such device, buffer=0x5f93bf5c, handle=0x0
639     if (!eglSwapBuffers(rh->egl_display, rh->egl_surface)) {
640       Log ("eglSwapBuffers failed: 0x%x (probably asynchronous resize)",
641            eglGetError());
642     }
643   } else {
644     ANativeWindow_Buffer buffer;
645     ARect rect = {0, 0, wnd->frame.width, wnd->frame.height};
646     int32_t result = ANativeWindow_lock(rh->native_window, &buffer, &rect);
647     if (result) {
648       Log ("ANativeWindow_lock failed (result = %d), frame dropped", result);
649     } else {
650       /* Android can resize surfaces asynchronously. */
651       if (wnd->frame.width != buffer.width ||
652           wnd->frame.height != buffer.height) {
653         Log ("buffer/window size mismatch: %dx%d (format = %d), wnd: %dx%d",
654              buffer.width, buffer.height, buffer.format,
655              wnd->frame.width, wnd->frame.height);
656       }
657
658       Assert (buffer.format == WINDOW_FORMAT_RGBA_8888 ||
659               buffer.format == WINDOW_FORMAT_RGBX_8888,
660               "bad buffer format");
661
662       jwxyz_blit (wnd->image_data, jwxyz_image_pitch (wnd), 0, 0,
663                   buffer.bits, buffer.stride * 4, 0, 0,
664                   MIN(wnd->frame.width, buffer.width),
665                   MIN(wnd->frame.height, buffer.height));
666       // TODO: Clear any area to sides and bottom.
667
668       ANativeWindow_unlockAndPost (rh->native_window);
669     }
670   }
671
672  END: ;
673
674 # ifdef DEBUG_FPS
675   Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
676       (int) ((fps1-fps0)*1000000),
677       (int) ((fps2-fps1)*1000000),
678       (int) ((fps3-fps2)*1000000),
679       (int) ((fps4-fps3)*1000000),
680       (int) ((double_time()-fps4)*1000000));
681 # endif
682
683   return delay;
684 }
685
686
687 // Extracts the C structure that is stored in the jwxyz Java object.
688 static struct running_hack *
689 getRunningHack (JNIEnv *env, jobject thiz)
690 {
691   jlong result = (*env)->GetLongField (env, thiz, runningHackField);
692   struct running_hack *rh = (struct running_hack *)(intptr_t)result;
693   if (rh)
694     rh->jobject = thiz;  // update this every time we call into C
695   return rh;
696 }
697
698 // Look up a class and mark it global in the provided variable.
699 static jclass
700 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
701 {
702   jclass clazz = (*env)->FindClass(env, className);
703   *globalRef = (*env)->NewGlobalRef(env, clazz);
704   return clazz;
705 }
706
707
708 /* Note: to find signature strings for native methods:
709    cd ./project/xscreensaver/build/intermediates/classes/debug/
710    javap -s -p org.jwz.xscreensaver.jwxyz
711  */
712
713
714 // Implementation of jwxyz's nativeInit Java method.
715 //
716 JNIEXPORT void JNICALL
717 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
718                                             jstring jhack, jobject defaults,
719                                             jint w, jint h,
720                                             jobject jni_surface)
721 {
722   pthread_mutex_lock(&mutg);
723
724   struct running_hack *rh = calloc(1, sizeof(struct running_hack));
725
726   if ((*env)->ExceptionOccurred(env)) abort();
727
728   // #### simplify
729   if (!classRefCount) {
730     jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
731     globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
732     runningHackField = (*env)->GetFieldID
733       (env, classjwxyz, "nativeRunningHackPtr", "J");
734     if ((*env)->ExceptionOccurred(env)) abort();
735
736     jclass classIterable =
737       acquireClass(env, "java/lang/Iterable", &globalRefIterable);
738     iterableIterator = (*env)->GetMethodID
739       (env, classIterable, "iterator", "()Ljava/util/Iterator;");
740     if ((*env)->ExceptionOccurred(env)) abort();
741
742     jclass classIterator =
743       acquireClass(env, "java/util/Iterator", &globalRefIterator);
744     iteratorHasNext = (*env)->GetMethodID
745       (env, classIterator, "hasNext", "()Z");
746     iteratorNext = (*env)->GetMethodID
747       (env, classIterator, "next", "()Ljava/lang/Object;");
748     if ((*env)->ExceptionOccurred(env)) abort();
749
750     jclass classMapEntry =
751       acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
752     entryGetKey = (*env)->GetMethodID
753       (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
754     entryGetValue = (*env)->GetMethodID
755       (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
756     if ((*env)->ExceptionOccurred(env)) abort();
757   }
758
759   ++classRefCount;
760
761   // Store the C struct into the Java object.
762   (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
763
764   // TODO: Sort the list so binary search works.
765   const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
766
767   int chosen = 0;
768   for (;;) {
769     if (chosen == countof(function_table)) {
770       Log ("Hack not found: %s", hack);
771       abort();
772     }
773     if (!strcmp(function_table[chosen].progname, hack))
774       break;
775     chosen++;
776   }
777
778   (*env)->ReleaseStringUTFChars(env, jhack, hack);
779
780   doinit (thiz, rh, env, &function_table[chosen], defaults, w, h,
781           jni_surface);
782
783   pthread_mutex_unlock(&mutg);
784 }
785
786
787 JNIEXPORT void JNICALL
788 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
789                                               jint w, jint h, jdouble rot)
790 {
791   pthread_mutex_lock(&mutg);
792   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
793
794   current_rotation = rot;
795
796   Log ("native rotation: %f", current_rotation);
797
798   struct running_hack *rh = getRunningHack(env, thiz);
799
800   prepare_context (rh);
801
802   if (rh->egl_p) {
803     glViewport (0, 0, w, h);
804   } else {
805     int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
806                                                    WINDOW_FORMAT_RGBX_8888);
807     if (result < 0)
808       Log ("failed to resize surface (%d)", result);
809   }
810
811   Window wnd = rh->window;
812   wnd->frame.x = 0;
813   wnd->frame.y = 0;
814   wnd->frame.width  = w;
815   wnd->frame.height = h;
816
817   if (ignore_rotation_p(rh->dpy) &&
818       rot != 0 && rot != 180 && rot != -180) {
819     int swap = w;
820     w = h;
821     h = swap;
822     wnd->frame.width  = w;
823     wnd->frame.height = h;
824   }
825
826   if (rh->jwxyz_gl_p) {
827     if (rh->frontbuffer_p) {
828       free_pixmap (rh, wnd);
829       create_pixmap (wnd, wnd);
830
831       rh->frontbuffer.frame = wnd->frame;
832       if (!rh->gl_fbo_p)
833         rh->frontbuffer.egl_surface = rh->egl_surface;
834     }
835
836     jwxyz_window_resized (rh->dpy);
837   } else if (rh->xsft->visual == DEFAULT_VISUAL) {
838     free_pixmap (rh, wnd);
839     create_pixmap (wnd, wnd);
840     XClearWindow (rh->dpy, wnd); // TODO: This is lame. Copy the bits.
841   }
842
843   if (rh->initted_p)
844     rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
845                           wnd->frame.width, wnd->frame.height);
846
847   if (rh->xsft->visual == GL_VISUAL) {
848     glMatrixMode (GL_PROJECTION);
849     glRotatef (-rot, 0, 0, 1);
850     glMatrixMode (GL_MODELVIEW);
851   }
852
853  END:
854   pthread_mutex_unlock(&mutg);
855 }
856
857
858 JNIEXPORT jlong JNICALL
859 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
860 {
861   pthread_mutex_lock(&mutg);
862   struct running_hack *rh = getRunningHack(env, thiz);
863   jlong result = drawXScreenSaver(env, rh);
864   pthread_mutex_unlock(&mutg);
865   return result;
866 }
867
868
869 // TODO: Check Java side is calling this properly
870 JNIEXPORT void JNICALL
871 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
872 {
873   pthread_mutex_lock(&mutg);
874   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
875
876   struct running_hack *rh = getRunningHack(env, thiz);
877
878   prepare_context (rh);
879
880   if (rh->initted_p)
881     rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
882   if (rh->jwxyz_gl_p)
883     XFreeGC (rh->dpy, rh->copy_gc);
884   if (rh->xsft->visual == GL_VISUAL)
885     jwzgles_free_state ();
886
887   if (rh->jwxyz_gl_p)
888     jwxyz_gl_free_display(rh->dpy);
889   else
890     jwxyz_image_free_display(rh->dpy);
891
892   if (rh->egl_p) {
893     // eglDestroy* probably isn't necessary here.
894     eglMakeCurrent (rh->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
895                     EGL_NO_CONTEXT);
896     eglDestroySurface (rh->egl_display, rh->egl_surface);
897     eglDestroyContext (rh->egl_display, rh->egl_ctx);
898     eglTerminate (rh->egl_display);
899   } else {
900     if (rh->xsft->visual == DEFAULT_VISUAL)
901       free_pixmap (rh, rh->window);
902     if (rh->native_window)
903       ANativeWindow_release (rh->native_window);
904   }
905
906   free(rh);
907   (*env)->SetLongField(env, thiz, runningHackField, 0);
908
909   --classRefCount;
910   if (!classRefCount) {
911     (*env)->DeleteGlobalRef(env, globalRefjwxyz);
912     (*env)->DeleteGlobalRef(env, globalRefIterable);
913     (*env)->DeleteGlobalRef(env, globalRefIterator);
914     (*env)->DeleteGlobalRef(env, globalRefMapEntry);
915   }
916
917  END:
918   pthread_mutex_unlock(&mutg);
919 }
920
921
922 static int
923 send_event (struct running_hack *rh, XEvent *e)
924 {
925   // Assumes mutex is locked and context is prepared
926
927   int *xP = 0, *yP = 0;
928   switch (e->xany.type) {
929   case ButtonPress: case ButtonRelease:
930     xP = &e->xbutton.x;
931     yP = &e->xbutton.y;
932     break;
933   case MotionNotify:
934     xP = &e->xmotion.x;
935     yP = &e->xmotion.y;
936     break;
937   }
938
939   // Rotate the coordinates in the events to match the pixels.
940   if (xP) {
941     if (ignore_rotation_p (rh->dpy)) {
942       Window win = XRootWindow (rh->dpy, 0);
943       int w = win->frame.width;
944       int h = win->frame.height;
945       int swap;
946       switch ((int) current_rotation) {
947       case 180: case -180:                              // #### untested
948         *xP = w - *xP;
949         *yP = h - *yP;
950         break;
951       case 90: case -270:
952         swap = *xP; *xP = *yP; *yP = swap;
953         *yP = h - *yP;
954         break;
955       case -90: case 270:                               // #### untested
956         swap = *xP; *xP = *yP; *yP = swap;
957         *xP = w - *xP;
958         break;
959       }
960     }
961
962     rh->window->window.last_mouse_x = *xP;
963     rh->window->window.last_mouse_y = *yP;
964   }
965
966   return (rh->xsft->event_cb
967           ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
968           : 0);
969 }
970
971
972 static void
973 send_queued_events (struct running_hack *rh)
974 {
975   struct event_queue *event, *next;
976   if (! rh->event_queue) return;
977   for (event = rh->event_queue, next = event->next;
978        event;
979        event = next, next = (event ? event->next : 0)) {
980     if (! send_event (rh, &event->event)) {
981       // #### flash the screen or something
982     }
983     free (event);
984   }
985   rh->event_queue = 0;
986 }
987
988
989 static void
990 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
991 {
992   pthread_mutex_lock (&mutg);
993   struct running_hack *rh = getRunningHack (env, thiz);
994   struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
995   memcpy (&q->event, e, sizeof(*e));
996   q->next = 0;
997
998   // Put it at the end.
999   struct event_queue *oq;
1000   for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
1001     ;
1002   if (oq)
1003     oq->next = q;
1004   else
1005     rh->event_queue = q;
1006
1007   pthread_mutex_unlock (&mutg);
1008 }
1009
1010
1011 JNIEXPORT void JNICALL
1012 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
1013                                                  int x, int y, jboolean down)
1014 {
1015   XEvent e;
1016   memset (&e, 0, sizeof(e));
1017   e.xany.type = (down ? ButtonPress : ButtonRelease);
1018   e.xbutton.button = Button1;
1019   e.xbutton.x = x;
1020   e.xbutton.y = y;
1021   queue_event (env, thiz, &e);
1022 }
1023
1024 JNIEXPORT void JNICALL
1025 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
1026                                                  int x, int y)
1027 {
1028   XEvent e;
1029   memset (&e, 0, sizeof(e));
1030   e.xany.type = MotionNotify;
1031   e.xmotion.x = x;
1032   e.xmotion.y = y;
1033   queue_event (env, thiz, &e);
1034 }
1035
1036 JNIEXPORT void JNICALL
1037 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
1038                                               jboolean down_p, 
1039                                               int code, int mods)
1040 {
1041   XEvent e;
1042   memset (&e, 0, sizeof(e));
1043   e.xkey.keycode = code;
1044   e.xkey.state = code;
1045   e.xany.type = (down_p ? KeyPress : KeyRelease);
1046   queue_event (env, thiz, &e);
1047   e.xany.type = KeyRelease;
1048   queue_event (env, thiz, &e);
1049 }
1050
1051
1052 /***************************************************************************
1053   Backend functions for jwxyz-gl.c
1054  */
1055
1056 static void
1057 finish_bind_drawable (Display *dpy, Drawable dst)
1058 {
1059   jwxyz_assert_gl ();
1060
1061   glViewport (0, 0, dst->frame.width, dst->frame.height);
1062   jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False);
1063 }
1064
1065
1066 static void
1067 bind_drawable_fbo (struct running_hack *rh, Drawable d)
1068 {
1069   glBindFramebufferOES (GL_FRAMEBUFFER_OES,
1070                         d->texture ? rh->fb_pixmap : rh->fb_default);
1071   if (d->texture) {
1072     glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
1073                                GL_TEXTURE_2D, d->texture, 0);
1074   }
1075 }
1076
1077
1078 void
1079 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
1080 {
1081   struct running_hack *rh = w->window.rh;
1082   JNIEnv *env = w->window.rh->jni_env;
1083   if ((*env)->ExceptionOccurred(env)) abort();
1084   if (rh->current_drawable != d) {
1085     if (rh->gl_fbo_p) {
1086       bind_drawable_fbo (rh, d);
1087     } else {
1088       eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx);
1089     }
1090     finish_bind_drawable (dpy, d);
1091     rh->current_drawable = d;
1092   }
1093 }
1094
1095 void
1096 jwxyz_gl_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
1097                     int src_x, int src_y,
1098                     unsigned int width, unsigned int height,
1099                     int dst_x, int dst_y)
1100 {
1101   Window w = XRootWindow (dpy, 0);
1102   struct running_hack *rh = w->window.rh;
1103
1104   jwxyz_gl_flush (dpy);
1105
1106   if (rh->gl_fbo_p && src->texture && src != dst) {
1107     bind_drawable_fbo (rh, dst);
1108     finish_bind_drawable (dpy, dst);
1109     rh->current_drawable = NULL;
1110
1111     jwxyz_gl_set_gc (dpy, gc);
1112
1113     glBindTexture (GL_TEXTURE_2D, src->texture);
1114
1115     jwxyz_gl_draw_image (dpy, gc, GL_TEXTURE_2D, to_pow2(src->frame.width),
1116                          to_pow2(src->frame.height),
1117                          src_x, src->frame.height - src_y - height,
1118                          jwxyz_drawable_depth (src), width, height,
1119                          dst_x, dst_y, False);
1120     return;
1121   }
1122
1123 #if 1
1124   // Kumppa: 0.24 FPS
1125   // Hilarious display corruption ahoy! (Note to self: it's on the emulator.)
1126   // TODO for Dave: Recheck behavior on the emulator with the better Pixmap support.
1127
1128   rh->current_drawable = NULL;
1129   if (rh->gl_fbo_p)
1130     bind_drawable_fbo (rh, src);
1131   else
1132     eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx);
1133
1134   jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y,
1135                                      width, height, dst_x, dst_y);
1136
1137   if (rh->gl_fbo_p)
1138     bind_drawable_fbo (rh, dst);
1139   finish_bind_drawable (dpy, dst);
1140
1141   jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y, width, height,
1142                                       dst_x, dst_y);
1143
1144 #else
1145   // Kumppa: 0.17 FPS
1146   jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
1147                                   width, height, dst_x, dst_y);
1148 #endif
1149   jwxyz_assert_gl ();
1150 }
1151
1152
1153 void
1154 jwxyz_assert_drawable (Window main_window, Drawable d)
1155 {
1156   check_gl_error("jwxyz_assert_drawable");
1157 }
1158
1159
1160 void
1161 jwxyz_assert_gl (void)
1162 {
1163   check_gl_error("jwxyz_assert_gl");
1164 }
1165
1166
1167 /***************************************************************************
1168   Backend functions for jwxyz-image.c
1169  */
1170
1171 ptrdiff_t
1172 jwxyz_image_pitch (Drawable d)
1173 {
1174   return d->frame.width * 4;
1175 }
1176
1177 void *
1178 jwxyz_image_data (Drawable d)
1179 {
1180   Assert (d->image_data, "no image storage (i.e. keep Xlib off the Window)");
1181   return d->image_data;
1182 }
1183
1184
1185 const XRectangle *
1186 jwxyz_frame (Drawable d)
1187 {
1188   return &d->frame;
1189 }
1190
1191
1192 unsigned int
1193 jwxyz_drawable_depth (Drawable d)
1194 {
1195   return (d->type == WINDOW
1196           ? visual_depth (NULL, NULL)
1197           : d->pixmap.depth);
1198 }
1199
1200
1201 void
1202 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
1203 {
1204   xvpos->x = 0;
1205   xvpos->y = 0;
1206
1207   if (xp) {
1208     xp->x = w->window.last_mouse_x;
1209     xp->y = w->window.last_mouse_y;
1210   }
1211 }
1212
1213
1214 static void
1215 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
1216 {
1217   fps_compute (fpst, 0, -1);
1218   fps_draw (fpst);
1219 }
1220
1221
1222 Pixmap
1223 XCreatePixmap (Display *dpy, Drawable d,
1224                unsigned int width, unsigned int height, unsigned int depth)
1225 {
1226   Window win = XRootWindow(dpy, 0);
1227
1228   Pixmap p = malloc(sizeof(*p));
1229   p->type = PIXMAP;
1230   p->frame.x = 0;
1231   p->frame.y = 0;
1232   p->frame.width = width;
1233   p->frame.height = height;
1234
1235   Assert(depth == 1 || depth == visual_depth(NULL, NULL),
1236          "XCreatePixmap: bad depth");
1237   p->pixmap.depth = depth;
1238
1239   create_pixmap (win, p);
1240
1241   /* For debugging. */
1242 # if 0
1243   jwxyz_bind_drawable (dpy, win, p);
1244   glClearColor (frand(1), frand(1), frand(1), 0);
1245   glClear (GL_COLOR_BUFFER_BIT);
1246 # endif
1247
1248   return p;
1249 }
1250
1251
1252 int
1253 XFreePixmap (Display *d, Pixmap p)
1254 {
1255   struct running_hack *rh = XRootWindow(d, 0)->window.rh;
1256
1257   if (rh->jwxyz_gl_p) {
1258     jwxyz_gl_flush (d);
1259
1260     if (rh->current_drawable == p)
1261       rh->current_drawable = NULL;
1262   }
1263
1264   free_pixmap (rh, p);
1265   free (p);
1266   return 0;
1267 }
1268
1269
1270 double
1271 current_device_rotation (void)
1272 {
1273   return current_rotation;
1274 }
1275
1276 Bool
1277 ignore_rotation_p (Display *dpy)
1278 {
1279   struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
1280   return rh->ignore_rotation_p;
1281 }
1282
1283
1284 static char *
1285 jstring_dup (JNIEnv *env, jstring str)
1286 {
1287   Assert (str, "expected jstring, not null");
1288   const char *cstr = (*env)->GetStringUTFChars (env, str, 0);
1289   size_t len = (*env)->GetStringUTFLength (env, str) + 1;
1290   char *result = malloc (len);
1291   if (result) {
1292     memcpy (result, cstr, len);
1293   }
1294   (*env)->ReleaseStringUTFChars (env, str, cstr);
1295   return result;
1296 }
1297
1298
1299 static char *
1300 get_string_resource_window (Window window, char *name)
1301 {
1302   JNIEnv *env = window->window.rh->jni_env;
1303   jobject obj = window->window.rh->jobject;
1304
1305   if ((*env)->ExceptionOccurred(env)) abort();
1306   jstring jstr = (*env)->NewStringUTF (env, name);
1307   jclass     c = (*env)->GetObjectClass (env, obj);
1308   jmethodID  m = (*env)->GetMethodID (env, c, "getStringResource",
1309                            "(Ljava/lang/String;)Ljava/lang/String;");
1310   if ((*env)->ExceptionOccurred(env)) abort();
1311
1312   jstring jvalue = (m
1313                   ? (*env)->CallObjectMethod (env, obj, m, jstr)
1314                   : NULL);
1315   (*env)->DeleteLocalRef (env, c);
1316   (*env)->DeleteLocalRef (env, jstr);
1317   char *ret = 0;
1318   if (jvalue)
1319     ret = jstring_dup (env, jvalue);
1320
1321   Log("pref %s = %s", name, (ret ? ret : "(null)"));
1322   return ret;
1323 }
1324
1325
1326 char *
1327 get_string_resource (Display *dpy, char *name, char *class)
1328 {
1329   return get_string_resource_window (RootWindow (dpy, 0), name);
1330 }
1331
1332
1333 /* Returns the contents of the URL. */
1334 char *
1335 textclient_mobile_url_string (Display *dpy, const char *url)
1336 {
1337   Window window = RootWindow (dpy, 0);
1338   JNIEnv *env = window->window.rh->jni_env;
1339   jobject obj = window->window.rh->jobject;
1340
1341   jstring jstr  = (*env)->NewStringUTF (env, url);
1342   jclass      c = (*env)->GetObjectClass (env, obj);
1343   jmethodID   m = (*env)->GetMethodID (env, c, "loadURL",
1344                             "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
1345   if ((*env)->ExceptionOccurred(env)) abort();
1346   jobject buf = (m
1347                  ? (*env)->CallObjectMethod (env, obj, m, jstr)
1348                  : NULL);
1349   (*env)->DeleteLocalRef (env, c);
1350   (*env)->DeleteLocalRef (env, jstr);
1351
1352   char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1353   char *body2;
1354   if (body) {
1355     int L = (*env)->GetDirectBufferCapacity (env, buf);
1356     body2 = malloc (L + 1);
1357     memcpy (body2, body, L);
1358     body2[L] = 0;
1359   } else {
1360     body2 = strdup ("ERROR");
1361   }
1362
1363   if (buf)
1364     (*env)->DeleteLocalRef (env, buf);
1365
1366   return body2;
1367 }
1368
1369
1370 float
1371 jwxyz_scale (Window main_window)
1372 {
1373   // TODO: Use the actual device resolution.
1374   return 2;
1375 }
1376
1377
1378 const char *
1379 jwxyz_default_font_family (int require)
1380 {
1381   /* Font families in XLFDs are totally ignored (for now). */
1382   return "sans-serif";
1383 }
1384
1385
1386 void *
1387 jwxyz_load_native_font (Window window,
1388                         int traits_jwxyz, int mask_jwxyz,
1389                         const char *font_name_ptr, size_t font_name_length,
1390                         int font_name_type, float size,
1391                         char **family_name_ret,
1392                         int *ascent_ret, int *descent_ret)
1393 {
1394   JNIEnv *env = window->window.rh->jni_env;
1395   jobject obj = window->window.rh->jobject;
1396
1397   jstring jname = NULL;
1398   if (font_name_ptr) {
1399     char *name_nul = malloc(font_name_length + 1);
1400     memcpy(name_nul, font_name_ptr, font_name_length);
1401     name_nul[font_name_length] = 0;
1402     jname = (*env)->NewStringUTF (env, name_nul);
1403     free(name_nul);
1404   }
1405
1406   jclass     c = (*env)->GetObjectClass (env, obj);
1407   jmethodID  m = (*env)->GetMethodID (env, c, "loadFont",
1408                            "(IILjava/lang/String;IF)[Ljava/lang/Object;");
1409   if ((*env)->ExceptionOccurred(env)) abort();
1410
1411   jobjectArray array = (m
1412                         ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
1413                                                     (jint)traits_jwxyz, jname,
1414                                                     (jint)font_name_type, (jfloat)size)
1415                         : NULL);
1416
1417   (*env)->DeleteLocalRef (env, c);
1418
1419   if (array) {
1420     jobject font = (*env)->GetObjectArrayElement (env, array, 0);
1421     jobject family_name =
1422       (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
1423     jobject asc  = (*env)->GetObjectArrayElement (env, array, 2);
1424     jobject desc = (*env)->GetObjectArrayElement (env, array, 3);
1425     if ((*env)->ExceptionOccurred(env)) abort();
1426
1427     if (family_name_ret)
1428       *family_name_ret = jstring_dup (env, family_name);
1429
1430     jobject paint = (*env)->NewGlobalRef (env, font);
1431     if ((*env)->ExceptionOccurred(env)) abort();
1432
1433     c = (*env)->GetObjectClass(env, asc);
1434     m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1435     if ((*env)->ExceptionOccurred(env)) abort();
1436
1437     *ascent_ret  = (int) (*env)->CallFloatMethod (env, asc,  m);
1438     *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1439
1440     return (void *) paint;
1441   } else {
1442     return 0;
1443   }
1444 }
1445
1446
1447 void
1448 jwxyz_release_native_font (Display *dpy, void *native_font)
1449 {
1450   Window window = RootWindow (dpy, 0);
1451   JNIEnv *env = window->window.rh->jni_env;
1452   if ((*env)->ExceptionOccurred(env)) abort();
1453   (*env)->DeleteGlobalRef (env, (jobject) native_font);
1454   if ((*env)->ExceptionOccurred(env)) abort();
1455 }
1456
1457
1458 /* If the local reference table fills up, use this to figure out where
1459    you missed a call to DeleteLocalRef. */
1460 /*
1461 static void dump_reference_tables(JNIEnv *env)
1462 {
1463   jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1464   jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1465                                            "()V");
1466   (*env)->CallStaticVoidMethod (env, c, m);
1467   (*env)->DeleteLocalRef (env, c);
1468 }
1469 */
1470
1471
1472 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1473 // string.  If pixmap_ret is provided, also renders the text.
1474 //
1475 void
1476 jwxyz_render_text (Display *dpy, void *native_font,
1477                    const char *str, size_t len, Bool utf8, Bool antialias_p,
1478                    XCharStruct *cs, char **pixmap_ret)
1479 {
1480   Window window = RootWindow (dpy, 0);
1481   JNIEnv *env = window->window.rh->jni_env;
1482   jobject obj = window->window.rh->jobject;
1483
1484   char *s2;
1485
1486   if (utf8) {
1487     s2 = malloc (len + 1);
1488     memcpy (s2, str, len);
1489     s2[len] = 0;
1490   } else {      // Convert Latin1 to UTF8
1491     s2 = malloc (len * 2 + 1);
1492     unsigned char *s3 = (unsigned char *) s2;
1493     int i;
1494     for (i = 0; i < len; i++) {
1495       unsigned char c = ((unsigned char *) str)[i];
1496       if (! (c & 0x80)) {
1497         *s3++ = c;
1498       } else {
1499         *s3++ = (0xC0 | (0x03 & (c >> 6)));
1500         *s3++ = (0x80 | (0x3F & c));
1501       }
1502     }
1503     *s3 = 0;
1504   }
1505
1506   jstring jstr  = (*env)->NewStringUTF (env, s2);
1507   jclass      c = (*env)->GetObjectClass (env, obj);
1508   jmethodID   m = (*env)->GetMethodID (env, c, "renderText",
1509     "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;");
1510   if ((*env)->ExceptionOccurred(env)) abort();
1511   jobject buf =
1512     (m
1513      ? (*env)->CallObjectMethod (env, obj, m,
1514                                  (jobject) native_font,
1515                                  jstr,
1516                                  (pixmap_ret ? JNI_TRUE : JNI_FALSE),
1517                                  antialias_p)
1518      : NULL);
1519   (*env)->DeleteLocalRef (env, c);
1520   (*env)->DeleteLocalRef (env, jstr);
1521   free (s2);
1522
1523   if ((*env)->ExceptionOccurred(env)) abort();
1524   unsigned char *bits = (unsigned char *)
1525     (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1526   if (bits) {
1527     int i = 0;
1528     int L = (*env)->GetDirectBufferCapacity (env, buf);
1529     if (L < 10) abort();
1530     cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1531     cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1532     cs->width    = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1533     cs->ascent   = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1534     cs->descent  = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1535
1536     if (pixmap_ret) {
1537       char *pix = malloc (L - i);
1538       if (! pix) abort();
1539       memcpy (pix, bits + i, L - i);
1540       *pixmap_ret = pix;
1541     }
1542   } else {
1543     memset (cs, 0, sizeof(*cs));
1544     if (pixmap_ret)
1545       *pixmap_ret = 0;
1546   }
1547
1548   if (buf)
1549     (*env)->DeleteLocalRef (env, buf);
1550 }
1551
1552
1553 char *
1554 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1555 {
1556   JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1557   /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1558   jclass
1559     c = (*env)->FindClass (env, "java/lang/Character"),
1560     c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
1561
1562   if ((*env)->ExceptionOccurred(env)) abort();
1563   jmethodID m = (*env)->GetStaticMethodID (
1564     env, c, "getName", "(I)Ljava/lang/String;");
1565   jthrowable exc = (*env)->ExceptionOccurred(env);
1566   if (exc) {
1567     if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1568       (*env)->ExceptionClear (env);
1569       Assert (!m, "jwxyz_unicode_character_name: m?");
1570     } else {
1571       abort();
1572     }
1573   }
1574
1575   char *ret = NULL;
1576
1577   if (m) {
1578     jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1579     if (name)
1580      ret = jstring_dup (env, name);
1581   }
1582
1583   if (!ret) {
1584     asprintf(&ret, "U+%.4lX", uc);
1585   }
1586
1587   return ret;
1588 }
1589
1590
1591 /* Called from utils/grabclient.c */
1592 char *
1593 jwxyz_draw_random_image (Display *dpy, Drawable drawable, GC gc)
1594 {
1595   Window window = RootWindow (dpy, 0);
1596   struct running_hack *rh = window->window.rh;
1597   JNIEnv *env = rh->jni_env;
1598   jobject obj = rh->jobject;
1599
1600   Bool images_p =
1601     get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1602   Bool grab_p =
1603     get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1604   Bool rotate_p =
1605     get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1606
1607   if (!images_p && !grab_p)
1608     return 0;
1609
1610   if (grab_p && images_p) {
1611     grab_p = !(random() & 5);    /* if both, screenshot 1/5th of the time */
1612     images_p = !grab_p;
1613   }
1614
1615   jclass      c = (*env)->GetObjectClass (env, obj);
1616   jmethodID   m = (*env)->GetMethodID (env, c, 
1617                                        (grab_p
1618                                         ? "getScreenshot"
1619                                         : "checkThenLoadRandomImage"),
1620                                        "(IIZ)[Ljava/lang/Object;");
1621   if ((*env)->ExceptionOccurred(env)) abort();
1622   jobjectArray img_name = (
1623     m
1624     ? (*env)->CallObjectMethod (env, obj, m,
1625                                 drawable->frame.width, drawable->frame.height,
1626                                 (rotate_p ? JNI_TRUE : JNI_FALSE))
1627     : NULL);
1628   if ((*env)->ExceptionOccurred(env)) abort();
1629   (*env)->DeleteLocalRef (env, c);
1630
1631   if (!img_name) {
1632     fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image"));
1633     return NULL;
1634   }
1635
1636   jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 0);
1637
1638   AndroidBitmapInfo bmp_info;
1639   AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1640
1641   XImage *img = XCreateImage (dpy, NULL, visual_depth(NULL, NULL),
1642                               ZPixmap, 0, NULL,
1643                               bmp_info.width, bmp_info.height, 0,
1644                               bmp_info.stride);
1645
1646   AndroidBitmap_lockPixels (env, jbitmap, (void **) &img->data);
1647
1648   XPutImage (dpy, drawable, gc, img, 0, 0,
1649              (drawable->frame.width  - bmp_info.width) / 2,
1650              (drawable->frame.height - bmp_info.height) / 2,
1651              bmp_info.width, bmp_info.height);
1652
1653   AndroidBitmap_unlockPixels (env, jbitmap);
1654   img->data = NULL;
1655   XDestroyImage (img);
1656
1657   return jstring_dup (env, (*env)->GetObjectArrayElement (env, img_name, 1));
1658 }
1659
1660
1661 XImage *
1662 jwxyz_png_to_ximage (Display *dpy, Visual *visual,
1663                      const unsigned char *png_data, unsigned long data_size)
1664 {
1665   Window window = RootWindow (dpy, 0);
1666   struct running_hack *rh = window->window.rh;
1667   JNIEnv *env = rh->jni_env;
1668   jobject obj = rh->jobject;
1669   jclass    c = (*env)->GetObjectClass (env, obj);
1670   jmethodID m = (*env)->GetMethodID (env, c, "decodePNG",
1671                                      "([B)Landroid/graphics/Bitmap;");
1672   if ((*env)->ExceptionOccurred(env)) abort();
1673   jbyteArray jdata = (*env)->NewByteArray (env, data_size);
1674   (*env)->SetByteArrayRegion (env, jdata, 0,
1675                               data_size, (const jbyte *) png_data);
1676   jobject jbitmap = (
1677     m
1678     ? (*env)->CallObjectMethod (env, obj, m, jdata)
1679     : NULL);
1680   if ((*env)->ExceptionOccurred(env)) abort();
1681   (*env)->DeleteLocalRef (env, c);
1682   (*env)->DeleteLocalRef (env, jdata);
1683   if (!jbitmap)
1684     return NULL;
1685
1686   AndroidBitmapInfo bmp_info;
1687   AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1688
1689   XImage *img = XCreateImage (dpy, NULL, 32, ZPixmap, 0, NULL,
1690                               bmp_info.width, bmp_info.height, 8,
1691                               bmp_info.stride);
1692   char *bits = 0;
1693   AndroidBitmap_lockPixels (env, jbitmap, (void **) &bits);
1694   img->data = (char *) calloc (img->bytes_per_line, img->height);
1695   memcpy (img->data, bits, img->bytes_per_line * img->height);
1696   AndroidBitmap_unlockPixels (env, jbitmap);
1697
1698   // Java should have returned ARGB data.
1699   // WTF, why isn't ANDROID_BITMAP_FORMAT_ARGB_8888 defined?
1700   if (bmp_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) abort();
1701 # ifndef __BYTE_ORDER__ // A GCC (and Clang)-ism.
1702 #  error Need a __BYTE_ORDER__.
1703 # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1704   img->byte_order = img->bitmap_bit_order = LSBFirst;
1705 # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1706   img->byte_order = img->bitmap_bit_order = MSBFirst;
1707 # else
1708 #  error Need a __BYTE_ORDER__.
1709 # endif
1710
1711   static const union {
1712     uint8_t bytes[4];
1713     uint32_t pixel;
1714   } c0 = {{0xff, 0x00, 0x00, 0x00}}, c1 = {{0x00, 0xff, 0x00, 0x00}},
1715     c2 = {{0x00, 0x00, 0xff, 0x00}};
1716
1717   img->red_mask   = c0.pixel;
1718   img->green_mask = c1.pixel;
1719   img->blue_mask  = c2.pixel;
1720
1721   return img;
1722 }
1723
1724 #endif /* HAVE_ANDROID */