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 jwxyz_logv(Bool error, const char *fmt, va_list args)
75 __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
76 "xscreensaver", fmt, args);
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
87 ts.tv_nsec = 25 * 1000000;
92 /* Handle an abort on Android
93 TODO: Test that Android handles aborts properly
96 jwxyz_abort (const char *fmt, ...)
98 /* Send error to Android device log */
103 va_start (args, fmt);
104 jwxyz_logv(True, fmt, args);
108 va_start (args, fmt);
109 vsprintf (buf, fmt, args);
113 (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
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"),
122 // Nonlocal exit out of the jwxyz code.
123 longjmp (jmp_target, 1);
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
130 https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
134 // #### only need one var I think
135 static size_t classRefCount = 0;
136 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
139 static jfieldID runningHackField;
140 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
141 static jmethodID entryGetKey, entryGetValue;
143 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
145 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
148 // Initialized OpenGL and runs the screenhack's init function.
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)
155 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
157 progname = chosen->progname;
158 rh->xsft = chosen->xsft;
161 rh->jobject = jwxyz_obj; // update this every time we call into C
163 (*env)->GetJavaVM (env, &global_jvm);
165 # undef ya_rand_init // This is the one and only place it is allowed
168 Window wnd = (Window) calloc(1, sizeof(*wnd));
170 wnd->frame.width = w;
171 wnd->frame.height = h;
174 rh->egl_window_ctx = eglGetCurrentContext();
175 Assert(rh->egl_window_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
177 wnd->egl_surface = eglGetCurrentSurface(EGL_DRAW);
178 Assert(eglGetCurrentSurface(EGL_READ) == wnd->egl_surface,
179 "doinit: EGL_READ != EGL_DRAW");
181 rh->egl_display = eglGetCurrentDisplay();
182 Assert(rh->egl_display != EGL_NO_DISPLAY, "doinit: EGL_NO_DISPLAY");
184 EGLint config_attribs[3];
185 config_attribs[0] = EGL_CONFIG_ID;
186 eglQueryContext(rh->egl_display, rh->egl_window_ctx, EGL_CONFIG_ID,
188 config_attribs[2] = EGL_NONE;
191 eglChooseConfig(rh->egl_display, config_attribs,
192 &rh->egl_config, 1, &num_config);
193 Assert(num_config == 1, "no EGL config chosen");
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?!");
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
205 progclass = rh->xsft->progclass;
207 if ((*env)->ExceptionOccurred(env)) abort();
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);
214 if ((*env)->ExceptionOccurred(env)) abort();
216 // Load the defaults.
217 // Unceremoniously stolen from [PrefsReader defaultsToDict:].
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;");
223 if ((*env)->ExceptionOccurred(env)) abort();
225 const struct { const char *key, *val; } default_defaults[] = {
226 { "doubleBuffer", "false" },
227 { "multiSample", "false" },
228 { "texFontCacheSize", "30" },
229 { "textMode", "date" },
231 "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
232 { "grabDesktopImages", "true" },
233 { "chooseRandomImages", "true" },
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);
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);
254 const char *const *defs = rh->xsft->defaults;
256 char *line = strdup (*defs);
259 while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
262 while (*val && *val != ':')
264 if (*val != ':') abort();
266 while (*val == ' ' || *val == '\t')
269 unsigned long L = strlen(val);
270 while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
273 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
274 strcpy (key2, progname);
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);
290 (*env)->DeleteLocalRef (env, c);
291 if ((*env)->ExceptionOccurred(env)) abort();
305 # ifdef GETTIMEOFDAY_TWO_ARGS
307 gettimeofday(&now, &tzp);
312 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
317 // Animates a single frame of the current hack.
320 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
323 double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
324 fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
327 unsigned long delay = 0;
329 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
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.
336 if (++rh->frame_count < 8)
342 fps1 = double_time();
345 // The init function might do graphics (e.g. XClearWindow) so it has
346 // to be run from inside onDrawFrame, not onSurfaceChanged.
348 if (! rh->initted_p) {
350 void *(*init_cb) (Display *, Window, void *) =
351 (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
354 get_pixel_resource (rh->dpy, 0, "background", "Background");
355 XSetWindowBackground (rh->dpy, rh->window, bg);
356 XClearWindow (rh->dpy, rh->window);
358 rh->closure = init_cb (rh->dpy, rh->window, rh->xsft->setup_arg);
359 rh->initted_p = True;
361 rh->ignore_rotation_p =
362 (rh->api == API_XLIB &&
363 get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
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;
370 rh->xsft->fps_cb = 0;
373 if ((*env)->ExceptionOccurred(env)) abort();
377 fps2 = double_time();
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);
385 fps3 = double_time();
388 delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure);
391 fps4 = double_time();
393 if (rh->fpst && rh->xsft->fps_cb)
394 rh->xsft->fps_cb (rh->dpy, rh->window, rh->fpst, rh->closure);
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));
411 // Extracts the C structure that is stored in the jwxyz Java object.
412 static struct running_hack *
413 getRunningHack (JNIEnv *env, jobject thiz)
415 jlong result = (*env)->GetLongField (env, thiz, runningHackField);
416 struct running_hack *rh = (struct running_hack *)(intptr_t)result;
418 rh->jobject = thiz; // update this every time we call into C
422 // Look up a class and mark it global in the provided variable.
424 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
426 jclass clazz = (*env)->FindClass(env, className);
427 *globalRef = (*env)->NewGlobalRef(env, clazz);
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
438 // Implementation of jwxyz's nativeInit Java method.
440 JNIEXPORT void JNICALL
441 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
442 jstring jhack, jint api,
446 pthread_mutex_lock(&mutg);
448 struct running_hack *rh = calloc(1, sizeof(struct running_hack));
450 if ((*env)->ExceptionOccurred(env)) abort();
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();
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();
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();
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();
485 // Store the C struct into the Java object.
486 (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
488 // TODO: Sort the list so binary search works.
489 const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
493 if (chosen == countof(function_table)) {
494 Log ("Hack not found: %s", hack);
497 if (!strcmp(function_table[chosen].progname, hack))
502 (*env)->ReleaseStringUTFChars(env, jhack, hack);
504 doinit (thiz, rh, env, &function_table[chosen], api, defaults, w, h);
506 pthread_mutex_unlock(&mutg);
510 JNIEXPORT void JNICALL
511 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
512 jint w, jint h, jdouble rot)
514 pthread_mutex_lock(&mutg);
515 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
517 current_rotation = rot;
519 Log ("native rotation: %f", current_rotation);
521 struct running_hack *rh = getRunningHack(env, thiz);
523 Window wnd = rh->window;
526 wnd->frame.width = w;
527 wnd->frame.height = h;
529 glViewport (0, 0, w, h);
531 if (ignore_rotation_p(rh->dpy) &&
532 rot != 0 && rot != 180 && rot != -180) {
536 wnd->frame.width = w;
537 wnd->frame.height = h;
540 jwxyz_window_resized (rh->dpy);
542 rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
543 wnd->frame.width, wnd->frame.height);
545 if (rh->api == API_GL) {
546 glMatrixMode (GL_PROJECTION);
547 glRotatef (-rot, 0, 0, 1);
548 glMatrixMode (GL_MODELVIEW);
552 pthread_mutex_unlock(&mutg);
556 JNIEXPORT jlong JNICALL
557 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
559 pthread_mutex_lock(&mutg);
560 struct running_hack *rh = getRunningHack(env, thiz);
561 jlong result = drawXScreenSaver(env, rh);
562 pthread_mutex_unlock(&mutg);
567 // TODO: Check Java side is calling this properly
568 JNIEXPORT void JNICALL
569 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
571 pthread_mutex_lock(&mutg);
572 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
574 struct running_hack *rh = getRunningHack(env, thiz);
576 prepare_context (rh);
579 rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
580 jwxyz_free_display(rh->dpy);
583 (*env)->SetLongField(env, thiz, runningHackField, 0);
586 if (!classRefCount) {
587 (*env)->DeleteGlobalRef(env, globalRefjwxyz);
588 (*env)->DeleteGlobalRef(env, globalRefIterable);
589 (*env)->DeleteGlobalRef(env, globalRefIterator);
590 (*env)->DeleteGlobalRef(env, globalRefMapEntry);
594 pthread_mutex_unlock(&mutg);
599 send_event (struct running_hack *rh, XEvent *e)
601 // Assumes mutex is locked and context is prepared
603 int *xP = 0, *yP = 0;
604 switch (e->xany.type) {
605 case ButtonPress: case ButtonRelease:
615 // Rotate the coordinates in the events to match the pixels.
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;
622 switch ((int) current_rotation) {
623 case 180: case -180: // #### untested
628 swap = *xP; *xP = *yP; *yP = swap;
631 case -90: case 270: // #### untested
632 swap = *xP; *xP = *yP; *yP = swap;
638 rh->window->window.last_mouse_x = *xP;
639 rh->window->window.last_mouse_y = *yP;
642 return (rh->xsft->event_cb
643 ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
649 send_queued_events (struct running_hack *rh)
651 struct event_queue *event, *next;
652 if (! rh->event_queue) return;
653 for (event = rh->event_queue, next = event->next;
655 event = next, next = (event ? event->next : 0)) {
656 if (! send_event (rh, &event->event)) {
657 // #### flash the screen or something
666 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
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));
674 // Put it at the end.
675 struct event_queue *oq;
676 for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
683 pthread_mutex_unlock (&mutg);
687 JNIEXPORT void JNICALL
688 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
689 int x, int y, jboolean down)
692 memset (&e, 0, sizeof(e));
693 e.xany.type = (down ? ButtonPress : ButtonRelease);
694 e.xbutton.button = Button1;
697 queue_event (env, thiz, &e);
700 JNIEXPORT void JNICALL
701 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
705 memset (&e, 0, sizeof(e));
706 e.xany.type = MotionNotify;
709 queue_event (env, thiz, &e);
712 JNIEXPORT void JNICALL
713 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
718 memset (&e, 0, sizeof(e));
719 e.xkey.keycode = 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);
729 /***************************************************************************
730 Backend functions for jwxyz-gl.c
734 prepare_context (struct running_hack *rh)
736 Window w = rh->window;
737 eglMakeCurrent (rh->egl_display, w->egl_surface, w->egl_surface,
739 rh->current_drawable = rh->window;
743 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
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;
756 glViewport (0, 0, d->frame.width, d->frame.height);
757 jwxyz_set_matrices (dpy, d->frame.width, d->frame.height, False);
764 jwxyz_frame (Drawable d)
771 jwxyz_drawable_depth (Drawable d)
773 return (d->type == WINDOW
774 ? visual_depth (NULL, NULL)
780 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
786 xp->x = w->window.last_mouse_x;
787 xp->y = w->window.last_mouse_y;
793 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
795 fps_compute (fpst, 0, -1);
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)
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);
810 jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
811 width, height, dst_x, dst_y);
818 jwxyz_assert_drawable (Window main_window, Drawable d)
820 check_gl_error("jwxyz_assert_drawable");
825 jwxyz_assert_gl (void)
827 check_gl_error("jwxyz_assert_gl");
832 XCreatePixmap (Display *dpy, Drawable d,
833 unsigned int width, unsigned int height, unsigned int depth)
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
840 Window win = XRootWindow(dpy, 0);
842 Pixmap p = malloc(sizeof(*p));
846 p->frame.width = width;
847 p->frame.height = height;
849 Assert(depth == 1 || depth == visual_depth(NULL, NULL),
850 "XCreatePixmap: bad depth");
851 p->pixmap.depth = depth;
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;
856 attribs[0] = EGL_WIDTH;
858 attribs[2] = EGL_HEIGHT;
860 attribs[4] = EGL_NONE;
861 p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
863 Assert(p->egl_surface != EGL_NO_SURFACE,
864 "XCreatePixmap: got EGL_NO_SURFACE");
866 jwxyz_bind_drawable (dpy, win, p);
867 glClearColor (frand(1), frand(1), frand(1), 0);
868 glClear (GL_COLOR_BUFFER_BIT);
875 XFreePixmap (Display *d, Pixmap p)
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);
887 current_device_rotation (void)
889 return current_rotation;
893 ignore_rotation_p (Display *dpy)
895 struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
896 return rh->ignore_rotation_p;
901 jstring_dup (JNIEnv *env, jstring str)
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);
908 memcpy (result, cstr, len);
910 (*env)->ReleaseStringUTFChars (env, str, cstr);
916 get_string_resource (Display *dpy, char *name, char *class)
918 Window window = RootWindow (dpy, 0);
919 JNIEnv *env = window->window.rh->jni_env;
920 jobject obj = window->window.rh->jobject;
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();
930 ? (*env)->CallObjectMethod (env, obj, m, jstr)
932 (*env)->DeleteLocalRef (env, c);
933 (*env)->DeleteLocalRef (env, jstr);
936 ret = jstring_dup (env, jvalue);
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 *native_name_ret = jstring_dup (env, name);
1012 jobject paint = (*env)->NewGlobalRef (env, font);
1013 if ((*env)->ExceptionOccurred(env)) abort();
1015 c = (*env)->GetObjectClass(env, size);
1016 m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1017 if ((*env)->ExceptionOccurred(env)) abort();
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);
1023 return (void *) paint;
1031 jwxyz_release_native_font (Display *dpy, void *native_font)
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();
1041 /* If the local reference table fills up, use this to figure out where
1042 you missed a call to DeleteLocalRef. */
1044 static void dump_reference_tables(JNIEnv *env)
1046 jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1047 jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1049 (*env)->CallStaticVoidMethod (env, c, m);
1050 (*env)->DeleteLocalRef (env, c);
1055 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1056 // string. If pixmap_ret is provided, also renders the text.
1059 jwxyz_render_text (Display *dpy, void *native_font,
1060 const char *str, size_t len, int utf8,
1061 XCharStruct *cs, char **pixmap_ret)
1063 Window window = RootWindow (dpy, 0);
1064 JNIEnv *env = window->window.rh->jni_env;
1065 jobject obj = window->window.rh->jobject;
1070 s2 = malloc (len + 1);
1071 memcpy (s2, str, len);
1073 } else { // Convert Latin1 to UTF8
1074 s2 = malloc (len * 2 + 1);
1075 unsigned char *s3 = (unsigned char *) s2;
1077 for (i = 0; i < len; i++) {
1078 unsigned char c = ((unsigned char *) str)[i];
1082 *s3++ = (0xC0 | (0x03 & (c >> 6)));
1083 *s3++ = (0x80 | (0x3F & c));
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();
1096 ? (*env)->CallObjectMethod (env, obj, m,
1097 (jobject) native_font,
1099 (pixmap_ret ? JNI_TRUE : JNI_FALSE))
1101 (*env)->DeleteLocalRef (env, c);
1102 (*env)->DeleteLocalRef (env, jstr);
1105 if ((*env)->ExceptionOccurred(env)) abort();
1106 unsigned char *bits = (unsigned char *)
1107 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 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;
1119 char *pix = malloc (L - i);
1121 memcpy (pix, bits + i, L - i);
1125 memset (cs, 0, sizeof(*cs));
1131 (*env)->DeleteLocalRef (env, buf);
1136 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1138 JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1139 /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1141 c = (*env)->FindClass (env, "java/lang/Character"),
1142 c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
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);
1149 if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1150 (*env)->ExceptionClear (env);
1151 Assert (!m, "jwxyz_unicode_character_name: m?");
1160 jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1162 ret = jstring_dup (env, name);
1166 asprintf(&ret, "U+%.4lX", uc);
1173 /* Called from utils/grabclient.c */
1175 jwxyz_load_random_image (Display *dpy,
1176 int *width_ret, int *height_ret,
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;
1185 get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1187 get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1189 get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1191 if (!images_p && !grab_p)
1194 if (grab_p && images_p) {
1195 grab_p = !(random() & 5); /* if both, screenshot 1/5th of the time */
1199 jclass c = (*env)->GetObjectClass (env, obj);
1200 jmethodID m = (*env)->GetMethodID (env, c,
1203 : "loadRandomImage"),
1204 "(IIZ)Ljava/nio/ByteBuffer;");
1205 if ((*env)->ExceptionOccurred(env)) abort();
1207 ? (*env)->CallObjectMethod (env, obj, m,
1208 window->frame.width,
1209 window->frame.height,
1210 (rotate_p ? JNI_TRUE : JNI_FALSE))
1212 (*env)->DeleteLocalRef (env, c);
1214 unsigned char *bits = (unsigned char *)
1215 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 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);
1226 if (width * height * 4 != L - i) abort();
1227 char *pix = malloc (L - i);
1229 memcpy (pix, bits + i, L - i);
1231 *height_ret = height;
1232 *name_ret = strdup (name);
1233 return (char *) pix;
1237 (*env)->DeleteLocalRef (env, buf);
1242 #endif /* HAVE_ANDROID */