1 /* xscreensaver, Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
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
11 * This file is three related things:
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.
21 #ifdef HAVE_ANDROID /* whole file */
32 #include <android/log.h>
35 #include "screenhackI.h"
38 #include "jwxyz-android.h"
39 #include "textclient.h"
40 #include "grabscreen.h"
43 #define countof(x) (sizeof(x)/sizeof(*(x)))
45 extern struct xscreensaver_function_table *xscreensaver_function_table;
47 struct function_table_entry {
49 struct xscreensaver_function_table *xsft;
52 #include "gen/function-table.h"
56 struct event_queue *next;
59 static void send_queued_events (struct running_hack *rh);
62 const char *progclass;
65 static JavaVM *global_jvm;
66 static jmp_buf jmp_target;
68 static double current_rotation = 0;
70 extern void check_gl_error (const char *type);
73 do_logv(int prio, const char *fmt, va_list args)
75 __android_log_vprint(prio, "xscreensaver", fmt, args);
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
86 ts.tv_nsec = 25 * 1000000;
91 void Log(const char *fmt, ...)
99 /* Handle an abort on Android
100 TODO: Test that Android handles aborts properly
103 jwxyz_abort (const char *fmt, ...)
105 /* Send error to Android device log */
110 va_start (args, fmt);
111 do_logv(ANDROID_LOG_ERROR, fmt, args);
115 va_start (args, fmt);
116 vsprintf (buf, fmt, args);
120 (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
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"),
129 // Nonlocal exit out of the jwxyz code.
130 longjmp (jmp_target, 1);
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
137 https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
141 // #### only need one var I think
142 static size_t classRefCount = 0;
143 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
146 static jfieldID runningHackField;
147 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
148 static jmethodID entryGetKey, entryGetValue;
150 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
152 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
155 // Initialized OpenGL and runs the screenhack's init function.
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)
162 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
164 progname = chosen->progname;
165 rh->xsft = chosen->xsft;
168 rh->jobject = jwxyz_obj; // update this every time we call into C
170 (*env)->GetJavaVM (env, &global_jvm);
172 # undef ya_rand_init // This is the one and only place it is allowed
175 Window wnd = (Window) calloc(1, sizeof(*wnd));
177 wnd->frame.width = w;
178 wnd->frame.height = h;
181 rh->egl_window_ctx = eglGetCurrentContext();
182 Assert(rh->egl_window_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
184 wnd->egl_surface = eglGetCurrentSurface(EGL_DRAW);
185 Assert(eglGetCurrentSurface(EGL_READ) == wnd->egl_surface,
186 "doinit: EGL_READ != EGL_DRAW");
188 rh->egl_display = eglGetCurrentDisplay();
189 Assert(rh->egl_display != EGL_NO_DISPLAY, "doinit: EGL_NO_DISPLAY");
191 EGLint config_attribs[3];
192 config_attribs[0] = EGL_CONFIG_ID;
193 eglQueryContext(rh->egl_display, rh->egl_window_ctx, EGL_CONFIG_ID,
195 config_attribs[2] = EGL_NONE;
198 eglChooseConfig(rh->egl_display, config_attribs,
199 &rh->egl_config, 1, &num_config);
200 Assert(num_config == 1, "no EGL config chosen");
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?!");
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
212 progclass = rh->xsft->progclass;
214 if ((*env)->ExceptionOccurred(env)) abort();
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);
221 if ((*env)->ExceptionOccurred(env)) abort();
223 // Load the defaults.
224 // Unceremoniously stolen from [PrefsReader defaultsToDict:].
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;");
230 if ((*env)->ExceptionOccurred(env)) abort();
232 const struct { const char *key, *val; } default_defaults[] = {
233 { "doubleBuffer", "false" },
234 { "multiSample", "false" },
235 { "texFontCacheSize", "30" },
236 { "textMode", "date" },
238 "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
239 { "grabDesktopImages", "true" },
240 { "chooseRandomImages", "true" },
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);
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);
261 const char *const *defs = rh->xsft->defaults;
263 char *line = strdup (*defs);
266 while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
269 while (*val && *val != ':')
271 if (*val != ':') abort();
273 while (*val == ' ' || *val == '\t')
276 unsigned long L = strlen(val);
277 while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
280 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
281 strcpy (key2, progname);
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);
297 (*env)->DeleteLocalRef (env, c);
298 if ((*env)->ExceptionOccurred(env)) abort();
306 // Animates a single frame of the current hack.
309 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
313 double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
316 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
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.
323 if (++rh->frame_count < 8)
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.
333 gettimeofday (&tv, 0);
334 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
336 fps0 = fps1 = fps2 = fps3 = fps4 = now;
338 if (now < rh->next_frame_time) goto END;
343 gettimeofday (&tv, 0);
344 fps1 = tv.tv_sec + (tv.tv_usec / 1000000.0);
347 // The init function might do graphics (e.g. XClearWindow) so it has
348 // to be run from inside onDrawFrame, not onSurfaceChanged.
350 if (! rh->initted_p) {
352 void *(*init_cb) (Display *, Window, void *) =
353 (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
356 get_pixel_resource (rh->dpy, 0, "background", "Background");
357 XSetWindowBackground (rh->dpy, rh->window, bg);
358 XClearWindow (rh->dpy, rh->window);
360 rh->closure = init_cb (rh->dpy, rh->window, rh->xsft->setup_arg);
361 rh->initted_p = True;
363 rh->ignore_rotation_p =
364 (rh->api == API_XLIB &&
365 get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
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;
372 rh->xsft->fps_cb = 0;
375 if ((*env)->ExceptionOccurred(env)) abort();
379 gettimeofday (&tv, 0);
380 fps2 = tv.tv_sec + (tv.tv_usec / 1000000.0);
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);
388 gettimeofday (&tv, 0);
389 fps3 = tv.tv_sec + (tv.tv_usec / 1000000.0);
392 unsigned long delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure);
395 /* #### Until we work out why eglMakeCurrent is so slow on ARM. */
396 if (delay <= 40000) delay = 0;
401 gettimeofday (&tv, 0);
402 fps4 = tv.tv_sec + (tv.tv_usec / 1000000.0);
404 if (rh->fpst && rh->xsft->fps_cb)
405 rh->xsft->fps_cb (rh->dpy, rh->window, rh->fpst, rh->closure);
407 gettimeofday (&tv, 0);
408 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
409 rh->next_frame_time = now + (delay / 1000000.0);
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));
424 // Extracts the C structure that is stored in the jwxyz Java object.
425 static struct running_hack *
426 getRunningHack (JNIEnv *env, jobject thiz)
428 jlong result = (*env)->GetLongField (env, thiz, runningHackField);
429 struct running_hack *rh = (struct running_hack *)(intptr_t)result;
431 rh->jobject = thiz; // update this every time we call into C
435 // Look up a class and mark it global in the provided variable.
437 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
439 jclass clazz = (*env)->FindClass(env, className);
440 *globalRef = (*env)->NewGlobalRef(env, clazz);
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
451 // Implementation of jwxyz's nativeInit Java method.
453 JNIEXPORT void JNICALL
454 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
455 jstring jhack, jint api,
459 pthread_mutex_lock(&mutg);
461 struct running_hack *rh = calloc(1, sizeof(struct running_hack));
463 if ((*env)->ExceptionOccurred(env)) abort();
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();
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();
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();
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();
498 // Store the C struct into the Java object.
499 (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
501 // TODO: Sort the list so binary search works.
502 const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
506 if (!chosen == countof(function_table)) {
507 Log ("Hack not found: %s", hack);
510 if (!strcmp(function_table[chosen].progname, hack))
515 (*env)->ReleaseStringUTFChars(env, jhack, hack);
517 doinit (thiz, rh, env, &function_table[chosen], api, defaults, w, h);
519 pthread_mutex_unlock(&mutg);
523 JNIEXPORT void JNICALL
524 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
525 jint w, jint h, jdouble rot)
527 pthread_mutex_lock(&mutg);
528 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
530 current_rotation = rot;
532 Log ("native rotation: %f", current_rotation);
534 struct running_hack *rh = getRunningHack(env, thiz);
536 Window wnd = rh->window;
539 wnd->frame.width = w;
540 wnd->frame.height = h;
542 glViewport (0, 0, w, h);
544 if (ignore_rotation_p(rh->dpy) &&
545 rot != 0 && rot != 180 && rot != -180) {
549 wnd->frame.width = w;
550 wnd->frame.height = h;
553 jwxyz_window_resized (rh->dpy);
555 rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
556 wnd->frame.width, wnd->frame.height);
558 if (rh->api == API_GL) {
559 glMatrixMode (GL_PROJECTION);
560 glRotatef (-rot, 0, 0, 1);
561 glMatrixMode (GL_MODELVIEW);
565 pthread_mutex_unlock(&mutg);
569 JNIEXPORT void JNICALL
570 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
572 pthread_mutex_lock(&mutg);
573 struct running_hack *rh = getRunningHack(env, thiz);
574 drawXScreenSaver(env, rh);
575 pthread_mutex_unlock(&mutg);
579 // TODO: Check Java side is calling this properly
580 JNIEXPORT void JNICALL
581 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
583 pthread_mutex_lock(&mutg);
584 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
586 struct running_hack *rh = getRunningHack(env, thiz);
588 prepare_context (rh);
591 rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
592 jwxyz_free_display(rh->dpy);
595 (*env)->SetLongField(env, thiz, runningHackField, 0);
598 if (!classRefCount) {
599 (*env)->DeleteGlobalRef(env, globalRefjwxyz);
600 (*env)->DeleteGlobalRef(env, globalRefIterable);
601 (*env)->DeleteGlobalRef(env, globalRefIterator);
602 (*env)->DeleteGlobalRef(env, globalRefMapEntry);
606 pthread_mutex_unlock(&mutg);
611 send_event (struct running_hack *rh, XEvent *e)
613 // Assumes mutex is locked and context is prepared
615 int *xP = 0, *yP = 0;
616 switch (e->xany.type) {
617 case ButtonPress: case ButtonRelease:
627 // Rotate the coordinates in the events to match the pixels.
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;
634 switch ((int) current_rotation) {
635 case 180: case -180: // #### untested
640 swap = *xP; *xP = *yP; *yP = swap;
643 case -90: case 270: // #### untested
644 swap = *xP; *xP = *yP; *yP = swap;
650 rh->window->window.last_mouse_x = *xP;
651 rh->window->window.last_mouse_y = *yP;
654 return (rh->xsft->event_cb
655 ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
661 send_queued_events (struct running_hack *rh)
663 struct event_queue *event, *next;
664 if (! rh->event_queue) return;
665 for (event = rh->event_queue, next = event->next;
667 event = next, next = (event ? event->next : 0)) {
668 if (! send_event (rh, &event->event)) {
669 // #### flash the screen or something
678 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
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));
686 // Put it at the end.
687 struct event_queue *oq;
688 for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
695 pthread_mutex_unlock (&mutg);
699 JNIEXPORT void JNICALL
700 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
701 int x, int y, jboolean down)
704 memset (&e, 0, sizeof(e));
705 e.xany.type = (down ? ButtonPress : ButtonRelease);
706 e.xbutton.button = Button1;
709 queue_event (env, thiz, &e);
712 JNIEXPORT void JNICALL
713 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
717 memset (&e, 0, sizeof(e));
718 e.xany.type = MotionNotify;
721 queue_event (env, thiz, &e);
724 JNIEXPORT void JNICALL
725 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
730 memset (&e, 0, sizeof(e));
731 e.xkey.keycode = 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);
741 /***************************************************************************
742 Backend functions for jwxyz-gl.c
746 prepare_context (struct running_hack *rh)
748 Window w = rh->window;
749 eglMakeCurrent (rh->egl_display, w->egl_surface, w->egl_surface,
751 rh->current_drawable = rh->window;
755 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
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;
768 glViewport (0, 0, d->frame.width, d->frame.height);
769 jwxyz_set_matrices (dpy, d->frame.width, d->frame.height, False);
776 jwxyz_frame (Drawable d)
783 jwxyz_drawable_depth (Drawable d)
785 return (d->type == WINDOW
786 ? visual_depth (NULL, NULL)
792 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
798 xp->x = w->window.last_mouse_x;
799 xp->y = w->window.last_mouse_y;
805 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
807 fps_compute (fpst, 0, -1);
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)
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);
822 jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
823 width, height, dst_x, dst_y);
830 jwxyz_assert_drawable (Window main_window, Drawable d)
832 check_gl_error("jwxyz_assert_drawable");
837 jwxyz_assert_gl (void)
839 check_gl_error("jwxyz_assert_gl");
844 XCreatePixmap (Display *dpy, Drawable d,
845 unsigned int width, unsigned int height, unsigned int depth)
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
852 Window win = XRootWindow(dpy, 0);
854 Pixmap p = malloc(sizeof(*p));
858 p->frame.width = width;
859 p->frame.height = height;
861 Assert(depth == 1 || depth == visual_depth(NULL, NULL),
862 "XCreatePixmap: bad depth");
863 p->pixmap.depth = depth;
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;
868 attribs[0] = EGL_WIDTH;
870 attribs[2] = EGL_HEIGHT;
872 attribs[4] = EGL_NONE;
873 p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
875 Assert(p->egl_surface != EGL_NO_SURFACE,
876 "XCreatePixmap: got EGL_NO_SURFACE");
878 jwxyz_bind_drawable (dpy, win, p);
879 glClearColor (frand(1), frand(1), frand(1), 0);
880 glClear (GL_COLOR_BUFFER_BIT);
887 XFreePixmap (Display *d, Pixmap p)
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);
899 current_device_rotation (void)
901 return current_rotation;
905 ignore_rotation_p (Display *dpy)
907 struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
908 return rh->ignore_rotation_p;
913 get_string_resource (Display *dpy, char *name, char *class)
915 Window window = RootWindow (dpy, 0);
916 JNIEnv *env = window->window.rh->jni_env;
917 jobject obj = window->window.rh->jobject;
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();
927 ? (*env)->CallObjectMethod (env, obj, m, jstr)
929 (*env)->DeleteLocalRef (env, c);
930 (*env)->DeleteLocalRef (env, jstr);
933 const char *cvalue = (*env)->GetStringUTFChars (env, jvalue, 0);
934 ret = strdup (cvalue);
935 (*env)->ReleaseStringUTFChars (env, jvalue, cvalue);
938 Log("pref %s = %s", name, (ret ? ret : "(null)"));
943 /* Returns the contents of the URL. */
945 textclient_mobile_url_string (Display *dpy, const char *url)
947 Window window = RootWindow (dpy, 0);
948 JNIEnv *env = window->window.rh->jni_env;
949 jobject obj = window->window.rh->jobject;
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();
957 ? (*env)->CallObjectMethod (env, obj, m, jstr)
959 (*env)->DeleteLocalRef (env, c);
960 (*env)->DeleteLocalRef (env, jstr);
962 char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
965 int L = (*env)->GetDirectBufferCapacity (env, buf);
966 body2 = malloc (L + 1);
967 memcpy (body2, body, L);
970 body2 = strdup ("ERROR");
974 (*env)->DeleteLocalRef (env, buf);
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)
985 Window window = RootWindow (dpy, 0);
986 JNIEnv *env = window->window.rh->jni_env;
987 jobject obj = window->window.rh->jobject;
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();
995 jobjectArray array = (m
996 ? (*env)->CallObjectMethod (env, obj, m, jstr)
998 (*env)->DeleteLocalRef (env, c);
999 (*env)->DeleteLocalRef (env, jstr);
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();
1010 const char *cname = (*env)->GetStringUTFChars (env, name, 0);
1011 *native_name_ret = strdup (cname);
1012 (*env)->ReleaseStringUTFChars (env, name, cname);
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();
1019 c = (*env)->GetObjectClass(env, size);
1020 m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1021 if ((*env)->ExceptionOccurred(env)) abort();
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);
1027 return (void *) font_id;
1035 jwxyz_release_native_font (Display *dpy, void *native_font)
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();
1048 /* If the local reference table fills up, use this to figure out where
1049 you missed a call to DeleteLocalRef. */
1051 static void dump_reference_tables(JNIEnv *env)
1053 jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1054 jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1056 (*env)->CallStaticVoidMethod (env, c, m);
1057 (*env)->DeleteLocalRef (env, c);
1062 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1063 // string. If pixmap_ret is provided, also renders the text.
1066 jwxyz_render_text (Display *dpy, void *native_font,
1067 const char *str, size_t len, int utf8,
1068 XCharStruct *cs, char **pixmap_ret)
1070 Window window = RootWindow (dpy, 0);
1071 JNIEnv *env = window->window.rh->jni_env;
1072 jobject obj = window->window.rh->jobject;
1077 s2 = malloc (len + 1);
1078 memcpy (s2, str, len);
1080 } else { // Convert Latin1 to UTF8
1081 s2 = malloc (len * 2 + 1);
1082 unsigned char *s3 = (unsigned char *) s2;
1084 for (i = 0; i < len; i++) {
1085 unsigned char c = ((unsigned char *) str)[i];
1089 *s3++ = (0xC0 | (0x03 & (c >> 6)));
1090 *s3++ = (0x80 | (0x3F & c));
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();
1103 ? (*env)->CallObjectMethod (env, obj, m,
1104 (jlong) (long) native_font,
1106 (pixmap_ret ? JNI_TRUE : JNI_FALSE))
1108 (*env)->DeleteLocalRef (env, c);
1109 (*env)->DeleteLocalRef (env, jstr);
1112 if ((*env)->ExceptionOccurred(env)) abort();
1113 unsigned char *bits = (unsigned char *)
1114 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 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;
1126 char *pix = malloc (L - i);
1128 memcpy (pix, bits + i, L - i);
1132 memset (cs, 0, sizeof(*cs));
1138 (*env)->DeleteLocalRef (env, buf);
1142 /* Called from utils/grabclient.c */
1144 jwxyz_load_random_image (Display *dpy,
1145 int *width_ret, int *height_ret,
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;
1154 get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1156 get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1158 get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1160 if (!images_p && !grab_p)
1163 if (grab_p && images_p) {
1164 grab_p = !(random() & 5); /* if both, screenshot 1/5th of the time */
1168 jclass c = (*env)->GetObjectClass (env, obj);
1169 jmethodID m = (*env)->GetMethodID (env, c,
1172 : "loadRandomImage"),
1173 "(IIZ)Ljava/nio/ByteBuffer;");
1174 if ((*env)->ExceptionOccurred(env)) abort();
1176 ? (*env)->CallObjectMethod (env, obj, m,
1177 window->frame.width,
1178 window->frame.height,
1179 (rotate_p ? JNI_TRUE : JNI_FALSE))
1181 (*env)->DeleteLocalRef (env, c);
1183 unsigned char *bits = (unsigned char *)
1184 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 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);
1195 if (width * height * 4 != L - i) abort();
1196 char *pix = malloc (L - i);
1198 memcpy (pix, bits + i, L - i);
1200 *height_ret = height;
1201 *name_ret = strdup (name);
1202 return (char *) pix;
1206 (*env)->DeleteLocalRef (env, buf);
1211 #endif /* HAVE_ANDROID */