From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / jwxyz / jwxyz-android.c
1 /* xscreensaver, Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * This file is three related things:
12  *
13  *   - It is the Android-specific C companion to jwxyz-gl.c;
14  *   - It is how C calls into Java to do things that OpenGL does not
15  *     have access to without Java-based APIs;
16  *   - It is how the jwxyz.java class calls into C to run the hacks.
17  */
18
19 #include "config.h"
20
21 #ifdef HAVE_ANDROID /* whole file */
22
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <time.h>
27 #include <math.h>
28 #include <setjmp.h>
29
30 #include <GLES/gl.h>
31 #include <jni.h>
32 #include <android/log.h>
33 #include <pthread.h>
34
35 #include "screenhackI.h"
36 #include "jwxyzI.h"
37 #include "jwzglesI.h"
38 #include "jwxyz-android.h"
39 #include "textclient.h"
40 #include "grabscreen.h"
41
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 };
51
52 #include "gen/function-table.h"
53
54 struct event_queue {
55   XEvent event;
56   struct event_queue *next;
57 };
58
59 static void send_queued_events (struct running_hack *rh);
60
61 const char *progname;
62 const char *progclass;
63 int mono_p = 0;
64
65 static JavaVM *global_jvm;
66 static jmp_buf jmp_target;
67
68 static double current_rotation = 0;
69
70 extern void check_gl_error (const char *type);
71
72 void
73 do_logv(int prio, const char *fmt, va_list args)
74 {
75   __android_log_vprint(prio, "xscreensaver", fmt, args);
76
77   /* The idea here is that if the device/emulator dies shortly after a log
78      message, then waiting here for a short while should increase the odds
79      that adb logcat can pick up the message before everything blows up. Of
80      course, doing this means dumping a ton of messages will slow things down
81      significantly.
82   */
83 # if 0
84   struct timespec ts;
85   ts.tv_sec = 0;
86   ts.tv_nsec = 25 * 1000000;
87   nanosleep(&ts, NULL);
88 # endif
89 }
90
91 void Log(const char *fmt, ...)
92 {
93   va_list args;
94   va_start (args, fmt);
95   Logv(fmt, args);
96   va_end (args);
97 }
98
99 /* Handle an abort on Android
100    TODO: Test that Android handles aborts properly
101  */
102 void
103 jwxyz_abort (const char *fmt, ...)
104 {
105   /* Send error to Android device log */
106   if (!fmt || !*fmt)
107     fmt = "abort";
108
109   va_list args;
110   va_start (args, fmt);
111   do_logv(ANDROID_LOG_ERROR, fmt, args);
112   va_end (args);
113
114   char buf[10240];
115   va_start (args, fmt);
116   vsprintf (buf, fmt, args);
117   va_end (args);
118
119   JNIEnv *env;
120   (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
121
122   if (! (*env)->ExceptionOccurred(env)) {
123     // If there's already an exception queued, let's just go with that one.
124     // Else, queue a Java exception to be thrown.
125     (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"),
126                       buf);
127   }
128
129   // Nonlocal exit out of the jwxyz code.
130   longjmp (jmp_target, 1);
131 }
132
133
134 /* We get to keep live references to Java classes in use because the VM can
135    unload a class that isn't being used, which invalidates field and method
136    IDs.
137    https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
138 */
139
140
141 // #### only need one var I think
142 static size_t classRefCount = 0;
143 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
144     globalRefMapEntry;
145
146 static jfieldID runningHackField;
147 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
148 static jmethodID entryGetKey, entryGetValue;
149
150 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
151
152 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
153
154
155 // Initialized OpenGL and runs the screenhack's init function.
156 //
157 static void
158 doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
159         const struct function_table_entry *chosen, jint api, 
160         jobject defaults, jint w, jint h)
161 {
162   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
163
164   progname = chosen->progname;
165   rh->xsft = chosen->xsft;
166   rh->api = api;
167   rh->jni_env = env;
168   rh->jobject = jwxyz_obj;  // update this every time we call into C
169
170   (*env)->GetJavaVM (env, &global_jvm);
171
172 # undef ya_rand_init  // This is the one and only place it is allowed
173   ya_rand_init (0);
174
175   Window wnd = (Window) calloc(1, sizeof(*wnd));
176   wnd->window.rh = rh;
177   wnd->frame.width = w;
178   wnd->frame.height = h;
179   wnd->type = WINDOW;
180
181   rh->egl_window_ctx = eglGetCurrentContext();
182   Assert(rh->egl_window_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
183
184   wnd->egl_surface = eglGetCurrentSurface(EGL_DRAW);
185   Assert(eglGetCurrentSurface(EGL_READ) == wnd->egl_surface,
186          "doinit: EGL_READ != EGL_DRAW");
187
188   rh->egl_display = eglGetCurrentDisplay();
189   Assert(rh->egl_display != EGL_NO_DISPLAY, "doinit: EGL_NO_DISPLAY");
190
191   EGLint config_attribs[3];
192   config_attribs[0] = EGL_CONFIG_ID;
193   eglQueryContext(rh->egl_display, rh->egl_window_ctx, EGL_CONFIG_ID,
194                   &config_attribs[1]);
195   config_attribs[2] = EGL_NONE;
196
197   EGLint num_config;
198   eglChooseConfig(rh->egl_display, config_attribs,
199                   &rh->egl_config, 1, &num_config);
200   Assert(num_config == 1, "no EGL config chosen");
201
202   rh->egl_xlib_ctx = eglCreateContext(rh->egl_display, rh->egl_config,
203                                       EGL_NO_CONTEXT, NULL);
204   Assert(rh->egl_xlib_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
205   Assert(rh->egl_xlib_ctx != rh->egl_window_ctx, "Only one context here?!");
206
207   rh->window = wnd;
208   rh->dpy = jwxyz_make_display(wnd);
209   Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
210   // TODO: Zero looks right, but double-check that is the right number
211
212   progclass = rh->xsft->progclass;
213
214   if ((*env)->ExceptionOccurred(env)) abort();
215   jwzgles_reset();
216
217   // This has to come before resource processing. It does not do graphics.
218   if (rh->xsft->setup_cb)
219     rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);
220
221   if ((*env)->ExceptionOccurred(env)) abort();
222
223   // Load the defaults.
224   // Unceremoniously stolen from [PrefsReader defaultsToDict:].
225
226   jclass     c = (*env)->GetObjectClass (env, defaults);
227   jmethodID  m = (*env)->GetMethodID (env, c, "put",
228                  "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
229   if (! m) abort();
230   if ((*env)->ExceptionOccurred(env)) abort();
231
232   const struct { const char *key, *val; } default_defaults[] = {
233     { "doubleBuffer", "false" },
234     { "multiSample",  "false" },
235     { "texFontCacheSize", "30" },
236     { "textMode", "date" },
237     { "textURL",
238       "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
239     { "grabDesktopImages",  "true" },
240     { "chooseRandomImages", "true" },
241   };
242
243   for (int i = 0; i < countof(default_defaults); i++) {
244     const char *key = default_defaults[i].key;
245     const char *val = default_defaults[i].val;
246     char *key2 = malloc (strlen(progname) + strlen(key) + 2);
247     strcpy (key2, progname);
248     strcat (key2, "_");
249     strcat (key2, key);
250
251     // defaults.put(key2, val);
252     jstring jkey = (*env)->NewStringUTF (env, key2);
253     jstring jval = (*env)->NewStringUTF (env, val);
254     (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
255     (*env)->DeleteLocalRef (env, jkey);
256     (*env)->DeleteLocalRef (env, jval);
257     // Log ("default0: \"%s\" = \"%s\"", key2, val);
258     free (key2);
259   }
260
261   const char *const *defs = rh->xsft->defaults;
262   while (*defs) {
263     char *line = strdup (*defs);
264     char *key, *val;
265     key = line;
266     while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
267       key++;
268     val = key;
269     while (*val && *val != ':')
270       val++;
271     if (*val != ':') abort();
272     *val++ = 0;
273     while (*val == ' ' || *val == '\t')
274       val++;
275
276     unsigned long L = strlen(val);
277     while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
278       val[--L] = 0;
279
280     char *key2 = malloc (strlen(progname) + strlen(key) + 2);
281     strcpy (key2, progname);
282     strcat (key2, "_");
283     strcat (key2, key);
284
285     // defaults.put(key2, val);
286     jstring jkey = (*env)->NewStringUTF (env, key2);
287     jstring jval = (*env)->NewStringUTF (env, val);
288     (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
289     (*env)->DeleteLocalRef (env, jkey);
290     (*env)->DeleteLocalRef (env, jval);
291     // Log ("default: \"%s\" = \"%s\"", key2, val);
292     free (key2);
293     free (line);
294     defs++;
295   }
296
297   (*env)->DeleteLocalRef (env, c);
298   if ((*env)->ExceptionOccurred(env)) abort();
299
300  END: ;
301 }
302
303
304 #undef DEBUG_FPS
305
306 // Animates a single frame of the current hack.
307 //
308 static void
309 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
310 {
311   double now = 0;
312 # ifdef DEBUG_FPS
313   double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
314 # endif
315
316   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
317
318   /* There is some kind of weird redisplay race condition between Settings
319      and the launching hack: e.g., Abstractile does XClearWindow at init,
320      but the screen is getting filled with random bits.  So let's wait a
321      few frames before really starting up.
322    */
323   if (++rh->frame_count < 8)
324     goto END;
325
326   /* Some of the screen hacks want to delay for long periods, and letting
327      the framework run the update function at 30 FPS when it really wanted
328      half a minute between frames would be bad.  So instead, we assume that
329      the framework's animation timer might fire whenever, but we only invoke
330      the screen hack's "draw frame" method when enough time has expired.
331   */
332   struct timeval tv;
333   gettimeofday (&tv, 0);
334   now = tv.tv_sec + (tv.tv_usec / 1000000.0);
335 # ifdef DEBUG_FPS
336   fps0 = fps1 = fps2 = fps3 = fps4 = now;
337 #endif
338   if (now < rh->next_frame_time) goto END;
339
340   prepare_context(rh);
341
342 # ifdef DEBUG_FPS
343   gettimeofday (&tv, 0);
344   fps1 = tv.tv_sec + (tv.tv_usec / 1000000.0);
345 # endif
346
347   // The init function might do graphics (e.g. XClearWindow) so it has
348   // to be run from inside onDrawFrame, not onSurfaceChanged.
349
350   if (! rh->initted_p) {
351
352     void *(*init_cb) (Display *, Window, void *) =
353       (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
354
355     unsigned int bg =
356       get_pixel_resource (rh->dpy, 0, "background", "Background");
357     XSetWindowBackground (rh->dpy, rh->window, bg);
358     XClearWindow (rh->dpy, rh->window);
359
360     rh->closure = init_cb (rh->dpy, rh->window, rh->xsft->setup_arg);
361     rh->initted_p = True;
362
363     rh->ignore_rotation_p =
364       (rh->api == API_XLIB &&
365        get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
366
367     if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
368       rh->fpst = fps_init (rh->dpy, rh->window);
369       if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
370     } else {
371       rh->fpst = NULL;
372       rh->xsft->fps_cb = 0;
373     }
374
375     if ((*env)->ExceptionOccurred(env)) abort();
376   }
377
378 # ifdef DEBUG_FPS
379   gettimeofday (&tv, 0);
380   fps2 = tv.tv_sec + (tv.tv_usec / 1000000.0);
381 # endif
382
383   // Apparently events don't come in on the drawing thread, and JNI flips
384   // out.  So we queue them there and run them here.
385   send_queued_events (rh);
386
387 # ifdef DEBUG_FPS
388   gettimeofday (&tv, 0);
389   fps3 = tv.tv_sec + (tv.tv_usec / 1000000.0);
390 # endif
391
392   unsigned long delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure);
393
394 # ifdef __arm__
395   /* #### Until we work out why eglMakeCurrent is so slow on ARM. */
396   if (delay <= 40000) delay = 0;
397 # endif
398
399
400 # ifdef DEBUG_FPS
401   gettimeofday (&tv, 0);
402   fps4 = tv.tv_sec + (tv.tv_usec / 1000000.0);
403 # endif
404   if (rh->fpst && rh->xsft->fps_cb)
405     rh->xsft->fps_cb (rh->dpy, rh->window, rh->fpst, rh->closure);
406
407   gettimeofday (&tv, 0);
408   now = tv.tv_sec + (tv.tv_usec / 1000000.0);
409   rh->next_frame_time = now + (delay / 1000000.0);
410
411  END: ;
412
413 # ifdef DEBUG_FPS
414   Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
415       (int) ((fps1-fps0)*1000000),
416       (int) ((fps2-fps1)*1000000),
417       (int) ((fps3-fps2)*1000000),
418       (int) ((fps4-fps3)*1000000),
419       (int) ( (now-fps4)*1000000));
420 # endif
421 }
422
423
424 // Extracts the C structure that is stored in the jwxyz Java object.
425 static struct running_hack *
426 getRunningHack (JNIEnv *env, jobject thiz)
427 {
428   jlong result = (*env)->GetLongField (env, thiz, runningHackField);
429   struct running_hack *rh = (struct running_hack *)(intptr_t)result;
430   if (rh)
431     rh->jobject = thiz;  // update this every time we call into C
432   return rh;
433 }
434
435 // Look up a class and mark it global in the provided variable.
436 static jclass
437 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
438 {
439   jclass clazz = (*env)->FindClass(env, className);
440   *globalRef = (*env)->NewGlobalRef(env, clazz);
441   return clazz;
442 }
443
444
445 /* Note: to find signature strings for native methods:
446    cd ./project/xscreensaver/build/intermediates/classes/debug/
447    javap -s -p org.jwz.xscreensaver.jwxyz
448  */
449
450
451 // Implementation of jwxyz's nativeInit Java method.
452 //
453 JNIEXPORT void JNICALL
454 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
455                                             jstring jhack, jint api,
456                                             jobject defaults,
457                                             jint w, jint h)
458 {
459   pthread_mutex_lock(&mutg);
460
461   struct running_hack *rh = calloc(1, sizeof(struct running_hack));
462
463   if ((*env)->ExceptionOccurred(env)) abort();
464
465   // #### simplify
466   if (!classRefCount) {
467     jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
468     globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
469     runningHackField = (*env)->GetFieldID
470       (env, classjwxyz, "nativeRunningHackPtr", "J");
471     if ((*env)->ExceptionOccurred(env)) abort();
472
473     jclass classIterable =
474       acquireClass(env, "java/lang/Iterable", &globalRefIterable);
475     iterableIterator = (*env)->GetMethodID
476       (env, classIterable, "iterator", "()Ljava/util/Iterator;");
477     if ((*env)->ExceptionOccurred(env)) abort();
478
479     jclass classIterator =
480       acquireClass(env, "java/util/Iterator", &globalRefIterator);
481     iteratorHasNext = (*env)->GetMethodID
482       (env, classIterator, "hasNext", "()Z");
483     iteratorNext = (*env)->GetMethodID
484       (env, classIterator, "next", "()Ljava/lang/Object;");
485     if ((*env)->ExceptionOccurred(env)) abort();
486
487     jclass classMapEntry =
488       acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
489     entryGetKey = (*env)->GetMethodID
490       (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
491     entryGetValue = (*env)->GetMethodID
492       (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
493     if ((*env)->ExceptionOccurred(env)) abort();
494   }
495
496   ++classRefCount;
497
498   // Store the C struct into the Java object.
499   (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
500
501   // TODO: Sort the list so binary search works.
502   const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
503
504   int chosen = 0;
505   for (;;) {
506     if (!chosen == countof(function_table)) {
507       Log ("Hack not found: %s", hack);
508       abort();
509     }
510     if (!strcmp(function_table[chosen].progname, hack))
511       break;
512     chosen++;
513   }
514
515   (*env)->ReleaseStringUTFChars(env, jhack, hack);
516
517   doinit (thiz, rh, env, &function_table[chosen], api, defaults, w, h);
518
519   pthread_mutex_unlock(&mutg);
520 }
521
522
523 JNIEXPORT void JNICALL
524 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
525                                               jint w, jint h, jdouble rot)
526 {
527   pthread_mutex_lock(&mutg);
528   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
529
530   current_rotation = rot;
531
532   Log ("native rotation: %f", current_rotation);
533
534   struct running_hack *rh = getRunningHack(env, thiz);
535
536   Window wnd = rh->window;
537   wnd->frame.x = 0;
538   wnd->frame.y = 0;
539   wnd->frame.width  = w;
540   wnd->frame.height = h;
541
542   glViewport (0, 0, w, h);
543
544   if (ignore_rotation_p(rh->dpy) &&
545       rot != 0 && rot != 180 && rot != -180) {
546     int swap = w;
547     w = h;
548     h = swap;
549     wnd->frame.width  = w;
550     wnd->frame.height = h;
551   }
552
553   jwxyz_window_resized (rh->dpy);
554   if (rh->initted_p)
555     rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
556                           wnd->frame.width, wnd->frame.height);
557
558   if (rh->api == API_GL) {
559     glMatrixMode (GL_PROJECTION);
560     glRotatef (-rot, 0, 0, 1);
561     glMatrixMode (GL_MODELVIEW);
562   }
563
564  END:
565   pthread_mutex_unlock(&mutg);
566 }
567
568
569 JNIEXPORT void JNICALL
570 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
571 {
572   pthread_mutex_lock(&mutg);
573   struct running_hack *rh = getRunningHack(env, thiz);
574   drawXScreenSaver(env, rh);
575   pthread_mutex_unlock(&mutg);
576 }
577
578
579 // TODO: Check Java side is calling this properly
580 JNIEXPORT void JNICALL
581 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
582 {
583   pthread_mutex_lock(&mutg);
584   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
585
586   struct running_hack *rh = getRunningHack(env, thiz);
587
588   prepare_context (rh);
589
590   if (rh->initted_p)
591     rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
592   jwxyz_free_display(rh->dpy);
593
594   free(rh);
595   (*env)->SetLongField(env, thiz, runningHackField, 0);
596
597   --classRefCount;
598   if (!classRefCount) {
599     (*env)->DeleteGlobalRef(env, globalRefjwxyz);
600     (*env)->DeleteGlobalRef(env, globalRefIterable);
601     (*env)->DeleteGlobalRef(env, globalRefIterator);
602     (*env)->DeleteGlobalRef(env, globalRefMapEntry);
603   }
604
605  END:
606   pthread_mutex_unlock(&mutg);
607 }
608
609
610 static int
611 send_event (struct running_hack *rh, XEvent *e)
612 {
613   // Assumes mutex is locked and context is prepared
614
615   int *xP = 0, *yP = 0;
616   switch (e->xany.type) {
617   case ButtonPress: case ButtonRelease:
618     xP = &e->xbutton.x;
619     yP = &e->xbutton.y;
620     break;
621   case MotionNotify:
622     xP = &e->xmotion.x;
623     yP = &e->xmotion.y;
624     break;
625   }
626
627   // Rotate the coordinates in the events to match the pixels.
628   if (xP) {
629     if (ignore_rotation_p (rh->dpy)) {
630       Window win = XRootWindow (rh->dpy, 0);
631       int w = win->frame.width;
632       int h = win->frame.height;
633       int swap;
634       switch ((int) current_rotation) {
635       case 180: case -180:                              // #### untested
636         *xP = w - *xP;
637         *yP = h - *yP;
638         break;
639       case 90: case -270:
640         swap = *xP; *xP = *yP; *yP = swap;
641         *yP = h - *yP;
642         break;
643       case -90: case 270:                               // #### untested
644         swap = *xP; *xP = *yP; *yP = swap;
645         *xP = w - *xP;
646         break;
647       }
648     }
649
650     rh->window->window.last_mouse_x = *xP;
651     rh->window->window.last_mouse_y = *yP;
652   }
653
654   return (rh->xsft->event_cb
655           ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
656           : 0);
657 }
658
659
660 static void
661 send_queued_events (struct running_hack *rh)
662 {
663   struct event_queue *event, *next;
664   if (! rh->event_queue) return;
665   for (event = rh->event_queue, next = event->next;
666        event;
667        event = next, next = (event ? event->next : 0)) {
668     if (! send_event (rh, &event->event)) {
669       // #### flash the screen or something
670     }
671     free (event);
672   }
673   rh->event_queue = 0;
674 }
675
676
677 static void
678 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
679 {
680   pthread_mutex_lock (&mutg);
681   struct running_hack *rh = getRunningHack (env, thiz);
682   struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
683   memcpy (&q->event, e, sizeof(*e));
684   q->next = 0;
685
686   // Put it at the end.
687   struct event_queue *oq;
688   for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
689     ;
690   if (oq)
691     oq->next = q;
692   else
693     rh->event_queue = q;
694
695   pthread_mutex_unlock (&mutg);
696 }
697
698
699 JNIEXPORT void JNICALL
700 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
701                                                  int x, int y, jboolean down)
702 {
703   XEvent e;
704   memset (&e, 0, sizeof(e));
705   e.xany.type = (down ? ButtonPress : ButtonRelease);
706   e.xbutton.button = Button1;
707   e.xbutton.x = x;
708   e.xbutton.y = y;
709   queue_event (env, thiz, &e);
710 }
711
712 JNIEXPORT void JNICALL
713 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
714                                                  int x, int y)
715 {
716   XEvent e;
717   memset (&e, 0, sizeof(e));
718   e.xany.type = MotionNotify;
719   e.xmotion.x = x;
720   e.xmotion.y = y;
721   queue_event (env, thiz, &e);
722 }
723
724 JNIEXPORT void JNICALL
725 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
726                                               jboolean down_p, 
727                                               int code, int mods)
728 {
729   XEvent e;
730   memset (&e, 0, sizeof(e));
731   e.xkey.keycode = code;
732   e.xkey.state = code;
733   e.xany.type = (down_p ? KeyPress : KeyRelease);
734   queue_event (env, thiz, &e);
735   e.xany.type = KeyRelease;
736   queue_event (env, thiz, &e);
737 }
738
739
740
741 /***************************************************************************
742   Backend functions for jwxyz-gl.c
743  */
744
745 void
746 prepare_context (struct running_hack *rh)
747 {
748   Window w = rh->window;
749   eglMakeCurrent (rh->egl_display, w->egl_surface, w->egl_surface,
750                   rh->egl_window_ctx);
751   rh->current_drawable = rh->window;
752 }
753
754 void
755 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
756 {
757   struct running_hack *rh = w->window.rh;
758   JNIEnv *env = w->window.rh->jni_env;
759   if ((*env)->ExceptionOccurred(env)) abort();
760   if (rh->current_drawable != d) {
761     EGLContext ctx = d == w ? rh->egl_window_ctx : rh->egl_xlib_ctx;
762     eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, ctx);
763     // Log("%p %p %p %p %p", rh->egl_display, d->egl_surface, ctx, w, d);
764     rh->current_drawable = d;
765     jwxyz_assert_gl ();
766
767     if (d != w) {
768       glViewport (0, 0, d->frame.width, d->frame.height);
769       jwxyz_set_matrices (dpy, d->frame.width, d->frame.height, False);
770     }
771   }
772 }
773
774
775 const XRectangle *
776 jwxyz_frame (Drawable d)
777 {
778   return &d->frame;
779 }
780
781
782 unsigned int
783 jwxyz_drawable_depth (Drawable d)
784 {
785   return (d->type == WINDOW
786           ? visual_depth (NULL, NULL)
787           : d->pixmap.depth);
788 }
789
790
791 void
792 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
793 {
794   xvpos->x = 0;
795   xvpos->y = 0;
796
797   if (xp) {
798     xp->x = w->window.last_mouse_x;
799     xp->y = w->window.last_mouse_y;
800   }
801 }
802
803
804 static void
805 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
806 {
807   fps_compute (fpst, 0, -1);
808   fps_draw (fpst);
809 }
810
811
812 void
813 jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
814                  int src_x, int src_y, unsigned int width, unsigned int height,
815                  int dst_x, int dst_y)
816 {
817 #if 0
818   // Hilarious display corruption ahoy!
819   jwxyz_gl_copy_area_copy_tex_image (dpy, src, dst, gc, src_x, src_y,
820                                      width, height, dst_x, dst_y);
821 #else
822   jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
823                                   width, height, dst_x, dst_y);
824 #endif
825   jwxyz_assert_gl ();
826 }
827
828
829 void
830 jwxyz_assert_drawable (Window main_window, Drawable d)
831 {
832   check_gl_error("jwxyz_assert_drawable");
833 }
834
835
836 void
837 jwxyz_assert_gl (void)
838 {
839   check_gl_error("jwxyz_assert_gl");
840 }
841
842
843 Pixmap
844 XCreatePixmap (Display *dpy, Drawable d,
845                unsigned int width, unsigned int height, unsigned int depth)
846 {
847   // See also:
848   // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
849   // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
850   // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
851
852   Window win = XRootWindow(dpy, 0);
853
854   Pixmap p = malloc(sizeof(*p));
855   p->type = PIXMAP;
856   p->frame.x = 0;
857   p->frame.y = 0;
858   p->frame.width = width;
859   p->frame.height = height;
860
861   Assert(depth == 1 || depth == visual_depth(NULL, NULL),
862          "XCreatePixmap: bad depth");
863   p->pixmap.depth = depth;
864
865   // Native EGL is Android 2.3/API 9. EGL in Java is available from API 1.
866   struct running_hack *rh = win->window.rh;
867   EGLint attribs[5];
868   attribs[0] = EGL_WIDTH;
869   attribs[1] = width;
870   attribs[2] = EGL_HEIGHT;
871   attribs[3] = height;
872   attribs[4] = EGL_NONE;
873   p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
874                                            attribs);
875   Assert(p->egl_surface != EGL_NO_SURFACE,
876          "XCreatePixmap: got EGL_NO_SURFACE");
877
878   jwxyz_bind_drawable (dpy, win, p);
879   glClearColor (frand(1), frand(1), frand(1), 0);
880   glClear (GL_COLOR_BUFFER_BIT);
881
882   return p;
883 }
884
885
886 int
887 XFreePixmap (Display *d, Pixmap p)
888 {
889   struct running_hack *rh = XRootWindow(d, 0)->window.rh;
890   if (rh->current_drawable == p)
891     rh->current_drawable = NULL;
892   eglDestroySurface(rh->egl_display, p->egl_surface);
893   free (p);
894   return 0;
895 }
896
897
898 double
899 current_device_rotation (void)
900 {
901   return current_rotation;
902 }
903
904 Bool
905 ignore_rotation_p (Display *dpy)
906 {
907   struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
908   return rh->ignore_rotation_p;
909 }
910
911
912 char *
913 get_string_resource (Display *dpy, char *name, char *class)
914 {
915   Window window = RootWindow (dpy, 0);
916   JNIEnv *env = window->window.rh->jni_env;
917   jobject obj = window->window.rh->jobject;
918
919   if ((*env)->ExceptionOccurred(env)) abort();
920   jstring jstr = (*env)->NewStringUTF (env, name);
921   jclass     c = (*env)->GetObjectClass (env, obj);
922   jmethodID  m = (*env)->GetMethodID (env, c, "getStringResource",
923                            "(Ljava/lang/String;)Ljava/lang/String;");
924   if ((*env)->ExceptionOccurred(env)) abort();
925
926   jstring jvalue = (m
927                   ? (*env)->CallObjectMethod (env, obj, m, jstr)
928                   : NULL);
929   (*env)->DeleteLocalRef (env, c);
930   (*env)->DeleteLocalRef (env, jstr);
931   char *ret = 0;
932   if (jvalue) {
933     const char *cvalue = (*env)->GetStringUTFChars (env, jvalue, 0);
934     ret = strdup (cvalue);
935     (*env)->ReleaseStringUTFChars (env, jvalue, cvalue);
936   }
937
938   Log("pref %s = %s", name, (ret ? ret : "(null)"));
939   return ret;
940 }
941
942
943 /* Returns the contents of the URL. */
944 char *
945 textclient_mobile_url_string (Display *dpy, const char *url)
946 {
947   Window window = RootWindow (dpy, 0);
948   JNIEnv *env = window->window.rh->jni_env;
949   jobject obj = window->window.rh->jobject;
950
951   jstring jstr  = (*env)->NewStringUTF (env, url);
952   jclass      c = (*env)->GetObjectClass (env, obj);
953   jmethodID   m = (*env)->GetMethodID (env, c, "loadURL",
954                             "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
955   if ((*env)->ExceptionOccurred(env)) abort();
956   jobject buf = (m
957                  ? (*env)->CallObjectMethod (env, obj, m, jstr)
958                  : NULL);
959   (*env)->DeleteLocalRef (env, c);
960   (*env)->DeleteLocalRef (env, jstr);
961
962   char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
963   char *body2;
964   if (body) {
965     int L = (*env)->GetDirectBufferCapacity (env, buf);
966     body2 = malloc (L + 1);
967     memcpy (body2, body, L);
968     body2[L] = 0;
969   } else {
970     body2 = strdup ("ERROR");
971   }
972
973   if (buf)
974     (*env)->DeleteLocalRef (env, buf);
975
976   return body2;
977 }
978
979
980 void *
981 jwxyz_load_native_font (Display *dpy, const char *name,
982                         char **native_name_ret, float *size_ret,
983                         int *ascent_ret, int *descent_ret)
984 {
985   Window window = RootWindow (dpy, 0);
986   JNIEnv *env = window->window.rh->jni_env;
987   jobject obj = window->window.rh->jobject;
988
989   jstring jstr = (*env)->NewStringUTF (env, name);
990   jclass     c = (*env)->GetObjectClass (env, obj);
991   jmethodID  m = (*env)->GetMethodID (env, c, "loadFont",
992                            "(Ljava/lang/String;)[Ljava/lang/Object;");
993   if ((*env)->ExceptionOccurred(env)) abort();
994
995   jobjectArray array = (m
996                         ? (*env)->CallObjectMethod (env, obj, m, jstr)
997                         : NULL);
998   (*env)->DeleteLocalRef (env, c);
999   (*env)->DeleteLocalRef (env, jstr);
1000   jstr = 0;
1001
1002   if (array) {
1003     jobject font = (*env)->GetObjectArrayElement (env, array, 0);
1004     jobject name = (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
1005     jobject size = (*env)->GetObjectArrayElement (env, array, 2);
1006     jobject asc  = (*env)->GetObjectArrayElement (env, array, 3);
1007     jobject desc = (*env)->GetObjectArrayElement (env, array, 4);
1008     if ((*env)->ExceptionOccurred(env)) abort();
1009
1010     const char *cname = (*env)->GetStringUTFChars (env, name, 0);
1011     *native_name_ret = strdup (cname);
1012     (*env)->ReleaseStringUTFChars (env, name, cname);
1013
1014     c = (*env)->GetObjectClass(env, font);
1015     m = (*env)->GetMethodID (env, c, "longValue", "()J");
1016     long font_id = (*env)->CallLongMethod (env, font, m);
1017     if ((*env)->ExceptionOccurred(env)) abort();
1018
1019     c = (*env)->GetObjectClass(env, size);
1020     m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1021     if ((*env)->ExceptionOccurred(env)) abort();
1022
1023     *size_ret    =       (*env)->CallFloatMethod (env, size, m);
1024     *ascent_ret  = (int) (*env)->CallFloatMethod (env, asc,  m);
1025     *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1026
1027     return (void *) font_id;
1028   } else {
1029     return 0;
1030   }
1031 }
1032
1033
1034 void
1035 jwxyz_release_native_font (Display *dpy, void *native_font)
1036 {
1037   Window window = RootWindow (dpy, 0);
1038   JNIEnv *env = window->window.rh->jni_env;
1039   jobject obj = window->window.rh->jobject;
1040   if ((*env)->ExceptionOccurred(env)) abort();
1041   jclass    c = (*env)->GetObjectClass (env, obj);
1042   jmethodID m = (*env)->GetMethodID (env, c, "releaseFont", "(J)V");
1043   (*env)->CallVoidMethod (env, obj, m, (jobject) native_font);
1044   if ((*env)->ExceptionOccurred(env)) abort();
1045 }
1046
1047
1048 /* If the local reference table fills up, use this to figure out where
1049    you missed a call to DeleteLocalRef. */
1050 /*
1051 static void dump_reference_tables(JNIEnv *env)
1052 {
1053   jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1054   jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1055                                            "()V");
1056   (*env)->CallStaticVoidMethod (env, c, m);
1057   (*env)->DeleteLocalRef (env, c);
1058 }
1059 */
1060
1061
1062 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1063 // string.  If pixmap_ret is provided, also renders the text.
1064 //
1065 void
1066 jwxyz_render_text (Display *dpy, void *native_font,
1067                    const char *str, size_t len, int utf8,
1068                    XCharStruct *cs, char **pixmap_ret)
1069 {
1070   Window window = RootWindow (dpy, 0);
1071   JNIEnv *env = window->window.rh->jni_env;
1072   jobject obj = window->window.rh->jobject;
1073
1074   char *s2;
1075
1076   if (utf8) {
1077     s2 = malloc (len + 1);
1078     memcpy (s2, str, len);
1079     s2[len] = 0;
1080   } else {      // Convert Latin1 to UTF8
1081     s2 = malloc (len * 2 + 1);
1082     unsigned char *s3 = (unsigned char *) s2;
1083     int i;
1084     for (i = 0; i < len; i++) {
1085       unsigned char c = ((unsigned char *) str)[i];
1086       if (! (c & 0x80)) {
1087         *s3++ = c;
1088       } else {
1089         *s3++ = (0xC0 | (0x03 & (c >> 6)));
1090         *s3++ = (0x80 | (0x3F & c));
1091       }
1092     }
1093     *s3 = 0;
1094   }
1095
1096   jstring jstr  = (*env)->NewStringUTF (env, s2);
1097   jclass      c = (*env)->GetObjectClass (env, obj);
1098   jmethodID   m = (*env)->GetMethodID (env, c, "renderText",
1099                             "(JLjava/lang/String;Z)Ljava/nio/ByteBuffer;");
1100   if ((*env)->ExceptionOccurred(env)) abort();
1101   jobject buf =
1102     (m
1103      ? (*env)->CallObjectMethod (env, obj, m,
1104                                  (jlong) (long) native_font,
1105                                  jstr,
1106                                  (pixmap_ret ? JNI_TRUE : JNI_FALSE))
1107      : NULL);
1108   (*env)->DeleteLocalRef (env, c);
1109   (*env)->DeleteLocalRef (env, jstr);
1110   free (s2);
1111
1112   if ((*env)->ExceptionOccurred(env)) abort();
1113   unsigned char *bits = (unsigned char *)
1114     (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1115   if (bits) {
1116     int i = 0;
1117     int L = (*env)->GetDirectBufferCapacity (env, buf);
1118     if (L < 10) abort();
1119     cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1120     cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1121     cs->width    = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1122     cs->ascent   = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1123     cs->descent  = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1124
1125     if (pixmap_ret) {
1126       char *pix = malloc (L - i);
1127       if (! pix) abort();
1128       memcpy (pix, bits + i, L - i);
1129       *pixmap_ret = pix;
1130     }
1131   } else {
1132     memset (cs, 0, sizeof(*cs));
1133     if (pixmap_ret)
1134       *pixmap_ret = 0;
1135   }
1136
1137   if (buf)
1138     (*env)->DeleteLocalRef (env, buf);
1139 }
1140
1141
1142 /* Called from utils/grabclient.c */
1143 char *
1144 jwxyz_load_random_image (Display *dpy,
1145                          int *width_ret, int *height_ret,
1146                          char **name_ret)
1147 {
1148   Window window = RootWindow (dpy, 0);
1149   struct running_hack *rh = window->window.rh;
1150   JNIEnv *env = rh->jni_env;
1151   jobject obj = rh->jobject;
1152
1153   Bool images_p =
1154     get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1155   Bool grab_p =
1156     get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1157   Bool rotate_p =
1158     get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1159
1160   if (!images_p && !grab_p)
1161     return 0;
1162
1163   if (grab_p && images_p) {
1164     grab_p = !(random() & 5);    /* if both, screenshot 1/5th of the time */
1165     images_p = !grab_p;
1166   }
1167
1168   jclass      c = (*env)->GetObjectClass (env, obj);
1169   jmethodID   m = (*env)->GetMethodID (env, c, 
1170                                        (grab_p
1171                                         ? "getScreenshot"
1172                                         : "loadRandomImage"),
1173                                        "(IIZ)Ljava/nio/ByteBuffer;");
1174   if ((*env)->ExceptionOccurred(env)) abort();
1175   jobject buf = (m
1176                  ? (*env)->CallObjectMethod (env, obj, m,
1177                                              window->frame.width,
1178                                              window->frame.height,
1179                                              (rotate_p ? JNI_TRUE : JNI_FALSE))
1180                  : NULL);
1181   (*env)->DeleteLocalRef (env, c);
1182
1183   unsigned char *bits = (unsigned char *)
1184     (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1185
1186   if (bits) {
1187     int i = 0;
1188     int L = (*env)->GetDirectBufferCapacity (env, buf);
1189     if (L < 100) abort();
1190     int width  = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1191     int height = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1192     char *name = (char *) bits + i;
1193     int L2 = strlen (name);
1194     i += L2 + 1;
1195     if (width * height * 4 != L - i) abort();
1196     char *pix = malloc (L - i);
1197     if (! pix) abort();
1198     memcpy (pix, bits + i, L - i);
1199     *width_ret  = width;
1200     *height_ret = height;
1201     *name_ret   = strdup (name);
1202     return (char *) pix;
1203   }
1204
1205   if (buf)
1206     (*env)->DeleteLocalRef (env, buf);
1207
1208   return 0;
1209 }
1210
1211 #endif /* HAVE_ANDROID */