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
jwxyz_logv(Bool error,const char * fmt,va_list args)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
jwxyz_abort(const char * fmt,...)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
create_pixmap(Window win,Drawable p)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
free_pixmap(struct running_hack * rh,Pixmap p)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
prepare_context(struct running_hack * rh)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
doinit(jobject jwxyz_obj,struct running_hack * rh,JNIEnv * env,const struct function_table_entry * chosen,jobject defaults,jint w,jint h,jobject jni_surface)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
double_time(void)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
drawXScreenSaver(JNIEnv * env,struct running_hack * rh)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 *
getRunningHack(JNIEnv * env,jobject thiz)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
acquireClass(JNIEnv * env,const char * className,jobject * globalRef)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
Java_org_jwz_xscreensaver_jwxyz_nativeInit(JNIEnv * env,jobject thiz,jstring jhack,jobject defaults,jint w,jint h,jobject jni_surface)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
Java_org_jwz_xscreensaver_jwxyz_nativeResize(JNIEnv * env,jobject thiz,jint w,jint h,jdouble rot)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
Java_org_jwz_xscreensaver_jwxyz_nativeRender(JNIEnv * env,jobject thiz)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
Java_org_jwz_xscreensaver_jwxyz_nativeDone(JNIEnv * env,jobject thiz)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
send_event(struct running_hack * rh,XEvent * e)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
send_queued_events(struct running_hack * rh)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
queue_event(JNIEnv * env,jobject thiz,XEvent * e)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
Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent(JNIEnv * env,jobject thiz,int x,int y,jboolean down)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
Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent(JNIEnv * env,jobject thiz,int x,int y)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
Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent(JNIEnv * env,jobject thiz,jboolean down_p,int code,int mods)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
finish_bind_drawable(Display * dpy,Drawable dst)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
bind_drawable_fbo(struct running_hack * rh,Drawable d)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
jwxyz_bind_drawable(Display * dpy,Window w,Drawable d)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
jwxyz_gl_copy_area(Display * dpy,Drawable src,Drawable dst,GC gc,int src_x,int src_y,unsigned int width,unsigned int height,int dst_x,int dst_y)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,
1142                                       jwxyz_drawable_depth (src),
1143                                       width, height, dst_x, dst_y);
1144 
1145 #else
1146   // Kumppa: 0.17 FPS
1147   jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
1148                                   width, height, dst_x, dst_y);
1149 #endif
1150   jwxyz_assert_gl ();
1151 }
1152 
1153 
1154 void
jwxyz_assert_drawable(Window main_window,Drawable d)1155 jwxyz_assert_drawable (Window main_window, Drawable d)
1156 {
1157   check_gl_error("jwxyz_assert_drawable");
1158 }
1159 
1160 
1161 void
jwxyz_assert_gl(void)1162 jwxyz_assert_gl (void)
1163 {
1164   check_gl_error("jwxyz_assert_gl");
1165 }
1166 
1167 
1168 /***************************************************************************
1169   Backend functions for jwxyz-image.c
1170  */
1171 
1172 ptrdiff_t
jwxyz_image_pitch(Drawable d)1173 jwxyz_image_pitch (Drawable d)
1174 {
1175   return d->frame.width * 4;
1176 }
1177 
1178 void *
jwxyz_image_data(Drawable d)1179 jwxyz_image_data (Drawable d)
1180 {
1181   Assert (d->image_data, "no image storage (i.e. keep Xlib off the Window)");
1182   return d->image_data;
1183 }
1184 
1185 
1186 const XRectangle *
jwxyz_frame(Drawable d)1187 jwxyz_frame (Drawable d)
1188 {
1189   return &d->frame;
1190 }
1191 
1192 
1193 unsigned int
jwxyz_drawable_depth(Drawable d)1194 jwxyz_drawable_depth (Drawable d)
1195 {
1196   return (d->type == WINDOW
1197           ? visual_depth (NULL, NULL)
1198           : d->pixmap.depth);
1199 }
1200 
1201 
1202 void
jwxyz_get_pos(Window w,XPoint * xvpos,XPoint * xp)1203 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
1204 {
1205   xvpos->x = 0;
1206   xvpos->y = 0;
1207 
1208   if (xp) {
1209     xp->x = w->window.last_mouse_x;
1210     xp->y = w->window.last_mouse_y;
1211   }
1212 }
1213 
1214 
1215 static void
screenhack_do_fps(Display * dpy,Window w,fps_state * fpst,void * closure)1216 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
1217 {
1218   fps_compute (fpst, 0, -1);
1219   fps_draw (fpst);
1220 }
1221 
1222 
1223 Pixmap
XCreatePixmap(Display * dpy,Drawable d,unsigned int width,unsigned int height,unsigned int depth)1224 XCreatePixmap (Display *dpy, Drawable d,
1225                unsigned int width, unsigned int height, unsigned int depth)
1226 {
1227   Window win = XRootWindow(dpy, 0);
1228 
1229   Pixmap p = malloc(sizeof(*p));
1230   p->type = PIXMAP;
1231   p->frame.x = 0;
1232   p->frame.y = 0;
1233   p->frame.width = width;
1234   p->frame.height = height;
1235 
1236   Assert(depth == 1 || depth == visual_depth(NULL, NULL),
1237          "XCreatePixmap: bad depth");
1238   p->pixmap.depth = depth;
1239 
1240   create_pixmap (win, p);
1241 
1242   /* For debugging. */
1243 # if 0
1244   jwxyz_bind_drawable (dpy, win, p);
1245   glClearColor (frand(1), frand(1), frand(1), 0);
1246   glClear (GL_COLOR_BUFFER_BIT);
1247 # endif
1248 
1249   return p;
1250 }
1251 
1252 
1253 int
XFreePixmap(Display * d,Pixmap p)1254 XFreePixmap (Display *d, Pixmap p)
1255 {
1256   struct running_hack *rh = XRootWindow(d, 0)->window.rh;
1257 
1258   if (rh->jwxyz_gl_p) {
1259     jwxyz_gl_flush (d);
1260 
1261     if (rh->current_drawable == p)
1262       rh->current_drawable = NULL;
1263   }
1264 
1265   free_pixmap (rh, p);
1266   free (p);
1267   return 0;
1268 }
1269 
1270 
1271 double
current_device_rotation(void)1272 current_device_rotation (void)
1273 {
1274   return current_rotation;
1275 }
1276 
1277 Bool
ignore_rotation_p(Display * dpy)1278 ignore_rotation_p (Display *dpy)
1279 {
1280   struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
1281   return rh->ignore_rotation_p;
1282 }
1283 
1284 
1285 static char *
jstring_dup(JNIEnv * env,jstring str)1286 jstring_dup (JNIEnv *env, jstring str)
1287 {
1288   Assert (str, "expected jstring, not null");
1289   const char *cstr = (*env)->GetStringUTFChars (env, str, 0);
1290   size_t len = (*env)->GetStringUTFLength (env, str) + 1;
1291   char *result = malloc (len);
1292   if (result) {
1293     memcpy (result, cstr, len);
1294   }
1295   (*env)->ReleaseStringUTFChars (env, str, cstr);
1296   return result;
1297 }
1298 
1299 
1300 static char *
get_string_resource_window(Window window,char * name)1301 get_string_resource_window (Window window, char *name)
1302 {
1303   JNIEnv *env = window->window.rh->jni_env;
1304   jobject obj = window->window.rh->jobject;
1305 
1306   if ((*env)->ExceptionOccurred(env)) abort();
1307   jstring jstr = (*env)->NewStringUTF (env, name);
1308   jclass     c = (*env)->GetObjectClass (env, obj);
1309   jmethodID  m = (*env)->GetMethodID (env, c, "getStringResource",
1310                            "(Ljava/lang/String;)Ljava/lang/String;");
1311   if ((*env)->ExceptionOccurred(env)) abort();
1312 
1313   jstring jvalue = (m
1314                   ? (*env)->CallObjectMethod (env, obj, m, jstr)
1315                   : NULL);
1316   (*env)->DeleteLocalRef (env, c);
1317   (*env)->DeleteLocalRef (env, jstr);
1318   char *ret = 0;
1319   if (jvalue)
1320     ret = jstring_dup (env, jvalue);
1321 
1322   Log("pref %s = %s", name, (ret ? ret : "(null)"));
1323   return ret;
1324 }
1325 
1326 
1327 char *
get_string_resource(Display * dpy,char * name,char * class)1328 get_string_resource (Display *dpy, char *name, char *class)
1329 {
1330   return get_string_resource_window (RootWindow (dpy, 0), name);
1331 }
1332 
1333 
1334 /* Returns the contents of the URL. */
1335 char *
textclient_mobile_url_string(Display * dpy,const char * url)1336 textclient_mobile_url_string (Display *dpy, const char *url)
1337 {
1338   Window window = RootWindow (dpy, 0);
1339   JNIEnv *env = window->window.rh->jni_env;
1340   jobject obj = window->window.rh->jobject;
1341 
1342   jstring jstr  = (*env)->NewStringUTF (env, url);
1343   jclass      c = (*env)->GetObjectClass (env, obj);
1344   jmethodID   m = (*env)->GetMethodID (env, c, "loadURL",
1345                             "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
1346   if ((*env)->ExceptionOccurred(env)) abort();
1347   jobject buf = (m
1348                  ? (*env)->CallObjectMethod (env, obj, m, jstr)
1349                  : NULL);
1350   (*env)->DeleteLocalRef (env, c);
1351   (*env)->DeleteLocalRef (env, jstr);
1352 
1353   char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1354   char *body2;
1355   if (body) {
1356     int L = (*env)->GetDirectBufferCapacity (env, buf);
1357     body2 = malloc (L + 1);
1358     memcpy (body2, body, L);
1359     body2[L] = 0;
1360   } else {
1361     body2 = strdup ("ERROR");
1362   }
1363 
1364   if (buf)
1365     (*env)->DeleteLocalRef (env, buf);
1366 
1367   return body2;
1368 }
1369 
1370 
1371 float
jwxyz_scale(Window main_window)1372 jwxyz_scale (Window main_window)
1373 {
1374   // TODO: Use the actual device resolution.
1375   return 2;
1376 }
1377 
1378 
1379 const char *
jwxyz_default_font_family(int require)1380 jwxyz_default_font_family (int require)
1381 {
1382   /* Font families in XLFDs are totally ignored (for now). */
1383   return "sans-serif";
1384 }
1385 
1386 
1387 void *
jwxyz_load_native_font(Window window,int traits_jwxyz,int mask_jwxyz,const char * font_name_ptr,size_t font_name_length,int font_name_type,float size,char ** family_name_ret,int * ascent_ret,int * descent_ret)1388 jwxyz_load_native_font (Window window,
1389                         int traits_jwxyz, int mask_jwxyz,
1390                         const char *font_name_ptr, size_t font_name_length,
1391                         int font_name_type, float size,
1392                         char **family_name_ret,
1393                         int *ascent_ret, int *descent_ret)
1394 {
1395   JNIEnv *env = window->window.rh->jni_env;
1396   jobject obj = window->window.rh->jobject;
1397 
1398   jstring jname = NULL;
1399   if (font_name_ptr) {
1400     char *name_nul = malloc(font_name_length + 1);
1401     memcpy(name_nul, font_name_ptr, font_name_length);
1402     name_nul[font_name_length] = 0;
1403     jname = (*env)->NewStringUTF (env, name_nul);
1404     free(name_nul);
1405   }
1406 
1407   jclass     c = (*env)->GetObjectClass (env, obj);
1408   jmethodID  m = (*env)->GetMethodID (env, c, "loadFont",
1409                            "(IILjava/lang/String;IF)[Ljava/lang/Object;");
1410   if ((*env)->ExceptionOccurred(env)) abort();
1411 
1412   jobjectArray array = (m
1413                         ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
1414                                                     (jint)traits_jwxyz, jname,
1415                                                     (jint)font_name_type, (jfloat)size)
1416                         : NULL);
1417 
1418   (*env)->DeleteLocalRef (env, c);
1419 
1420   if (array) {
1421     jobject font = (*env)->GetObjectArrayElement (env, array, 0);
1422     jobject family_name =
1423       (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
1424     jobject asc  = (*env)->GetObjectArrayElement (env, array, 2);
1425     jobject desc = (*env)->GetObjectArrayElement (env, array, 3);
1426     if ((*env)->ExceptionOccurred(env)) abort();
1427 
1428     if (family_name_ret)
1429       *family_name_ret = jstring_dup (env, family_name);
1430 
1431     jobject paint = (*env)->NewGlobalRef (env, font);
1432     if ((*env)->ExceptionOccurred(env)) abort();
1433 
1434     c = (*env)->GetObjectClass(env, asc);
1435     m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1436     if ((*env)->ExceptionOccurred(env)) abort();
1437 
1438     *ascent_ret  = (int) (*env)->CallFloatMethod (env, asc,  m);
1439     *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1440 
1441     return (void *) paint;
1442   } else {
1443     return 0;
1444   }
1445 }
1446 
1447 
1448 void
jwxyz_release_native_font(Display * dpy,void * native_font)1449 jwxyz_release_native_font (Display *dpy, void *native_font)
1450 {
1451   Window window = RootWindow (dpy, 0);
1452   JNIEnv *env = window->window.rh->jni_env;
1453   if ((*env)->ExceptionOccurred(env)) abort();
1454   (*env)->DeleteGlobalRef (env, (jobject) native_font);
1455   if ((*env)->ExceptionOccurred(env)) abort();
1456 }
1457 
1458 
1459 /* If the local reference table fills up, use this to figure out where
1460    you missed a call to DeleteLocalRef. */
1461 /*
1462 static void dump_reference_tables(JNIEnv *env)
1463 {
1464   jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1465   jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1466                                            "()V");
1467   (*env)->CallStaticVoidMethod (env, c, m);
1468   (*env)->DeleteLocalRef (env, c);
1469 }
1470 */
1471 
1472 
1473 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1474 // string.  If pixmap_ret is provided, also renders the text.
1475 //
1476 void
jwxyz_render_text(Display * dpy,void * native_font,const char * str,size_t len,Bool utf8,Bool antialias_p,XCharStruct * cs,char ** pixmap_ret)1477 jwxyz_render_text (Display *dpy, void *native_font,
1478                    const char *str, size_t len, Bool utf8, Bool antialias_p,
1479                    XCharStruct *cs, char **pixmap_ret)
1480 {
1481   Window window = RootWindow (dpy, 0);
1482   JNIEnv *env = window->window.rh->jni_env;
1483   jobject obj = window->window.rh->jobject;
1484 
1485   char *s2;
1486 
1487   if (utf8) {
1488     s2 = malloc (len + 1);
1489     memcpy (s2, str, len);
1490     s2[len] = 0;
1491   } else {	// Convert Latin1 to UTF8
1492     s2 = malloc (len * 2 + 1);
1493     unsigned char *s3 = (unsigned char *) s2;
1494     int i;
1495     for (i = 0; i < len; i++) {
1496       unsigned char c = ((unsigned char *) str)[i];
1497       if (! (c & 0x80)) {
1498         *s3++ = c;
1499       } else {
1500         *s3++ = (0xC0 | (0x03 & (c >> 6)));
1501         *s3++ = (0x80 | (0x3F & c));
1502       }
1503     }
1504     *s3 = 0;
1505   }
1506 
1507   jstring jstr  = (*env)->NewStringUTF (env, s2);
1508   jclass      c = (*env)->GetObjectClass (env, obj);
1509   jmethodID   m = (*env)->GetMethodID (env, c, "renderText",
1510     "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;");
1511   if ((*env)->ExceptionOccurred(env)) abort();
1512   jobject buf =
1513     (m
1514      ? (*env)->CallObjectMethod (env, obj, m,
1515                                  (jobject) native_font,
1516                                  jstr,
1517                                  (pixmap_ret ? JNI_TRUE : JNI_FALSE),
1518                                  antialias_p)
1519      : NULL);
1520   (*env)->DeleteLocalRef (env, c);
1521   (*env)->DeleteLocalRef (env, jstr);
1522   free (s2);
1523 
1524   if ((*env)->ExceptionOccurred(env)) abort();
1525   unsigned char *bits = (unsigned char *)
1526     (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1527   if (bits) {
1528     int i = 0;
1529     int L = (*env)->GetDirectBufferCapacity (env, buf);
1530     if (L < 10) abort();
1531     cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1532     cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1533     cs->width    = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1534     cs->ascent   = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1535     cs->descent  = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1536 
1537     if (pixmap_ret) {
1538       char *pix = malloc (L - i);
1539       if (! pix) abort();
1540       memcpy (pix, bits + i, L - i);
1541       *pixmap_ret = pix;
1542     }
1543   } else {
1544     memset (cs, 0, sizeof(*cs));
1545     if (pixmap_ret)
1546       *pixmap_ret = 0;
1547   }
1548 
1549   if (buf)
1550     (*env)->DeleteLocalRef (env, buf);
1551 }
1552 
1553 
1554 char *
jwxyz_unicode_character_name(Display * dpy,Font fid,unsigned long uc)1555 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1556 {
1557   JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1558   /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1559   jclass
1560     c = (*env)->FindClass (env, "java/lang/Character"),
1561     c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
1562 
1563   if ((*env)->ExceptionOccurred(env)) abort();
1564   jmethodID m = (*env)->GetStaticMethodID (
1565     env, c, "getName", "(I)Ljava/lang/String;");
1566   jthrowable exc = (*env)->ExceptionOccurred(env);
1567   if (exc) {
1568     if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1569       (*env)->ExceptionClear (env);
1570       Assert (!m, "jwxyz_unicode_character_name: m?");
1571     } else {
1572       abort();
1573     }
1574   }
1575 
1576   char *ret = NULL;
1577 
1578   if (m) {
1579     jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1580     if (name)
1581      ret = jstring_dup (env, name);
1582   }
1583 
1584   if (!ret) {
1585     asprintf(&ret, "U+%.4lX", uc);
1586   }
1587 
1588   return ret;
1589 }
1590 
1591 
1592 /* Called from utils/grabclient.c */
1593 char *
jwxyz_draw_random_image(Display * dpy,Drawable drawable,GC gc)1594 jwxyz_draw_random_image (Display *dpy, Drawable drawable, GC gc)
1595 {
1596   Window window = RootWindow (dpy, 0);
1597   struct running_hack *rh = window->window.rh;
1598   JNIEnv *env = rh->jni_env;
1599   jobject obj = rh->jobject;
1600 
1601   Bool images_p =
1602     get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1603   Bool grab_p =
1604     get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1605   Bool rotate_p =
1606     get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1607 
1608   if (!images_p && !grab_p)
1609     return 0;
1610 
1611   if (grab_p && images_p) {
1612     grab_p = !(random() & 5);    /* if both, screenshot 1/5th of the time */
1613     images_p = !grab_p;
1614   }
1615 
1616   jclass      c = (*env)->GetObjectClass (env, obj);
1617   jmethodID   m = (*env)->GetMethodID (env, c,
1618                                        (grab_p
1619                                         ? "getScreenshot"
1620                                         : "checkThenLoadRandomImage"),
1621                                        "(IIZ)[Ljava/lang/Object;");
1622   if ((*env)->ExceptionOccurred(env)) abort();
1623   jobjectArray img_name = (
1624     m
1625     ? (*env)->CallObjectMethod (env, obj, m,
1626                                 drawable->frame.width, drawable->frame.height,
1627                                 (rotate_p ? JNI_TRUE : JNI_FALSE))
1628     : NULL);
1629   if ((*env)->ExceptionOccurred(env)) abort();
1630   (*env)->DeleteLocalRef (env, c);
1631 
1632   if (!img_name) {
1633     fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image"));
1634     return NULL;
1635   }
1636 
1637   jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 0);
1638 
1639   AndroidBitmapInfo bmp_info;
1640   AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1641 
1642   XImage *img = XCreateImage (dpy, NULL, visual_depth(NULL, NULL),
1643                               ZPixmap, 0, NULL,
1644                               bmp_info.width, bmp_info.height, 0,
1645                               bmp_info.stride);
1646 
1647   AndroidBitmap_lockPixels (env, jbitmap, (void **) &img->data);
1648 
1649   XPutImage (dpy, drawable, gc, img, 0, 0,
1650              (drawable->frame.width  - bmp_info.width) / 2,
1651              (drawable->frame.height - bmp_info.height) / 2,
1652              bmp_info.width, bmp_info.height);
1653 
1654   AndroidBitmap_unlockPixels (env, jbitmap);
1655   img->data = NULL;
1656   XDestroyImage (img);
1657 
1658   return jstring_dup (env, (*env)->GetObjectArrayElement (env, img_name, 1));
1659 }
1660 
1661 
1662 XImage *
jwxyz_png_to_ximage(Display * dpy,Visual * visual,const unsigned char * png_data,unsigned long data_size)1663 jwxyz_png_to_ximage (Display *dpy, Visual *visual,
1664                      const unsigned char *png_data, unsigned long data_size)
1665 {
1666   Window window = RootWindow (dpy, 0);
1667   struct running_hack *rh = window->window.rh;
1668   JNIEnv *env = rh->jni_env;
1669   jobject obj = rh->jobject;
1670   jclass    c = (*env)->GetObjectClass (env, obj);
1671   jmethodID m = (*env)->GetMethodID (env, c, "decodePNG",
1672                                      "([B)Landroid/graphics/Bitmap;");
1673   if ((*env)->ExceptionOccurred(env)) abort();
1674   jbyteArray jdata = (*env)->NewByteArray (env, data_size);
1675   (*env)->SetByteArrayRegion (env, jdata, 0,
1676                               data_size, (const jbyte *) png_data);
1677   jobject jbitmap = (
1678     m
1679     ? (*env)->CallObjectMethod (env, obj, m, jdata)
1680     : NULL);
1681   if ((*env)->ExceptionOccurred(env)) abort();
1682   (*env)->DeleteLocalRef (env, c);
1683   (*env)->DeleteLocalRef (env, jdata);
1684   if (!jbitmap)
1685     return NULL;
1686 
1687   AndroidBitmapInfo bmp_info;
1688   AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1689 
1690   XImage *img = XCreateImage (dpy, NULL, 32, ZPixmap, 0, NULL,
1691                               bmp_info.width, bmp_info.height, 8,
1692                               bmp_info.stride);
1693   char *bits = 0;
1694   AndroidBitmap_lockPixels (env, jbitmap, (void **) &bits);
1695   img->data = (char *) calloc (img->bytes_per_line, img->height);
1696   memcpy (img->data, bits, img->bytes_per_line * img->height);
1697   AndroidBitmap_unlockPixels (env, jbitmap);
1698 
1699   // Java should have returned ARGB data.
1700   // WTF, why isn't ANDROID_BITMAP_FORMAT_ARGB_8888 defined?
1701   if (bmp_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) abort();
1702 # ifndef __BYTE_ORDER__ // A GCC (and Clang)-ism.
1703 #  error Need a __BYTE_ORDER__.
1704 # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1705   img->byte_order = img->bitmap_bit_order = LSBFirst;
1706 # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1707   img->byte_order = img->bitmap_bit_order = MSBFirst;
1708 # else
1709 #  error Need a __BYTE_ORDER__.
1710 # endif
1711 
1712   static const union {
1713     uint8_t bytes[4];
1714     uint32_t pixel;
1715   } c0 = {{0xff, 0x00, 0x00, 0x00}}, c1 = {{0x00, 0xff, 0x00, 0x00}},
1716     c2 = {{0x00, 0x00, 0xff, 0x00}};
1717 
1718   img->red_mask   = c0.pixel;
1719   img->green_mask = c1.pixel;
1720   img->blue_mask  = c2.pixel;
1721 
1722   return img;
1723 }
1724 
1725 #endif /* HAVE_ANDROID */
1726