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