1 /* xscreensaver, Copyright © 2016-2021 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
14 See the comment at the top of jwxyz-common.c for an explanation of
15 the division of labor between these various modules.
17 This file is three related things:
19 - It is the Android-specific companion to jwxyz-gl.c or jwxyz-image.c;
20 - It is how C calls into Java to do things that OpenGL does not have
21 access to without Java-based APIs;
22 - It is how the jwxyz.java class calls into C to run the hacks.
25 #ifdef HAVE_ANDROID /* whole file */
34 #define GL_GLEXT_PROTOTYPES
37 #include <GLES/glext.h>
39 # include <GLES3/gl3.h>
42 #include <android/bitmap.h>
43 #include <android/log.h>
44 #include <android/native_window_jni.h>
47 #include "screenhackI.h"
50 #include "jwxyz-android.h"
51 #include "textclient.h"
52 #include "grabclient.h"
57 #define countof(x) (sizeof(x)/sizeof(*(x)))
59 extern struct xscreensaver_function_table *xscreensaver_function_table;
61 struct function_table_entry {
63 struct xscreensaver_function_table *xsft;
66 #include "gen/function-table.h"
70 struct event_queue *next;
73 static void send_queued_events (struct running_hack *rh);
76 const char *progclass;
79 static JavaVM *global_jvm;
80 static jmp_buf jmp_target;
82 static double current_rotation = 0;
84 extern void check_gl_error (const char *type);
87 jwxyz_logv(Bool error, const char *fmt, va_list args)
89 __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
90 "xscreensaver", fmt, args);
92 /* The idea here is that if the device/emulator dies shortly after a log
93 message, then waiting here for a short while should increase the odds
94 that adb logcat can pick up the message before everything blows up. Of
95 course, doing this means dumping a ton of messages will slow things down
101 ts.tv_nsec = 25 * 1000000;
102 nanosleep(&ts, NULL);
106 /* Handle an abort on Android
107 TODO: Test that Android handles aborts properly
110 jwxyz_abort (const char *fmt, ...)
112 /* Send error to Android device log */
117 va_start (args, fmt);
118 jwxyz_logv(True, fmt, args);
122 va_start (args, fmt);
123 vsprintf (buf, fmt, args);
127 (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
129 if (! (*env)->ExceptionOccurred(env)) {
130 // If there's already an exception queued, let's just go with that one.
131 // Else, queue a Java exception to be thrown.
132 (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"),
136 // Nonlocal exit out of the jwxyz code.
137 longjmp (jmp_target, 1);
141 /* We get to keep live references to Java classes in use because the VM can
142 unload a class that isn't being used, which invalidates field and method
144 https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
148 // #### only need one var I think
149 static size_t classRefCount = 0;
150 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
153 static jfieldID runningHackField;
154 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
155 static jmethodID entryGetKey, entryGetValue;
157 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
159 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
160 static char *get_string_resource_window (Window window, char *name);
163 /* Also creates double-buffered windows. */
165 create_pixmap (Window win, Drawable p)
168 // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
169 // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
170 // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
172 Assert (p->frame.width, "p->frame.width");
173 Assert (p->frame.height, "p->frame.height");
175 if (win->window.rh->jwxyz_gl_p) {
176 struct running_hack *rh = win->window.rh;
179 glGenTextures (1, &p->texture);
180 glBindTexture (GL_TEXTURE_2D, p->texture);
182 glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
183 to_pow2(p->frame.width), to_pow2(p->frame.height),
184 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
186 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
187 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
190 attribs[0] = EGL_WIDTH;
191 attribs[1] = p->frame.width;
192 attribs[2] = EGL_HEIGHT;
193 attribs[3] = p->frame.height;
194 attribs[4] = EGL_NONE;
196 p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
198 Assert (p->egl_surface != EGL_NO_SURFACE,
199 "XCreatePixmap: got EGL_NO_SURFACE");
202 p->image_data = malloc (p->frame.width * p->frame.height * 4);
208 free_pixmap (struct running_hack *rh, Pixmap p)
210 if (rh->jwxyz_gl_p) {
212 glDeleteTextures (1, &p->texture);
214 eglDestroySurface(rh->egl_display, p->egl_surface);
217 free (p->image_data);
223 prepare_context (struct running_hack *rh)
226 /* TODO: Adreno recommends against doing this every frame. */
227 Assert (eglMakeCurrent(rh->egl_display, rh->egl_surface, rh->egl_surface,
229 "eglMakeCurrent failed");
232 /* Don't set matrices here; set them when an Xlib call triggers
233 jwxyz_bind_drawable/jwxyz_set_matrices.
236 rh->current_drawable = NULL;
238 if (rh->xsft->visual == GL_VISUAL)
239 jwzgles_make_current (rh->gles_state);
244 get_egl_config_android(Window window, EGLDisplay *egl_display,
245 EGLConfig *egl_config)
247 # define R EGL_RED_SIZE
248 # define G EGL_GREEN_SIZE
249 # define B EGL_BLUE_SIZE
250 # define A EGL_ALPHA_SIZE
251 # define D EGL_DEPTH_SIZE
252 # define I EGL_BUFFER_SIZE
253 # define ST EGL_STENCIL_SIZE
254 EGLint templates[][40] = {
255 { R,8, G,8, B,8, A,8, D,8, ST,1, EGL_NONE }, /* rgba stencil */
256 { R,8, G,8, B,8, D,8, ST,1, EGL_NONE }, /* rgb stencil */
257 { R,4, G,4, B,4, D,4, ST,1, EGL_NONE },
258 { R,2, G,2, B,2, D,2, ST,1, EGL_NONE },
259 { R,8, G,8, B,8, A,8, D,8, EGL_NONE }, /* rgba */
260 { R,8, G,8, B,8, D,8, EGL_NONE }, /* rgb */
261 { R,4, G,4, B,4, D,4, EGL_NONE },
262 { R,2, G,2, B,2, D,2, EGL_NONE },
263 { R,1, G,1, B,1, D,1, EGL_NONE } /* monochrome */
267 int i, j, k, iter, pass;
269 char *glsls = get_string_resource_window (window, "prefersGLSL");
270 Bool glslp = (glsls && !strcasecmp(glsls, "true"));
271 iter = (glslp ? 2 : 1);
274 for (pass = 0; pass < iter; pass++)
276 for (i = 0; i < countof(templates); i++)
278 for (j = 0, k = 0; templates[i][j] != EGL_NONE; j += 2)
280 attrs[k++] = templates[i][j];
281 attrs[k++] = templates[i][j+1];
284 attrs[k++] = EGL_RENDERABLE_TYPE;
286 if (glslp && pass == 0)
287 attrs[k++] = EGL_OPENGL_ES3_BIT;
289 attrs[k++] = EGL_OPENGL_ES_BIT;
291 attrs[k++] = EGL_OPENGL_ES_BIT;
294 attrs[k++] = EGL_NONE;
297 if (eglChooseConfig (egl_display, attrs, egl_config, 1, &nconfig)
301 if (i < countof(templates))
304 Assert (*egl_config != 0, "no EGL config chosen");
308 const struct { int hexp; EGLint i; const char *s; } fields[] = {
309 { 1, EGL_CONFIG_ID, "config ID:" },
310 { 1, EGL_CONFIG_CAVEAT, "caveat:" },
311 { 1, EGL_CONFORMANT, "conformant:" },
312 { 0, EGL_COLOR_BUFFER_TYPE, "buffer type:" },
313 { 0, EGL_RED_SIZE, "color size:" },
314 { 0, EGL_TRANSPARENT_RED_VALUE, "transparency:" },
315 { 0, EGL_BUFFER_SIZE, "buffer size:" },
316 { 0, EGL_DEPTH_SIZE, "depth size:" },
317 { 0, EGL_LUMINANCE_SIZE, "lum size:" },
318 { 0, EGL_STENCIL_SIZE, "stencil size:" },
319 { 0, EGL_ALPHA_MASK_SIZE, "alpha mask:" },
320 { 0, EGL_LEVEL, "level:" },
321 { 0, EGL_SAMPLES, "samples:" },
322 { 0, EGL_SAMPLE_BUFFERS, "sample bufs:" },
323 { 0, EGL_NATIVE_RENDERABLE, "native render:" },
324 { 1, EGL_NATIVE_VISUAL_TYPE, "native type:" },
325 { 1, EGL_RENDERABLE_TYPE, "render type:" },
326 { 0, EGL_SURFACE_TYPE, "surface type:" },
327 { 0, EGL_BIND_TO_TEXTURE_RGB, "bind RGB:" },
328 { 0, EGL_BIND_TO_TEXTURE_RGBA, "bind RGBA:" },
329 { 0, EGL_MAX_PBUFFER_WIDTH, "buffer width:" },
330 { 0, EGL_MAX_PBUFFER_HEIGHT, "buffer height:" },
331 { 0, EGL_MAX_PBUFFER_PIXELS, "buffer pixels:" },
332 { 0, EGL_MAX_SWAP_INTERVAL, "max swap:" },
333 { 0, EGL_MIN_SWAP_INTERVAL, "min swap:" },
335 EGLint r=0, g=0, b=0, a=0, tt=0, tr=0, tg=0, tb=0;
336 eglGetConfigAttrib (egl_display, *egl_config, EGL_RED_SIZE, &r);
337 eglGetConfigAttrib (egl_display, *egl_config, EGL_GREEN_SIZE, &g);
338 eglGetConfigAttrib (egl_display, *egl_config, EGL_BLUE_SIZE, &b);
339 eglGetConfigAttrib (egl_display, *egl_config, EGL_ALPHA_SIZE, &a);
340 eglGetConfigAttrib (egl_display, *egl_config,
341 EGL_TRANSPARENT_TYPE, &tt);
342 eglGetConfigAttrib (egl_display, *egl_config,
343 EGL_TRANSPARENT_RED_VALUE, &tr);
344 eglGetConfigAttrib (egl_display, *egl_config,
345 EGL_TRANSPARENT_GREEN_VALUE,&tg);
346 eglGetConfigAttrib (egl_display, *egl_config,
347 EGL_TRANSPARENT_BLUE_VALUE, &tb);
348 for (i = 0; i < countof(fields); i++)
352 eglGetConfigAttrib (egl_display, *egl_config, fields[i].i, &v);
353 if (fields[i].i == EGL_RED_SIZE)
354 sprintf (s, "%d, %d, %d, %d", r, g, b, a);
355 else if (fields[i].i == EGL_TRANSPARENT_RED_VALUE && tt != EGL_NONE)
356 sprintf (s, "%d, %d, %d", tr, tg, tb);
357 else if (fields[i].i == EGL_CONFIG_CAVEAT)
358 strcpy (s, (v == EGL_NONE ? "none" :
359 v == EGL_SLOW_CONFIG ? "slow" :
360 # ifdef EGL_NON_CONFORMANT
361 v == EGL_NON_CONFORMANT ? "non-conformant" :
364 else if (fields[i].i == EGL_COLOR_BUFFER_TYPE)
365 strcpy (s, (v == EGL_RGB_BUFFER ? "RGB" :
366 v == EGL_LUMINANCE_BUFFER ? "luminance" :
368 else if (fields[i].i == EGL_CONFORMANT ||
369 fields[i].i == EGL_RENDERABLE_TYPE)
371 sprintf (s, "0x%02x", v);
372 if (v & EGL_OPENGL_BIT) strcat (s, " OpenGL");
373 if (v & EGL_OPENGL_ES_BIT) strcat (s, " GLES-1.x");
374 if (v & EGL_OPENGL_ES2_BIT) strcat (s, " GLES-2.0");
375 # ifdef EGL_OPENGL_ES3_BIT
376 if (v & EGL_OPENGL_ES3_BIT) strcat (s, " GLES-3.0");
378 if (v & EGL_OPENVG_BIT) strcat (s, " OpenVG");
380 else if (fields[i].hexp)
381 sprintf (s, "0x%02x", v);
383 sprintf (s, "%d", v);
387 if (*s) Log ("init: EGL %-14s %s\n", fields[i].s, s);
395 get_egl_context_android(Window window, EGLDisplay *egl_display,
396 EGLConfig *egl_config, EGLContext *egl_context)
398 EGLint context_attribs[][3] = {
399 { EGL_CONTEXT_CLIENT_VERSION, 1, EGL_NONE },
400 { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }
406 # ifdef EGL_OPENGL_ES3_BIT
407 char *glsls = get_string_resource_window (window, "prefersGLSL");
408 glslp = (glsls && !strcasecmp(glsls, "true"));
411 EGLint renderable_type;
412 eglGetConfigAttrib (egl_display, egl_config, EGL_RENDERABLE_TYPE,
414 Bool gles3p = (renderable_type & EGL_OPENGL_ES3_BIT) != 0;
415 glslp = glslp && gles3p;
420 iter = (glslp ? 2 : 1);
422 *egl_context = EGL_NO_CONTEXT;
423 for (pass = 0; pass < iter; pass++)
425 if (glslp && pass == 0)
426 attrs = context_attribs[1];
428 attrs = context_attribs[0];
429 *egl_context = eglCreateContext (egl_display, egl_config,
430 EGL_NO_CONTEXT, attrs);
431 if (*egl_context != EGL_NO_CONTEXT)
435 Assert (*egl_context != EGL_NO_CONTEXT, "init: EGL_NO_CONTEXT");
439 // Initialized OpenGL and runs the screenhack's init function.
442 doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
443 const struct function_table_entry *chosen,
444 jobject defaults, jint w, jint h, jobject jni_surface)
446 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
448 progname = chosen->progname;
449 rh->xsft = chosen->xsft;
451 rh->jobject = jwxyz_obj; // update this every time we call into C
453 (*env)->GetJavaVM (env, &global_jvm);
455 # undef ya_rand_init // This is the one and only place it is allowed
458 Window wnd = (Window) calloc(1, sizeof(*wnd));
460 wnd->frame.width = w;
461 wnd->frame.height = h;
465 progclass = rh->xsft->progclass;
467 if ((*env)->ExceptionOccurred(env)) abort();
469 // This has to come before resource processing. It does not do graphics.
470 if (rh->xsft->setup_cb)
471 rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);
473 if ((*env)->ExceptionOccurred(env)) abort();
475 // Load the defaults.
476 // Unceremoniously stolen from [PrefsReader defaultsToDict:].
478 jclass c = (*env)->GetObjectClass (env, defaults);
479 jmethodID m = (*env)->GetMethodID (env, c, "put",
480 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
482 if ((*env)->ExceptionOccurred(env)) abort();
484 const struct { const char *key, *val; } default_defaults[] = {
485 { "doubleBuffer", "True" },
486 { "multiSample", "False" },
487 { "texFontCacheSize", "30" },
488 { "textMode", "date" },
490 "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
491 { "grabDesktopImages", "True" },
492 { "chooseRandomImages", "True" },
495 for (int i = 0; i < countof(default_defaults); i++) {
496 const char *key = default_defaults[i].key;
497 const char *val = default_defaults[i].val;
498 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
499 strcpy (key2, progname);
503 // defaults.put(key2, val);
504 jstring jkey = (*env)->NewStringUTF (env, key2);
505 jstring jval = (*env)->NewStringUTF (env, val);
506 (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
507 (*env)->DeleteLocalRef (env, jkey);
508 (*env)->DeleteLocalRef (env, jval);
509 // Log ("default0: \"%s\" = \"%s\"", key2, val);
513 const char *const *defs = rh->xsft->defaults;
515 char *line = strdup (*defs);
518 while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
521 while (*val && *val != ':')
523 if (*val != ':') abort();
525 while (*val == ' ' || *val == '\t')
528 unsigned long L = strlen(val);
529 while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
532 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
533 strcpy (key2, progname);
537 // defaults.put(key2, val);
538 jstring jkey = (*env)->NewStringUTF (env, key2);
539 jstring jval = (*env)->NewStringUTF (env, val);
540 (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
541 (*env)->DeleteLocalRef (env, jkey);
542 (*env)->DeleteLocalRef (env, jval);
543 // Log ("default: \"%s\" = \"%s\"", key2, val);
549 (*env)->DeleteLocalRef (env, c);
550 if ((*env)->ExceptionOccurred(env)) abort();
553 /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */
555 /* jwxyz_gl_p controls which implementation of Pixmaps we are using.
557 - jwxyz-image.c implements them in CPU RAM, and is used for Android GL
558 hacks, and for kumppa, petri and slip, which are too slow otherwise.
560 - jwxyz-gl.c implements them in terms of OpenGL textures, and is used
561 for all other Android X11 hacks.
563 Why two implemementations of Pixmaps for Android?
565 - GL hacks don't tend to need much in the way of Xlib, and having a
566 GL context to render Xlib alongside a GL context for rendering GL
569 - X11 hacks render to a GL context because hardware acceleration tends
570 to work well with Xlib geometric stuff. Better framerates, lower
574 rh->xsft->visual == DEFAULT_VISUAL &&
575 strcmp (progname, "kumppa") &&
576 strcmp (progname, "petri") &&
577 strcmp (progname, "slip") &&
578 strcmp (progname, "testx11");
580 Log ("init: %s @ %dx%d: using JWXYZ_%s", progname, w, h,
581 rh->jwxyz_gl_p ? "GL" : "IMAGE");
583 rh->egl_p = rh->jwxyz_gl_p || rh->xsft->visual == GL_VISUAL;
585 int egl_major = -1, egl_minor = -1;
588 // GL init. Must come after resource processing.
590 rh->egl_display = eglGetDisplay (EGL_DEFAULT_DISPLAY);
591 Assert (rh->egl_display != EGL_NO_DISPLAY, "init: EGL_NO_DISPLAY");
593 Assert (eglInitialize (rh->egl_display, &egl_major, &egl_minor),
594 "eglInitialize failed");
596 get_egl_config_android (rh->window, rh->egl_display, &rh->egl_config);
598 get_egl_context_android(rh->window, rh->egl_display, rh->egl_config,
601 ANativeWindow *native_window =
602 ANativeWindow_fromSurface (env, jni_surface);
604 rh->egl_surface = eglCreateWindowSurface (rh->egl_display, rh->egl_config,
605 native_window, NULL);
606 Assert (rh->egl_surface != EGL_NO_SURFACE, "init: EGL_NO_SURFACE");
608 ANativeWindow_release (native_window);
610 rh->native_window = ANativeWindow_fromSurface (env, jni_surface);
612 int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
613 WINDOW_FORMAT_RGBX_8888);
615 // Maybe check this earlier?
616 Log ("can't set format (%d), surface may be invalid.", result);
617 (*env)->ThrowNew (env,
618 (*env)->FindClass(env, "org/jwz/xscreensaver/jwxyz$SurfaceLost"),
621 ANativeWindow_release (rh->native_window);
622 rh->native_window = NULL;
627 prepare_context (rh);
630 // GL_SHADING_LANGUAGE_VERSION undefined
631 Log ("init %s / %s / %s / EGL %d.%d",
632 glGetString (GL_VENDOR),
633 glGetString (GL_RENDERER),
634 glGetString (GL_VERSION),
635 egl_major, egl_minor);
638 if (rh->jwxyz_gl_p) {
639 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
640 rh->gl_fbo_p = jwzgles_gluCheckExtension (
641 (const GLubyte *)"GL_OES_framebuffer_object", extensions);
644 glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default);
645 Assert (!rh->fb_default, "default framebuffer not current framebuffer");
646 glGenFramebuffersOES (1, &rh->fb_pixmap);
649 wnd->egl_surface = rh->egl_surface;
652 rh->frontbuffer_p = False;
654 if (rh->xsft->visual == DEFAULT_VISUAL ||
655 (rh->xsft->visual == GL_VISUAL &&
656 strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
658 rh->frontbuffer_p = True;
660 # if 0 /* Might need to be 0 for Adreno...? */
661 if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
663 eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
665 if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
666 eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR,
667 EGL_BUFFER_PRESERVED);
668 rh->frontbuffer_p = False;
673 if (rh->frontbuffer_p) {
674 /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
675 create_pixmap (wnd, wnd);
677 /* No preserving backbuffers, so manual blit from back to "front". */
678 rh->frontbuffer.type = PIXMAP;
679 rh->frontbuffer.frame = wnd->frame;
680 rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL);
683 rh->frontbuffer.texture = 0;
685 Assert (wnd->egl_surface != rh->egl_surface,
686 "oops: wnd->egl_surface == rh->egl_surface");
687 rh->frontbuffer.egl_surface = rh->egl_surface;
692 rh->dpy = jwxyz_gl_make_display(wnd);
696 create_pixmap (wnd, wnd);
698 static const unsigned char rgba_bytes[] = {0, 1, 2, 3};
699 rh->dpy = jwxyz_image_make_display(wnd, rgba_bytes);
703 Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
704 // TODO: Zero looks right, but double-check that is the right number
706 /* Requires valid rh->dpy. */
708 rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
710 if (rh->xsft->visual == GL_VISUAL)
711 rh->gles_state = jwzgles_make_state();
724 # ifdef GETTIMEOFDAY_TWO_ARGS
726 gettimeofday(&now, &tzp);
731 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
736 // Animates a single frame of the current hack.
739 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
742 double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
743 fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
746 unsigned long delay = 0;
748 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
750 Window wnd = rh->window;
752 prepare_context (rh);
755 /* There is some kind of weird redisplay race condition between Settings
756 and the launching hack: e.g., Abstractile does XClearWindow at init,
757 but the screen is getting filled with random bits. So let's wait a
758 few frames before really starting up.
760 TODO: Is this still true?
762 if (++rh->frame_count < 8) {
763 /* glClearColor (1.0, 0.0, 1.0, 0.0); */
764 glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */
770 fps1 = double_time();
773 // The init function might do graphics (e.g. XClearWindow) so it has
774 // to be run from inside onDrawFrame, not onSurfaceChanged.
776 if (! rh->initted_p) {
778 void *(*init_cb) (Display *, Window, void *) =
779 (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
781 if (rh->xsft->visual == DEFAULT_VISUAL) {
783 get_pixel_resource (rh->dpy, 0, "background", "Background");
784 XSetWindowBackground (rh->dpy, wnd, bg);
785 XClearWindow (rh->dpy, wnd);
788 rh->closure = init_cb (rh->dpy, wnd, rh->xsft->setup_arg);
789 rh->initted_p = True;
791 /* ignore_rotation_p doesn't quite work at the moment. */
792 rh->ignore_rotation_p = False;
794 (rh->xsft->visual == DEFAULT_VISUAL &&
795 get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
798 if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
799 rh->fpst = fps_init (rh->dpy, wnd);
800 if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
805 if ((*env)->ExceptionOccurred(env)) abort();
809 fps2 = double_time();
812 // Apparently events don't come in on the drawing thread, and JNI flips
813 // out. So we queue them there and run them here.
814 // TODO: Events should be coming in on the drawing thread now, so dump this.
815 send_queued_events (rh);
818 fps3 = double_time();
821 delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure);
824 jwxyz_gl_flush (rh->dpy);
827 fps4 = double_time();
829 if (rh->fpst && rh->xsft->fps_cb)
830 rh->xsft->fps_cb (rh->dpy, wnd, rh->fpst, rh->closure);
833 if (rh->jwxyz_gl_p && rh->frontbuffer_p) {
834 jwxyz_gl_copy_area (rh->dpy, wnd, &rh->frontbuffer, rh->copy_gc,
835 0, 0, wnd->frame.width, wnd->frame.height,
839 // Getting failure here before/during/after resize, sometimes. Log sez:
840 // W/Adreno-EGLSUB(18428): <DequeueBuffer:607>: dequeue native buffer fail: No such device, buffer=0x5f93bf5c, handle=0x0
841 if (!eglSwapBuffers(rh->egl_display, rh->egl_surface)) {
842 Log ("eglSwapBuffers failed: 0x%x (probably asynchronous resize)",
846 ANativeWindow_Buffer buffer;
847 ARect rect = {0, 0, wnd->frame.width, wnd->frame.height};
848 int32_t result = ANativeWindow_lock(rh->native_window, &buffer, &rect);
850 Log ("ANativeWindow_lock failed (result = %d), frame dropped", result);
852 /* Android can resize surfaces asynchronously. */
853 if (wnd->frame.width != buffer.width ||
854 wnd->frame.height != buffer.height) {
855 Log ("buffer/window size mismatch: %dx%d (format = %d), wnd: %dx%d",
856 buffer.width, buffer.height, buffer.format,
857 wnd->frame.width, wnd->frame.height);
860 Assert (buffer.format == WINDOW_FORMAT_RGBA_8888 ||
861 buffer.format == WINDOW_FORMAT_RGBX_8888,
862 "bad buffer format");
864 jwxyz_blit (wnd->image_data, jwxyz_image_pitch (wnd), 0, 0,
865 buffer.bits, buffer.stride * 4, 0, 0,
866 MIN(wnd->frame.width, buffer.width),
867 MIN(wnd->frame.height, buffer.height));
868 // TODO: Clear any area to sides and bottom.
870 ANativeWindow_unlockAndPost (rh->native_window);
877 Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
878 (int) ((fps1-fps0)*1000000),
879 (int) ((fps2-fps1)*1000000),
880 (int) ((fps3-fps2)*1000000),
881 (int) ((fps4-fps3)*1000000),
882 (int) ((double_time()-fps4)*1000000));
889 // Extracts the C structure that is stored in the jwxyz Java object.
890 static struct running_hack *
891 getRunningHack (JNIEnv *env, jobject thiz)
893 jlong result = (*env)->GetLongField (env, thiz, runningHackField);
894 struct running_hack *rh = (struct running_hack *)(intptr_t)result;
896 rh->jobject = thiz; // update this every time we call into C
900 // Look up a class and mark it global in the provided variable.
902 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
904 jclass clazz = (*env)->FindClass(env, className);
905 *globalRef = (*env)->NewGlobalRef(env, clazz);
910 /* Note: to find signature strings for native methods:
911 cd ./project/xscreensaver/build/intermediates/classes/debug/
912 javap -s -p org.jwz.xscreensaver.jwxyz
916 // Implementation of jwxyz's nativeInit Java method.
918 JNIEXPORT void JNICALL
919 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
920 jstring jhack, jobject defaults,
924 pthread_mutex_lock(&mutg);
926 struct running_hack *rh = calloc(1, sizeof(struct running_hack));
928 if ((*env)->ExceptionOccurred(env)) abort();
931 if (!classRefCount) {
932 jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
933 globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
934 runningHackField = (*env)->GetFieldID
935 (env, classjwxyz, "nativeRunningHackPtr", "J");
936 if ((*env)->ExceptionOccurred(env)) abort();
938 jclass classIterable =
939 acquireClass(env, "java/lang/Iterable", &globalRefIterable);
940 iterableIterator = (*env)->GetMethodID
941 (env, classIterable, "iterator", "()Ljava/util/Iterator;");
942 if ((*env)->ExceptionOccurred(env)) abort();
944 jclass classIterator =
945 acquireClass(env, "java/util/Iterator", &globalRefIterator);
946 iteratorHasNext = (*env)->GetMethodID
947 (env, classIterator, "hasNext", "()Z");
948 iteratorNext = (*env)->GetMethodID
949 (env, classIterator, "next", "()Ljava/lang/Object;");
950 if ((*env)->ExceptionOccurred(env)) abort();
952 jclass classMapEntry =
953 acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
954 entryGetKey = (*env)->GetMethodID
955 (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
956 entryGetValue = (*env)->GetMethodID
957 (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
958 if ((*env)->ExceptionOccurred(env)) abort();
963 // Store the C struct into the Java object.
964 (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
966 // TODO: Sort the list so binary search works.
967 const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
971 if (chosen == countof(function_table)) {
972 Log ("Hack not found: %s", hack);
975 if (!strcmp(function_table[chosen].progname, hack))
980 (*env)->ReleaseStringUTFChars(env, jhack, hack);
982 doinit (thiz, rh, env, &function_table[chosen], defaults, w, h,
985 pthread_mutex_unlock(&mutg);
989 JNIEXPORT void JNICALL
990 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
991 jint w, jint h, jdouble rot)
993 pthread_mutex_lock(&mutg);
994 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
996 current_rotation = rot;
998 Log ("native rotation: %f", current_rotation);
1000 struct running_hack *rh = getRunningHack(env, thiz);
1002 prepare_context (rh);
1005 glViewport (0, 0, w, h);
1007 int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
1008 WINDOW_FORMAT_RGBX_8888);
1010 Log ("failed to resize surface (%d)", result);
1013 Window wnd = rh->window;
1016 wnd->frame.width = w;
1017 wnd->frame.height = h;
1019 if (ignore_rotation_p(rh->dpy) &&
1020 rot != 0 && rot != 180 && rot != -180) {
1024 wnd->frame.width = w;
1025 wnd->frame.height = h;
1028 if (rh->jwxyz_gl_p) {
1029 if (rh->frontbuffer_p) {
1030 free_pixmap (rh, wnd);
1031 create_pixmap (wnd, wnd);
1033 rh->frontbuffer.frame = wnd->frame;
1035 rh->frontbuffer.egl_surface = rh->egl_surface;
1038 jwxyz_window_resized (rh->dpy);
1040 free_pixmap (rh, wnd);
1041 create_pixmap (wnd, wnd);
1042 XClearWindow (rh->dpy, wnd); // TODO: This is lame. Copy the bits.
1046 rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
1047 wnd->frame.width, wnd->frame.height);
1049 if (rh->xsft->visual == GL_VISUAL) {
1050 glMatrixMode (GL_PROJECTION);
1051 glRotatef (-rot, 0, 0, 1);
1052 glMatrixMode (GL_MODELVIEW);
1056 pthread_mutex_unlock(&mutg);
1060 JNIEXPORT jlong JNICALL
1061 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
1063 pthread_mutex_lock(&mutg);
1064 struct running_hack *rh = getRunningHack(env, thiz);
1065 jlong result = drawXScreenSaver(env, rh);
1066 pthread_mutex_unlock(&mutg);
1071 // TODO: Check Java side is calling this properly
1072 JNIEXPORT void JNICALL
1073 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
1075 pthread_mutex_lock(&mutg);
1076 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
1078 struct running_hack *rh = getRunningHack(env, thiz);
1080 prepare_context (rh);
1083 rh->xsft->fps_free (rh->fpst);
1085 rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
1087 XFreeGC (rh->dpy, rh->copy_gc);
1088 if (rh->xsft->visual == GL_VISUAL)
1089 jwzgles_free_state ();
1092 jwxyz_gl_free_display(rh->dpy);
1094 jwxyz_image_free_display(rh->dpy);
1097 // eglDestroy* probably isn't necessary here.
1098 eglMakeCurrent (rh->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
1100 eglDestroySurface (rh->egl_display, rh->egl_surface);
1101 eglDestroyContext (rh->egl_display, rh->egl_ctx);
1102 eglTerminate (rh->egl_display);
1104 free_pixmap (rh, rh->window);
1105 if (rh->native_window)
1106 ANativeWindow_release (rh->native_window);
1110 (*env)->SetLongField(env, thiz, runningHackField, 0);
1113 if (!classRefCount) {
1114 (*env)->DeleteGlobalRef(env, globalRefjwxyz);
1115 (*env)->DeleteGlobalRef(env, globalRefIterable);
1116 (*env)->DeleteGlobalRef(env, globalRefIterator);
1117 (*env)->DeleteGlobalRef(env, globalRefMapEntry);
1121 pthread_mutex_unlock(&mutg);
1126 send_event (struct running_hack *rh, XEvent *e)
1128 // Assumes mutex is locked and context is prepared
1130 int *xP = 0, *yP = 0;
1131 switch (e->xany.type) {
1132 case ButtonPress: case ButtonRelease:
1142 // Rotate the coordinates in the events to match the pixels.
1144 if (ignore_rotation_p (rh->dpy)) {
1145 Window win = XRootWindow (rh->dpy, 0);
1146 int w = win->frame.width;
1147 int h = win->frame.height;
1149 switch ((int) current_rotation) {
1150 case 180: case -180: // #### untested
1155 swap = *xP; *xP = *yP; *yP = swap;
1158 case -90: case 270: // #### untested
1159 swap = *xP; *xP = *yP; *yP = swap;
1165 rh->window->window.last_mouse_x = *xP;
1166 rh->window->window.last_mouse_y = *yP;
1169 return (rh->xsft->event_cb
1170 ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
1176 send_queued_events (struct running_hack *rh)
1178 struct event_queue *event, *next;
1179 if (! rh->event_queue) return;
1180 for (event = rh->event_queue, next = event->next;
1182 event = next, next = (event ? event->next : 0)) {
1183 if (! send_event (rh, &event->event)) {
1184 // #### flash the screen or something
1188 rh->event_queue = 0;
1193 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
1195 pthread_mutex_lock (&mutg);
1196 struct running_hack *rh = getRunningHack (env, thiz);
1197 struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
1198 memcpy (&q->event, e, sizeof(*e));
1201 // Put it at the end.
1202 struct event_queue *oq;
1203 for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
1208 rh->event_queue = q;
1210 pthread_mutex_unlock (&mutg);
1214 JNIEXPORT void JNICALL
1215 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
1216 int x, int y, jboolean down)
1219 memset (&e, 0, sizeof(e));
1220 e.xany.type = (down ? ButtonPress : ButtonRelease);
1221 e.xbutton.button = Button1;
1224 queue_event (env, thiz, &e);
1227 JNIEXPORT void JNICALL
1228 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
1232 memset (&e, 0, sizeof(e));
1233 e.xany.type = MotionNotify;
1236 queue_event (env, thiz, &e);
1239 JNIEXPORT void JNICALL
1240 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
1245 memset (&e, 0, sizeof(e));
1246 e.xkey.keycode = code;
1247 e.xkey.state = code;
1248 e.xany.type = (down_p ? KeyPress : KeyRelease);
1249 queue_event (env, thiz, &e);
1250 e.xany.type = KeyRelease;
1251 queue_event (env, thiz, &e);
1255 /***************************************************************************
1256 Backend functions for jwxyz-gl.c
1260 finish_bind_drawable (Display *dpy, Drawable dst)
1264 glViewport (0, 0, dst->frame.width, dst->frame.height);
1265 jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False);
1270 bind_drawable_fbo (struct running_hack *rh, Drawable d)
1272 glBindFramebufferOES (GL_FRAMEBUFFER_OES,
1273 d->texture ? rh->fb_pixmap : rh->fb_default);
1275 glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
1276 GL_TEXTURE_2D, d->texture, 0);
1282 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
1284 struct running_hack *rh = w->window.rh;
1285 JNIEnv *env = w->window.rh->jni_env;
1286 if ((*env)->ExceptionOccurred(env)) abort();
1287 if (rh->current_drawable != d) {
1289 bind_drawable_fbo (rh, d);
1291 eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx);
1293 finish_bind_drawable (dpy, d);
1294 rh->current_drawable = d;
1299 jwxyz_gl_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
1300 int src_x, int src_y,
1301 unsigned int width, unsigned int height,
1302 int dst_x, int dst_y)
1304 Window w = XRootWindow (dpy, 0);
1305 struct running_hack *rh = w->window.rh;
1307 jwxyz_gl_flush (dpy);
1309 if (rh->gl_fbo_p && src->texture && src != dst) {
1310 bind_drawable_fbo (rh, dst);
1311 finish_bind_drawable (dpy, dst);
1312 rh->current_drawable = NULL;
1314 jwxyz_gl_set_gc (dpy, gc);
1316 glBindTexture (GL_TEXTURE_2D, src->texture);
1318 jwxyz_gl_draw_image (dpy, gc, GL_TEXTURE_2D, to_pow2(src->frame.width),
1319 to_pow2(src->frame.height),
1320 src_x, src->frame.height - src_y - height,
1321 jwxyz_drawable_depth (src), width, height,
1322 dst_x, dst_y, False);
1328 // Hilarious display corruption ahoy! (Note to self: it's on the emulator.)
1329 // TODO for Dave: Recheck behavior on the emulator with the better Pixmap support.
1331 rh->current_drawable = NULL;
1333 bind_drawable_fbo (rh, src);
1335 eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx);
1337 jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y,
1338 width, height, dst_x, dst_y);
1341 bind_drawable_fbo (rh, dst);
1342 finish_bind_drawable (dpy, dst);
1344 jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
1345 jwxyz_drawable_depth (src),
1346 width, height, dst_x, dst_y);
1350 jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
1351 width, height, dst_x, dst_y);
1358 jwxyz_assert_drawable (Window main_window, Drawable d)
1360 check_gl_error("jwxyz_assert_drawable");
1365 jwxyz_assert_gl (void)
1367 check_gl_error("jwxyz_assert_gl");
1371 /***************************************************************************
1372 Backend functions for jwxyz-image.c
1376 jwxyz_image_pitch (Drawable d)
1378 return d->frame.width * 4;
1382 jwxyz_image_data (Drawable d)
1384 Assert (d->image_data, "no image storage (i.e. keep Xlib off the Window)");
1385 return d->image_data;
1390 jwxyz_frame (Drawable d)
1397 jwxyz_drawable_depth (Drawable d)
1399 return (d->type == WINDOW
1400 ? visual_depth (NULL, NULL)
1406 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
1412 xp->x = w->window.last_mouse_x;
1413 xp->y = w->window.last_mouse_y;
1419 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
1421 fps_compute (fpst, 0, -1);
1427 XCreatePixmap (Display *dpy, Drawable d,
1428 unsigned int width, unsigned int height, unsigned int depth)
1430 Window win = XRootWindow(dpy, 0);
1432 Pixmap p = malloc(sizeof(*p));
1436 p->frame.width = width;
1437 p->frame.height = height;
1439 Assert(depth == 1 || depth == visual_depth(NULL, NULL),
1440 "XCreatePixmap: bad depth");
1441 p->pixmap.depth = depth;
1443 create_pixmap (win, p);
1445 /* For debugging. */
1447 jwxyz_bind_drawable (dpy, win, p);
1448 glClearColor (frand(1), frand(1), frand(1), 0);
1449 glClear (GL_COLOR_BUFFER_BIT);
1457 XFreePixmap (Display *d, Pixmap p)
1459 struct running_hack *rh = XRootWindow(d, 0)->window.rh;
1461 if (rh->jwxyz_gl_p) {
1464 if (rh->current_drawable == p)
1465 rh->current_drawable = NULL;
1468 free_pixmap (rh, p);
1475 current_device_rotation (void)
1477 return current_rotation;
1481 ignore_rotation_p (Display *dpy)
1483 struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
1484 return rh->ignore_rotation_p;
1489 jstring_dup (JNIEnv *env, jstring str)
1491 Assert (str, "expected jstring, not null");
1492 const char *cstr = (*env)->GetStringUTFChars (env, str, 0);
1493 size_t len = (*env)->GetStringUTFLength (env, str) + 1;
1494 char *result = malloc (len);
1496 memcpy (result, cstr, len);
1498 (*env)->ReleaseStringUTFChars (env, str, cstr);
1504 get_string_resource_window (Window window, char *name)
1506 JNIEnv *env = window->window.rh->jni_env;
1507 jobject obj = window->window.rh->jobject;
1509 if ((*env)->ExceptionOccurred(env)) abort();
1510 jstring jstr = (*env)->NewStringUTF (env, name);
1511 jclass c = (*env)->GetObjectClass (env, obj);
1512 jmethodID m = (*env)->GetMethodID (env, c, "getStringResource",
1513 "(Ljava/lang/String;)Ljava/lang/String;");
1514 if ((*env)->ExceptionOccurred(env)) abort();
1517 ? (*env)->CallObjectMethod (env, obj, m, jstr)
1519 (*env)->DeleteLocalRef (env, c);
1520 (*env)->DeleteLocalRef (env, jstr);
1523 ret = jstring_dup (env, jvalue);
1525 Log("pref %s = %s", name, (ret ? ret : "(null)"));
1531 get_string_resource (Display *dpy, char *name, char *class)
1533 return get_string_resource_window (RootWindow (dpy, 0), name);
1537 /* Returns the contents of the URL. */
1539 textclient_mobile_url_string (Display *dpy, const char *url)
1541 Window window = RootWindow (dpy, 0);
1542 JNIEnv *env = window->window.rh->jni_env;
1543 jobject obj = window->window.rh->jobject;
1545 jstring jstr = (*env)->NewStringUTF (env, url);
1546 jclass c = (*env)->GetObjectClass (env, obj);
1547 jmethodID m = (*env)->GetMethodID (env, c, "loadURL",
1548 "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
1549 if ((*env)->ExceptionOccurred(env)) abort();
1551 ? (*env)->CallObjectMethod (env, obj, m, jstr)
1553 (*env)->DeleteLocalRef (env, c);
1554 (*env)->DeleteLocalRef (env, jstr);
1556 char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1559 int L = (*env)->GetDirectBufferCapacity (env, buf);
1560 body2 = malloc (L + 1);
1561 memcpy (body2, body, L);
1564 body2 = strdup ("ERROR");
1568 (*env)->DeleteLocalRef (env, buf);
1575 jwxyz_scale (Window main_window)
1577 // TODO: Use the actual device resolution.
1582 jwxyz_font_scale (Window main_window)
1584 return jwxyz_scale (main_window);
1589 jwxyz_default_font_family (int require)
1591 /* Font families in XLFDs are totally ignored (for now). */
1592 return "sans-serif";
1597 jwxyz_load_native_font (Window window,
1598 int traits_jwxyz, int mask_jwxyz,
1599 const char *font_name_ptr, size_t font_name_length,
1600 int font_name_type, float size,
1601 char **family_name_ret,
1602 int *ascent_ret, int *descent_ret)
1604 JNIEnv *env = window->window.rh->jni_env;
1605 jobject obj = window->window.rh->jobject;
1607 jstring jname = NULL;
1608 if (font_name_ptr) {
1609 char *name_nul = malloc(font_name_length + 1);
1610 memcpy(name_nul, font_name_ptr, font_name_length);
1611 name_nul[font_name_length] = 0;
1612 jname = (*env)->NewStringUTF (env, name_nul);
1616 jclass c = (*env)->GetObjectClass (env, obj);
1617 jmethodID m = (*env)->GetMethodID (env, c, "loadFont",
1618 "(IILjava/lang/String;IF)[Ljava/lang/Object;");
1619 if ((*env)->ExceptionOccurred(env)) abort();
1621 jobjectArray array = (m
1622 ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
1623 (jint)traits_jwxyz, jname,
1624 (jint)font_name_type, (jfloat)size)
1627 (*env)->DeleteLocalRef (env, c);
1630 jobject font = (*env)->GetObjectArrayElement (env, array, 0);
1631 jobject family_name =
1632 (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
1633 jobject asc = (*env)->GetObjectArrayElement (env, array, 2);
1634 jobject desc = (*env)->GetObjectArrayElement (env, array, 3);
1635 if ((*env)->ExceptionOccurred(env)) abort();
1637 if (family_name_ret)
1638 *family_name_ret = jstring_dup (env, family_name);
1640 jobject paint = (*env)->NewGlobalRef (env, font);
1641 if ((*env)->ExceptionOccurred(env)) abort();
1643 c = (*env)->GetObjectClass(env, asc);
1644 m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1645 if ((*env)->ExceptionOccurred(env)) abort();
1647 *ascent_ret = (int) (*env)->CallFloatMethod (env, asc, m);
1648 *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1650 return (void *) paint;
1658 jwxyz_release_native_font (Display *dpy, void *native_font)
1660 Window window = RootWindow (dpy, 0);
1661 JNIEnv *env = window->window.rh->jni_env;
1662 if ((*env)->ExceptionOccurred(env)) abort();
1663 (*env)->DeleteGlobalRef (env, (jobject) native_font);
1664 if ((*env)->ExceptionOccurred(env)) abort();
1668 /* If the local reference table fills up, use this to figure out where
1669 you missed a call to DeleteLocalRef. */
1671 static void dump_reference_tables(JNIEnv *env)
1673 jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1674 jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1676 (*env)->CallStaticVoidMethod (env, c, m);
1677 (*env)->DeleteLocalRef (env, c);
1682 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1683 // string. If pixmap_ret is provided, also renders the text.
1686 jwxyz_render_text (Display *dpy, void *native_font,
1687 const char *str, size_t len, Bool utf8, Bool antialias_p,
1688 XCharStruct *cs, char **pixmap_ret)
1690 Window window = RootWindow (dpy, 0);
1691 JNIEnv *env = window->window.rh->jni_env;
1692 jobject obj = window->window.rh->jobject;
1697 s2 = malloc (len + 1);
1698 memcpy (s2, str, len);
1700 } else { // Convert Latin1 to UTF8
1701 s2 = malloc (len * 2 + 1);
1702 unsigned char *s3 = (unsigned char *) s2;
1704 for (i = 0; i < len; i++) {
1705 unsigned char c = ((unsigned char *) str)[i];
1709 *s3++ = (0xC0 | (0x03 & (c >> 6)));
1710 *s3++ = (0x80 | (0x3F & c));
1716 jstring jstr = (*env)->NewStringUTF (env, s2);
1717 jclass c = (*env)->GetObjectClass (env, obj);
1718 jmethodID m = (*env)->GetMethodID (env, c, "renderText",
1719 "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;");
1720 if ((*env)->ExceptionOccurred(env)) abort();
1723 ? (*env)->CallObjectMethod (env, obj, m,
1724 (jobject) native_font,
1726 (pixmap_ret ? JNI_TRUE : JNI_FALSE),
1729 (*env)->DeleteLocalRef (env, c);
1730 (*env)->DeleteLocalRef (env, jstr);
1733 if ((*env)->ExceptionOccurred(env)) abort();
1734 unsigned char *bits = (unsigned char *)
1735 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1738 int L = (*env)->GetDirectBufferCapacity (env, buf);
1739 if (L < 10) abort();
1740 cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1741 cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1742 cs->width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1743 cs->ascent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1744 cs->descent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1747 char *pix = malloc (L - i);
1749 memcpy (pix, bits + i, L - i);
1753 memset (cs, 0, sizeof(*cs));
1759 (*env)->DeleteLocalRef (env, buf);
1763 // Returns the verbose Unicode name of this character, like "agrave" or
1764 // "daggerdouble". Used by Unicrud, and by Fontglide with debugMetrics.
1767 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1769 JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1770 /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1772 c = (*env)->FindClass (env, "java/lang/Character"),
1773 c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
1775 if ((*env)->ExceptionOccurred(env)) abort();
1776 jmethodID m = (*env)->GetStaticMethodID (
1777 env, c, "getName", "(I)Ljava/lang/String;");
1778 jthrowable exc = (*env)->ExceptionOccurred(env);
1780 if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1781 (*env)->ExceptionClear (env);
1782 Assert (!m, "jwxyz_unicode_character_name: m?");
1791 jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1793 ret = jstring_dup (env, name);
1797 asprintf(&ret, "U+%.4lX", uc);
1804 /* Called from utils/grabclient.c */
1806 jwxyz_draw_random_image (Display *dpy, Drawable drawable, GC gc)
1808 Window window = RootWindow (dpy, 0);
1809 struct running_hack *rh = window->window.rh;
1810 JNIEnv *env = rh->jni_env;
1811 jobject obj = rh->jobject;
1814 get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1816 get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1818 get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1820 if (!images_p && !grab_p)
1823 if (grab_p && images_p) {
1824 grab_p = !(random() & 5); /* if both, screenshot 1/5th of the time */
1828 jclass c = (*env)->GetObjectClass (env, obj);
1829 jmethodID m = (*env)->GetMethodID (env, c,
1832 : "checkThenLoadRandomImage"),
1833 "(IIZ)[Ljava/lang/Object;");
1834 if ((*env)->ExceptionOccurred(env)) abort();
1835 jobjectArray img_name = (
1837 ? (*env)->CallObjectMethod (env, obj, m,
1838 drawable->frame.width, drawable->frame.height,
1839 (rotate_p ? JNI_TRUE : JNI_FALSE))
1841 if ((*env)->ExceptionOccurred(env)) abort();
1842 (*env)->DeleteLocalRef (env, c);
1845 fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image"));
1849 jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 0);
1851 AndroidBitmapInfo bmp_info;
1852 AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1854 XImage *img = XCreateImage (dpy, NULL, visual_depth(NULL, NULL),
1856 bmp_info.width, bmp_info.height, 0,
1859 AndroidBitmap_lockPixels (env, jbitmap, (void **) &img->data);
1861 XPutImage (dpy, drawable, gc, img, 0, 0,
1862 (drawable->frame.width - bmp_info.width) / 2,
1863 (drawable->frame.height - bmp_info.height) / 2,
1864 bmp_info.width, bmp_info.height);
1866 AndroidBitmap_unlockPixels (env, jbitmap);
1868 XDestroyImage (img);
1870 return jstring_dup (env, (*env)->GetObjectArrayElement (env, img_name, 1));
1875 jwxyz_png_to_ximage (Display *dpy, Visual *visual,
1876 const unsigned char *png_data, unsigned long data_size)
1878 Window window = RootWindow (dpy, 0);
1879 struct running_hack *rh = window->window.rh;
1880 JNIEnv *env = rh->jni_env;
1881 jobject obj = rh->jobject;
1882 jclass c = (*env)->GetObjectClass (env, obj);
1883 jmethodID m = (*env)->GetMethodID (env, c, "decodePNG",
1884 "([B)Landroid/graphics/Bitmap;");
1885 if ((*env)->ExceptionOccurred(env)) abort();
1886 jbyteArray jdata = (*env)->NewByteArray (env, data_size);
1887 (*env)->SetByteArrayRegion (env, jdata, 0,
1888 data_size, (const jbyte *) png_data);
1891 ? (*env)->CallObjectMethod (env, obj, m, jdata)
1893 if ((*env)->ExceptionOccurred(env)) abort();
1894 (*env)->DeleteLocalRef (env, c);
1895 (*env)->DeleteLocalRef (env, jdata);
1899 AndroidBitmapInfo bmp_info;
1900 AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1902 XImage *img = XCreateImage (dpy, NULL, 32, ZPixmap, 0, NULL,
1903 bmp_info.width, bmp_info.height, 8,
1906 AndroidBitmap_lockPixels (env, jbitmap, (void **) &bits);
1907 img->data = (char *) calloc (img->bytes_per_line, img->height);
1908 memcpy (img->data, bits, img->bytes_per_line * img->height);
1909 AndroidBitmap_unlockPixels (env, jbitmap);
1911 // Java should have returned ARGB data.
1912 // WTF, why isn't ANDROID_BITMAP_FORMAT_ARGB_8888 defined?
1913 if (bmp_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) abort();
1914 # ifndef __BYTE_ORDER__ // A GCC (and Clang)-ism.
1915 # error Need a __BYTE_ORDER__.
1916 # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1917 img->byte_order = img->bitmap_bit_order = LSBFirst;
1918 # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1919 img->byte_order = img->bitmap_bit_order = MSBFirst;
1921 # error Need a __BYTE_ORDER__.
1924 static const union {
1927 } c0 = {{0xff, 0x00, 0x00, 0x00}}, c1 = {{0x00, 0xff, 0x00, 0x00}},
1928 c2 = {{0x00, 0x00, 0xff, 0x00}};
1930 img->red_mask = c0.pixel;
1931 img->green_mask = c1.pixel;
1932 img->blue_mask = c2.pixel;
1937 #endif /* HAVE_ANDROID */