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