1 /* xscreensaver, Copyright (c) 2016-2018 Jamie Zawinski <jwz@jwz.org>
2 *
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
9 * implied warranty.
10 *
11 * This file is three related things:
12 *
13 * - It is the Android-specific C companion to jwxyz-gl.c;
14 * - It is how C calls into Java to do things that OpenGL does not
15 * have access to without Java-based APIs;
16 * - It is how the jwxyz.java class calls into C to run the hacks.
17 */
18
19 #ifdef HAVE_ANDROID /* whole file */
20
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <time.h>
25 #include <math.h>
26 #include <setjmp.h>
27
28 #include <GLES/gl.h>
29 #include <GLES/glext.h>
30 #include <jni.h>
31 #include <android/bitmap.h>
32 #include <android/log.h>
33 #include <android/native_window_jni.h>
34 #include <pthread.h>
35
36 #include "screenhackI.h"
37 #include "jwxyzI.h"
38 #include "jwzglesI.h"
39 #include "jwxyz-android.h"
40 #include "textclient.h"
41 #include "grabscreen.h"
42 #include "pow2.h"
43
44
45 #define countof(x) (sizeof(x)/sizeof(*(x)))
46
47 extern struct xscreensaver_function_table *xscreensaver_function_table;
48
49 struct function_table_entry {
50 const char *progname;
51 struct xscreensaver_function_table *xsft;
52 };
53
54 #include "gen/function-table.h"
55
56 struct event_queue {
57 XEvent event;
58 struct event_queue *next;
59 };
60
61 static void send_queued_events (struct running_hack *rh);
62
63 const char *progname;
64 const char *progclass;
65 int mono_p = 0;
66
67 static JavaVM *global_jvm;
68 static jmp_buf jmp_target;
69
70 static double current_rotation = 0;
71
72 extern void check_gl_error (const char *type);
73
74 void
jwxyz_logv(Bool error,const char * fmt,va_list args)75 jwxyz_logv(Bool error, const char *fmt, va_list args)
76 {
77 __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
78 "xscreensaver", fmt, args);
79
80 /* The idea here is that if the device/emulator dies shortly after a log
81 message, then waiting here for a short while should increase the odds
82 that adb logcat can pick up the message before everything blows up. Of
83 course, doing this means dumping a ton of messages will slow things down
84 significantly.
85 */
86 # if 0
87 struct timespec ts;
88 ts.tv_sec = 0;
89 ts.tv_nsec = 25 * 1000000;
90 nanosleep(&ts, NULL);
91 # endif
92 }
93
94 /* Handle an abort on Android
95 TODO: Test that Android handles aborts properly
96 */
97 void
jwxyz_abort(const char * fmt,...)98 jwxyz_abort (const char *fmt, ...)
99 {
100 /* Send error to Android device log */
101 if (!fmt || !*fmt)
102 fmt = "abort";
103
104 va_list args;
105 va_start (args, fmt);
106 jwxyz_logv(True, fmt, args);
107 va_end (args);
108
109 char buf[10240];
110 va_start (args, fmt);
111 vsprintf (buf, fmt, args);
112 va_end (args);
113
114 JNIEnv *env;
115 (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
116
117 if (! (*env)->ExceptionOccurred(env)) {
118 // If there's already an exception queued, let's just go with that one.
119 // Else, queue a Java exception to be thrown.
120 (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"),
121 buf);
122 }
123
124 // Nonlocal exit out of the jwxyz code.
125 longjmp (jmp_target, 1);
126 }
127
128
129 /* We get to keep live references to Java classes in use because the VM can
130 unload a class that isn't being used, which invalidates field and method
131 IDs.
132 https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
133 */
134
135
136 // #### only need one var I think
137 static size_t classRefCount = 0;
138 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
139 globalRefMapEntry;
140
141 static jfieldID runningHackField;
142 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
143 static jmethodID entryGetKey, entryGetValue;
144
145 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
146
147 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
148 static char *get_string_resource_window (Window window, char *name);
149
150
151 /* Also creates double-buffered windows. */
152 static void
create_pixmap(Window win,Drawable p)153 create_pixmap (Window win, Drawable p)
154 {
155 // See also:
156 // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
157 // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
158 // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
159
160 Assert (p->frame.width, "p->frame.width");
161 Assert (p->frame.height, "p->frame.height");
162
163 if (win->window.rh->jwxyz_gl_p) {
164 struct running_hack *rh = win->window.rh;
165
166 if (rh->gl_fbo_p) {
167 glGenTextures (1, &p->texture);
168 glBindTexture (GL_TEXTURE_2D, p->texture);
169
170 glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
171 to_pow2(p->frame.width), to_pow2(p->frame.height),
172 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
173
174 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
175 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
176 } else {
177 EGLint attribs[5];
178 attribs[0] = EGL_WIDTH;
179 attribs[1] = p->frame.width;
180 attribs[2] = EGL_HEIGHT;
181 attribs[3] = p->frame.height;
182 attribs[4] = EGL_NONE;
183
184 p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
185 attribs);
186 Assert (p->egl_surface != EGL_NO_SURFACE,
187 "XCreatePixmap: got EGL_NO_SURFACE");
188 }
189 } else {
190 p->image_data = malloc (p->frame.width * p->frame.height * 4);
191 }
192 }
193
194
195 static void
free_pixmap(struct running_hack * rh,Pixmap p)196 free_pixmap (struct running_hack *rh, Pixmap p)
197 {
198 if (rh->jwxyz_gl_p) {
199 if (rh->gl_fbo_p) {
200 glDeleteTextures (1, &p->texture);
201 } else {
202 eglDestroySurface(rh->egl_display, p->egl_surface);
203 }
204 } else {
205 free (p->image_data);
206 }
207 }
208
209
210 static void
prepare_context(struct running_hack * rh)211 prepare_context (struct running_hack *rh)
212 {
213 if (rh->egl_p) {
214 /* TODO: Adreno recommends against doing this every frame. */
215 Assert (eglMakeCurrent(rh->egl_display, rh->egl_surface, rh->egl_surface,
216 rh->egl_ctx),
217 "eglMakeCurrent failed");
218 }
219
220 /* Don't set matrices here; set them when an Xlib call triggers
221 jwxyz_bind_drawable/jwxyz_set_matrices.
222 */
223 if (rh->jwxyz_gl_p)
224 rh->current_drawable = NULL;
225
226 if (rh->xsft->visual == GL_VISUAL)
227 jwzgles_make_current (rh->gles_state);
228 }
229
230
231 // Initialized OpenGL and runs the screenhack's init function.
232 //
233 static void
doinit(jobject jwxyz_obj,struct running_hack * rh,JNIEnv * env,const struct function_table_entry * chosen,jobject defaults,jint w,jint h,jobject jni_surface)234 doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
235 const struct function_table_entry *chosen,
236 jobject defaults, jint w, jint h, jobject jni_surface)
237 {
238 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
239
240 progname = chosen->progname;
241 rh->xsft = chosen->xsft;
242 rh->jni_env = env;
243 rh->jobject = jwxyz_obj; // update this every time we call into C
244
245 (*env)->GetJavaVM (env, &global_jvm);
246
247 # undef ya_rand_init // This is the one and only place it is allowed
248 ya_rand_init (0);
249
250 Window wnd = (Window) calloc(1, sizeof(*wnd));
251 wnd->window.rh = rh;
252 wnd->frame.width = w;
253 wnd->frame.height = h;
254 wnd->type = WINDOW;
255
256 rh->window = wnd;
257 progclass = rh->xsft->progclass;
258
259 if ((*env)->ExceptionOccurred(env)) abort();
260
261 // This has to come before resource processing. It does not do graphics.
262 if (rh->xsft->setup_cb)
263 rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);
264
265 if ((*env)->ExceptionOccurred(env)) abort();
266
267 // Load the defaults.
268 // Unceremoniously stolen from [PrefsReader defaultsToDict:].
269
270 jclass c = (*env)->GetObjectClass (env, defaults);
271 jmethodID m = (*env)->GetMethodID (env, c, "put",
272 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
273 if (! m) abort();
274 if ((*env)->ExceptionOccurred(env)) abort();
275
276 const struct { const char *key, *val; } default_defaults[] = {
277 { "doubleBuffer", "True" },
278 { "multiSample", "False" },
279 { "texFontCacheSize", "30" },
280 { "textMode", "date" },
281 { "textURL",
282 "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
283 { "grabDesktopImages", "True" },
284 { "chooseRandomImages", "True" },
285 };
286
287 for (int i = 0; i < countof(default_defaults); i++) {
288 const char *key = default_defaults[i].key;
289 const char *val = default_defaults[i].val;
290 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
291 strcpy (key2, progname);
292 strcat (key2, "_");
293 strcat (key2, key);
294
295 // defaults.put(key2, val);
296 jstring jkey = (*env)->NewStringUTF (env, key2);
297 jstring jval = (*env)->NewStringUTF (env, val);
298 (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
299 (*env)->DeleteLocalRef (env, jkey);
300 (*env)->DeleteLocalRef (env, jval);
301 // Log ("default0: \"%s\" = \"%s\"", key2, val);
302 free (key2);
303 }
304
305 const char *const *defs = rh->xsft->defaults;
306 while (*defs) {
307 char *line = strdup (*defs);
308 char *key, *val;
309 key = line;
310 while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
311 key++;
312 val = key;
313 while (*val && *val != ':')
314 val++;
315 if (*val != ':') abort();
316 *val++ = 0;
317 while (*val == ' ' || *val == '\t')
318 val++;
319
320 unsigned long L = strlen(val);
321 while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
322 val[--L] = 0;
323
324 char *key2 = malloc (strlen(progname) + strlen(key) + 2);
325 strcpy (key2, progname);
326 strcat (key2, "_");
327 strcat (key2, key);
328
329 // defaults.put(key2, val);
330 jstring jkey = (*env)->NewStringUTF (env, key2);
331 jstring jval = (*env)->NewStringUTF (env, val);
332 (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
333 (*env)->DeleteLocalRef (env, jkey);
334 (*env)->DeleteLocalRef (env, jval);
335 // Log ("default: \"%s\" = \"%s\"", key2, val);
336 free (key2);
337 free (line);
338 defs++;
339 }
340
341 (*env)->DeleteLocalRef (env, c);
342 if ((*env)->ExceptionOccurred(env)) abort();
343
344
345 /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */
346
347 /* ####: This is lame, use a resource. */
348 rh->jwxyz_gl_p =
349 rh->xsft->visual == DEFAULT_VISUAL &&
350 strcmp (progname, "kumppa") &&
351 strcmp (progname, "petri") &&
352 strcmp (progname, "slip") &&
353 strcmp (progname, "testx11");
354
355 Log ("init: %s @ %dx%d: using JWXYZ_%s", progname, w, h,
356 rh->jwxyz_gl_p ? "GL" : "IMAGE");
357
358 rh->egl_p = rh->jwxyz_gl_p || rh->xsft->visual == GL_VISUAL;
359
360 if (rh->egl_p) {
361 // GL init. Must come after resource processing.
362
363 rh->egl_display = eglGetDisplay (EGL_DEFAULT_DISPLAY);
364 Assert (rh->egl_display != EGL_NO_DISPLAY, "init: EGL_NO_DISPLAY");
365
366 int egl_major, egl_minor;
367 Assert (eglInitialize (rh->egl_display, &egl_major, &egl_minor),
368 "eglInitialize failed");
369
370 // TODO: Skip depth and (probably) alpha for Xlib.
371 // TODO: Could ask for EGL_SWAP_BEHAVIOR_PRESERVED_BIT here...maybe?
372 // TODO: Probably should try to ask for EGL_PBUFFER_BIT.
373 // TODO: Do like visual-gl.c and work from a list of configs.
374 /* Probably don't need EGL_FRAMEBUFFER_TARGET_ANDROID here if GLSurfaceView
375 doesn't use it.
376 */
377 EGLint config_attribs[] = {
378 EGL_RED_SIZE, 8,
379 EGL_GREEN_SIZE, 8,
380 EGL_BLUE_SIZE, 8,
381 EGL_ALPHA_SIZE, 8,
382 EGL_DEPTH_SIZE, 16,
383 EGL_NONE
384 };
385
386 EGLint num_config;
387 Assert (eglChooseConfig (rh->egl_display, config_attribs,
388 &rh->egl_config, 1, &num_config),
389 "eglChooseConfig failed");
390 Assert (num_config == 1, "no EGL config chosen");
391
392 EGLint no_attribs[] = {EGL_NONE};
393 rh->egl_ctx = eglCreateContext (rh->egl_display, rh->egl_config,
394 EGL_NO_CONTEXT, no_attribs);
395 Assert (rh->egl_ctx != EGL_NO_CONTEXT, "init: EGL_NO_CONTEXT");
396
397 ANativeWindow *native_window =
398 ANativeWindow_fromSurface (env, jni_surface);
399
400 rh->egl_surface = eglCreateWindowSurface (rh->egl_display, rh->egl_config,
401 native_window, no_attribs);
402 Assert (rh->egl_surface != EGL_NO_SURFACE, "init: EGL_NO_SURFACE");
403
404 ANativeWindow_release (native_window);
405 } else {
406 rh->native_window = ANativeWindow_fromSurface (env, jni_surface);
407
408 int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
409 WINDOW_FORMAT_RGBX_8888);
410 if (result < 0) {
411 // Maybe check this earlier?
412 Log ("can't set format (%d), surface may be invalid.", result);
413 (*env)->ThrowNew (env,
414 (*env)->FindClass(env, "org/jwz/xscreensaver/jwxyz$SurfaceLost"),
415 "Surface lost");
416
417 ANativeWindow_release (rh->native_window);
418 rh->native_window = NULL;
419 return;
420 }
421 }
422
423 prepare_context (rh);
424
425 if (rh->egl_p) {
426 Log ("init %s / %s / %s",
427 glGetString (GL_VENDOR),
428 glGetString (GL_RENDERER),
429 glGetString (GL_VERSION));
430 }
431
432 if (rh->jwxyz_gl_p) {
433 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
434 rh->gl_fbo_p = jwzgles_gluCheckExtension (
435 (const GLubyte *)"GL_OES_framebuffer_object", extensions);
436
437 if (rh->gl_fbo_p) {
438 glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default);
439 Assert (!rh->fb_default, "default framebuffer not current framebuffer");
440 glGenFramebuffersOES (1, &rh->fb_pixmap);
441 wnd->texture = 0;
442 } else {
443 wnd->egl_surface = rh->egl_surface;
444 }
445
446 rh->frontbuffer_p = False;
447
448 if (rh->xsft->visual == DEFAULT_VISUAL ||
449 (rh->xsft->visual == GL_VISUAL &&
450 strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
451
452 rh->frontbuffer_p = True;
453
454 # if 0 /* Might need to be 0 for Adreno...? */
455 if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
456 EGLint surface_type;
457 eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
458 &surface_type);
459 if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
460 eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR,
461 EGL_BUFFER_PRESERVED);
462 rh->frontbuffer_p = False;
463 }
464 }
465 # endif
466
467 if (rh->frontbuffer_p) {
468 /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
469 create_pixmap (wnd, wnd);
470
471 /* No preserving backbuffers, so manual blit from back to "front". */
472 rh->frontbuffer.type = PIXMAP;
473 rh->frontbuffer.frame = wnd->frame;
474 rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL);
475
476 if(rh->gl_fbo_p) {
477 rh->frontbuffer.texture = 0;
478 } else {
479 Assert (wnd->egl_surface != rh->egl_surface,
480 "oops: wnd->egl_surface == rh->egl_surface");
481 rh->frontbuffer.egl_surface = rh->egl_surface;
482 }
483 }
484 }
485
486 rh->dpy = jwxyz_gl_make_display(wnd);
487
488 } else {
489
490 if (rh->xsft->visual == DEFAULT_VISUAL)
491 create_pixmap (wnd, wnd);
492 else
493 wnd->image_data = NULL;
494
495 static const unsigned char rgba_bytes[] = {0, 1, 2, 3};
496 rh->dpy = jwxyz_image_make_display(wnd, rgba_bytes);
497
498 }
499
500 Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
501 // TODO: Zero looks right, but double-check that is the right number
502
503 /* Requires valid rh->dpy. */
504 if (rh->jwxyz_gl_p)
505 rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
506
507 if (rh->xsft->visual == GL_VISUAL)
508 rh->gles_state = jwzgles_make_state();
509 END: ;
510 }
511
512
513 #undef DEBUG_FPS
514
515 #ifdef DEBUG_FPS
516
517 static double
double_time(void)518 double_time (void)
519 {
520 struct timeval now;
521 # ifdef GETTIMEOFDAY_TWO_ARGS
522 struct timezone tzp;
523 gettimeofday(&now, &tzp);
524 # else
525 gettimeofday(&now);
526 # endif
527
528 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
529 }
530
531 #endif
532
533 // Animates a single frame of the current hack.
534 //
535 static jlong
drawXScreenSaver(JNIEnv * env,struct running_hack * rh)536 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
537 {
538 # ifdef DEBUG_FPS
539 double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
540 fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
541 # endif
542
543 unsigned long delay = 0;
544
545 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
546
547 Window wnd = rh->window;
548
549 prepare_context (rh);
550
551 if (rh->egl_p) {
552 /* There is some kind of weird redisplay race condition between Settings
553 and the launching hack: e.g., Abstractile does XClearWindow at init,
554 but the screen is getting filled with random bits. So let's wait a
555 few frames before really starting up.
556
557 TODO: Is this still true?
558 */
559 if (++rh->frame_count < 8) {
560 /* glClearColor (1.0, 0.0, 1.0, 0.0); */
561 glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */
562 goto END;
563 }
564 }
565
566 # ifdef DEBUG_FPS
567 fps1 = double_time();
568 # endif
569
570 // The init function might do graphics (e.g. XClearWindow) so it has
571 // to be run from inside onDrawFrame, not onSurfaceChanged.
572
573 if (! rh->initted_p) {
574
575 void *(*init_cb) (Display *, Window, void *) =
576 (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
577
578 if (rh->xsft->visual == DEFAULT_VISUAL) {
579 unsigned int bg =
580 get_pixel_resource (rh->dpy, 0, "background", "Background");
581 XSetWindowBackground (rh->dpy, wnd, bg);
582 XClearWindow (rh->dpy, wnd);
583 }
584
585 rh->closure = init_cb (rh->dpy, wnd, rh->xsft->setup_arg);
586 rh->initted_p = True;
587
588 /* ignore_rotation_p doesn't quite work at the moment. */
589 rh->ignore_rotation_p = False;
590 /*
591 (rh->xsft->visual == DEFAULT_VISUAL &&
592 get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
593 */
594
595 if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
596 rh->fpst = fps_init (rh->dpy, wnd);
597 if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
598 } else {
599 rh->fpst = NULL;
600 rh->xsft->fps_cb = 0;
601 }
602
603 if ((*env)->ExceptionOccurred(env)) abort();
604 }
605
606 # ifdef DEBUG_FPS
607 fps2 = double_time();
608 # endif
609
610 // Apparently events don't come in on the drawing thread, and JNI flips
611 // out. So we queue them there and run them here.
612 // TODO: Events should be coming in on the drawing thread now, so dump this.
613 send_queued_events (rh);
614
615 # ifdef DEBUG_FPS
616 fps3 = double_time();
617 # endif
618
619 delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure);
620
621 if (rh->jwxyz_gl_p)
622 jwxyz_gl_flush (rh->dpy);
623
624 # ifdef DEBUG_FPS
625 fps4 = double_time();
626 # endif
627 if (rh->fpst && rh->xsft->fps_cb)
628 rh->xsft->fps_cb (rh->dpy, wnd, rh->fpst, rh->closure);
629
630 if (rh->egl_p) {
631 if (rh->jwxyz_gl_p && rh->frontbuffer_p) {
632 jwxyz_gl_copy_area (rh->dpy, wnd, &rh->frontbuffer, rh->copy_gc,
633 0, 0, wnd->frame.width, wnd->frame.height,
634 0, 0);
635 }
636
637 // Getting failure here before/during/after resize, sometimes. Log sez:
638 // W/Adreno-EGLSUB(18428): <DequeueBuffer:607>: dequeue native buffer fail: No such device, buffer=0x5f93bf5c, handle=0x0
639 if (!eglSwapBuffers(rh->egl_display, rh->egl_surface)) {
640 Log ("eglSwapBuffers failed: 0x%x (probably asynchronous resize)",
641 eglGetError());
642 }
643 } else {
644 ANativeWindow_Buffer buffer;
645 ARect rect = {0, 0, wnd->frame.width, wnd->frame.height};
646 int32_t result = ANativeWindow_lock(rh->native_window, &buffer, &rect);
647 if (result) {
648 Log ("ANativeWindow_lock failed (result = %d), frame dropped", result);
649 } else {
650 /* Android can resize surfaces asynchronously. */
651 if (wnd->frame.width != buffer.width ||
652 wnd->frame.height != buffer.height) {
653 Log ("buffer/window size mismatch: %dx%d (format = %d), wnd: %dx%d",
654 buffer.width, buffer.height, buffer.format,
655 wnd->frame.width, wnd->frame.height);
656 }
657
658 Assert (buffer.format == WINDOW_FORMAT_RGBA_8888 ||
659 buffer.format == WINDOW_FORMAT_RGBX_8888,
660 "bad buffer format");
661
662 jwxyz_blit (wnd->image_data, jwxyz_image_pitch (wnd), 0, 0,
663 buffer.bits, buffer.stride * 4, 0, 0,
664 MIN(wnd->frame.width, buffer.width),
665 MIN(wnd->frame.height, buffer.height));
666 // TODO: Clear any area to sides and bottom.
667
668 ANativeWindow_unlockAndPost (rh->native_window);
669 }
670 }
671
672 END: ;
673
674 # ifdef DEBUG_FPS
675 Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
676 (int) ((fps1-fps0)*1000000),
677 (int) ((fps2-fps1)*1000000),
678 (int) ((fps3-fps2)*1000000),
679 (int) ((fps4-fps3)*1000000),
680 (int) ((double_time()-fps4)*1000000));
681 # endif
682
683 return delay;
684 }
685
686
687 // Extracts the C structure that is stored in the jwxyz Java object.
688 static struct running_hack *
getRunningHack(JNIEnv * env,jobject thiz)689 getRunningHack (JNIEnv *env, jobject thiz)
690 {
691 jlong result = (*env)->GetLongField (env, thiz, runningHackField);
692 struct running_hack *rh = (struct running_hack *)(intptr_t)result;
693 if (rh)
694 rh->jobject = thiz; // update this every time we call into C
695 return rh;
696 }
697
698 // Look up a class and mark it global in the provided variable.
699 static jclass
acquireClass(JNIEnv * env,const char * className,jobject * globalRef)700 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
701 {
702 jclass clazz = (*env)->FindClass(env, className);
703 *globalRef = (*env)->NewGlobalRef(env, clazz);
704 return clazz;
705 }
706
707
708 /* Note: to find signature strings for native methods:
709 cd ./project/xscreensaver/build/intermediates/classes/debug/
710 javap -s -p org.jwz.xscreensaver.jwxyz
711 */
712
713
714 // Implementation of jwxyz's nativeInit Java method.
715 //
716 JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeInit(JNIEnv * env,jobject thiz,jstring jhack,jobject defaults,jint w,jint h,jobject jni_surface)717 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
718 jstring jhack, jobject defaults,
719 jint w, jint h,
720 jobject jni_surface)
721 {
722 pthread_mutex_lock(&mutg);
723
724 struct running_hack *rh = calloc(1, sizeof(struct running_hack));
725
726 if ((*env)->ExceptionOccurred(env)) abort();
727
728 // #### simplify
729 if (!classRefCount) {
730 jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
731 globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
732 runningHackField = (*env)->GetFieldID
733 (env, classjwxyz, "nativeRunningHackPtr", "J");
734 if ((*env)->ExceptionOccurred(env)) abort();
735
736 jclass classIterable =
737 acquireClass(env, "java/lang/Iterable", &globalRefIterable);
738 iterableIterator = (*env)->GetMethodID
739 (env, classIterable, "iterator", "()Ljava/util/Iterator;");
740 if ((*env)->ExceptionOccurred(env)) abort();
741
742 jclass classIterator =
743 acquireClass(env, "java/util/Iterator", &globalRefIterator);
744 iteratorHasNext = (*env)->GetMethodID
745 (env, classIterator, "hasNext", "()Z");
746 iteratorNext = (*env)->GetMethodID
747 (env, classIterator, "next", "()Ljava/lang/Object;");
748 if ((*env)->ExceptionOccurred(env)) abort();
749
750 jclass classMapEntry =
751 acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
752 entryGetKey = (*env)->GetMethodID
753 (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
754 entryGetValue = (*env)->GetMethodID
755 (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
756 if ((*env)->ExceptionOccurred(env)) abort();
757 }
758
759 ++classRefCount;
760
761 // Store the C struct into the Java object.
762 (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
763
764 // TODO: Sort the list so binary search works.
765 const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
766
767 int chosen = 0;
768 for (;;) {
769 if (chosen == countof(function_table)) {
770 Log ("Hack not found: %s", hack);
771 abort();
772 }
773 if (!strcmp(function_table[chosen].progname, hack))
774 break;
775 chosen++;
776 }
777
778 (*env)->ReleaseStringUTFChars(env, jhack, hack);
779
780 doinit (thiz, rh, env, &function_table[chosen], defaults, w, h,
781 jni_surface);
782
783 pthread_mutex_unlock(&mutg);
784 }
785
786
787 JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeResize(JNIEnv * env,jobject thiz,jint w,jint h,jdouble rot)788 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
789 jint w, jint h, jdouble rot)
790 {
791 pthread_mutex_lock(&mutg);
792 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
793
794 current_rotation = rot;
795
796 Log ("native rotation: %f", current_rotation);
797
798 struct running_hack *rh = getRunningHack(env, thiz);
799
800 prepare_context (rh);
801
802 if (rh->egl_p) {
803 glViewport (0, 0, w, h);
804 } else {
805 int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
806 WINDOW_FORMAT_RGBX_8888);
807 if (result < 0)
808 Log ("failed to resize surface (%d)", result);
809 }
810
811 Window wnd = rh->window;
812 wnd->frame.x = 0;
813 wnd->frame.y = 0;
814 wnd->frame.width = w;
815 wnd->frame.height = h;
816
817 if (ignore_rotation_p(rh->dpy) &&
818 rot != 0 && rot != 180 && rot != -180) {
819 int swap = w;
820 w = h;
821 h = swap;
822 wnd->frame.width = w;
823 wnd->frame.height = h;
824 }
825
826 if (rh->jwxyz_gl_p) {
827 if (rh->frontbuffer_p) {
828 free_pixmap (rh, wnd);
829 create_pixmap (wnd, wnd);
830
831 rh->frontbuffer.frame = wnd->frame;
832 if (!rh->gl_fbo_p)
833 rh->frontbuffer.egl_surface = rh->egl_surface;
834 }
835
836 jwxyz_window_resized (rh->dpy);
837 } else if (rh->xsft->visual == DEFAULT_VISUAL) {
838 free_pixmap (rh, wnd);
839 create_pixmap (wnd, wnd);
840 XClearWindow (rh->dpy, wnd); // TODO: This is lame. Copy the bits.
841 }
842
843 if (rh->initted_p)
844 rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
845 wnd->frame.width, wnd->frame.height);
846
847 if (rh->xsft->visual == GL_VISUAL) {
848 glMatrixMode (GL_PROJECTION);
849 glRotatef (-rot, 0, 0, 1);
850 glMatrixMode (GL_MODELVIEW);
851 }
852
853 END:
854 pthread_mutex_unlock(&mutg);
855 }
856
857
858 JNIEXPORT jlong JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeRender(JNIEnv * env,jobject thiz)859 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
860 {
861 pthread_mutex_lock(&mutg);
862 struct running_hack *rh = getRunningHack(env, thiz);
863 jlong result = drawXScreenSaver(env, rh);
864 pthread_mutex_unlock(&mutg);
865 return result;
866 }
867
868
869 // TODO: Check Java side is calling this properly
870 JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeDone(JNIEnv * env,jobject thiz)871 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
872 {
873 pthread_mutex_lock(&mutg);
874 if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
875
876 struct running_hack *rh = getRunningHack(env, thiz);
877
878 prepare_context (rh);
879
880 if (rh->initted_p)
881 rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
882 if (rh->jwxyz_gl_p)
883 XFreeGC (rh->dpy, rh->copy_gc);
884 if (rh->xsft->visual == GL_VISUAL)
885 jwzgles_free_state ();
886
887 if (rh->jwxyz_gl_p)
888 jwxyz_gl_free_display(rh->dpy);
889 else
890 jwxyz_image_free_display(rh->dpy);
891
892 if (rh->egl_p) {
893 // eglDestroy* probably isn't necessary here.
894 eglMakeCurrent (rh->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
895 EGL_NO_CONTEXT);
896 eglDestroySurface (rh->egl_display, rh->egl_surface);
897 eglDestroyContext (rh->egl_display, rh->egl_ctx);
898 eglTerminate (rh->egl_display);
899 } else {
900 if (rh->xsft->visual == DEFAULT_VISUAL)
901 free_pixmap (rh, rh->window);
902 if (rh->native_window)
903 ANativeWindow_release (rh->native_window);
904 }
905
906 free(rh);
907 (*env)->SetLongField(env, thiz, runningHackField, 0);
908
909 --classRefCount;
910 if (!classRefCount) {
911 (*env)->DeleteGlobalRef(env, globalRefjwxyz);
912 (*env)->DeleteGlobalRef(env, globalRefIterable);
913 (*env)->DeleteGlobalRef(env, globalRefIterator);
914 (*env)->DeleteGlobalRef(env, globalRefMapEntry);
915 }
916
917 END:
918 pthread_mutex_unlock(&mutg);
919 }
920
921
922 static int
send_event(struct running_hack * rh,XEvent * e)923 send_event (struct running_hack *rh, XEvent *e)
924 {
925 // Assumes mutex is locked and context is prepared
926
927 int *xP = 0, *yP = 0;
928 switch (e->xany.type) {
929 case ButtonPress: case ButtonRelease:
930 xP = &e->xbutton.x;
931 yP = &e->xbutton.y;
932 break;
933 case MotionNotify:
934 xP = &e->xmotion.x;
935 yP = &e->xmotion.y;
936 break;
937 }
938
939 // Rotate the coordinates in the events to match the pixels.
940 if (xP) {
941 if (ignore_rotation_p (rh->dpy)) {
942 Window win = XRootWindow (rh->dpy, 0);
943 int w = win->frame.width;
944 int h = win->frame.height;
945 int swap;
946 switch ((int) current_rotation) {
947 case 180: case -180: // #### untested
948 *xP = w - *xP;
949 *yP = h - *yP;
950 break;
951 case 90: case -270:
952 swap = *xP; *xP = *yP; *yP = swap;
953 *yP = h - *yP;
954 break;
955 case -90: case 270: // #### untested
956 swap = *xP; *xP = *yP; *yP = swap;
957 *xP = w - *xP;
958 break;
959 }
960 }
961
962 rh->window->window.last_mouse_x = *xP;
963 rh->window->window.last_mouse_y = *yP;
964 }
965
966 return (rh->xsft->event_cb
967 ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
968 : 0);
969 }
970
971
972 static void
send_queued_events(struct running_hack * rh)973 send_queued_events (struct running_hack *rh)
974 {
975 struct event_queue *event, *next;
976 if (! rh->event_queue) return;
977 for (event = rh->event_queue, next = event->next;
978 event;
979 event = next, next = (event ? event->next : 0)) {
980 if (! send_event (rh, &event->event)) {
981 // #### flash the screen or something
982 }
983 free (event);
984 }
985 rh->event_queue = 0;
986 }
987
988
989 static void
queue_event(JNIEnv * env,jobject thiz,XEvent * e)990 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
991 {
992 pthread_mutex_lock (&mutg);
993 struct running_hack *rh = getRunningHack (env, thiz);
994 struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
995 memcpy (&q->event, e, sizeof(*e));
996 q->next = 0;
997
998 // Put it at the end.
999 struct event_queue *oq;
1000 for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
1001 ;
1002 if (oq)
1003 oq->next = q;
1004 else
1005 rh->event_queue = q;
1006
1007 pthread_mutex_unlock (&mutg);
1008 }
1009
1010
1011 JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent(JNIEnv * env,jobject thiz,int x,int y,jboolean down)1012 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
1013 int x, int y, jboolean down)
1014 {
1015 XEvent e;
1016 memset (&e, 0, sizeof(e));
1017 e.xany.type = (down ? ButtonPress : ButtonRelease);
1018 e.xbutton.button = Button1;
1019 e.xbutton.x = x;
1020 e.xbutton.y = y;
1021 queue_event (env, thiz, &e);
1022 }
1023
1024 JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent(JNIEnv * env,jobject thiz,int x,int y)1025 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
1026 int x, int y)
1027 {
1028 XEvent e;
1029 memset (&e, 0, sizeof(e));
1030 e.xany.type = MotionNotify;
1031 e.xmotion.x = x;
1032 e.xmotion.y = y;
1033 queue_event (env, thiz, &e);
1034 }
1035
1036 JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent(JNIEnv * env,jobject thiz,jboolean down_p,int code,int mods)1037 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
1038 jboolean down_p,
1039 int code, int mods)
1040 {
1041 XEvent e;
1042 memset (&e, 0, sizeof(e));
1043 e.xkey.keycode = code;
1044 e.xkey.state = code;
1045 e.xany.type = (down_p ? KeyPress : KeyRelease);
1046 queue_event (env, thiz, &e);
1047 e.xany.type = KeyRelease;
1048 queue_event (env, thiz, &e);
1049 }
1050
1051
1052 /***************************************************************************
1053 Backend functions for jwxyz-gl.c
1054 */
1055
1056 static void
finish_bind_drawable(Display * dpy,Drawable dst)1057 finish_bind_drawable (Display *dpy, Drawable dst)
1058 {
1059 jwxyz_assert_gl ();
1060
1061 glViewport (0, 0, dst->frame.width, dst->frame.height);
1062 jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False);
1063 }
1064
1065
1066 static void
bind_drawable_fbo(struct running_hack * rh,Drawable d)1067 bind_drawable_fbo (struct running_hack *rh, Drawable d)
1068 {
1069 glBindFramebufferOES (GL_FRAMEBUFFER_OES,
1070 d->texture ? rh->fb_pixmap : rh->fb_default);
1071 if (d->texture) {
1072 glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
1073 GL_TEXTURE_2D, d->texture, 0);
1074 }
1075 }
1076
1077
1078 void
jwxyz_bind_drawable(Display * dpy,Window w,Drawable d)1079 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
1080 {
1081 struct running_hack *rh = w->window.rh;
1082 JNIEnv *env = w->window.rh->jni_env;
1083 if ((*env)->ExceptionOccurred(env)) abort();
1084 if (rh->current_drawable != d) {
1085 if (rh->gl_fbo_p) {
1086 bind_drawable_fbo (rh, d);
1087 } else {
1088 eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx);
1089 }
1090 finish_bind_drawable (dpy, d);
1091 rh->current_drawable = d;
1092 }
1093 }
1094
1095 void
jwxyz_gl_copy_area(Display * dpy,Drawable src,Drawable dst,GC gc,int src_x,int src_y,unsigned int width,unsigned int height,int dst_x,int dst_y)1096 jwxyz_gl_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
1097 int src_x, int src_y,
1098 unsigned int width, unsigned int height,
1099 int dst_x, int dst_y)
1100 {
1101 Window w = XRootWindow (dpy, 0);
1102 struct running_hack *rh = w->window.rh;
1103
1104 jwxyz_gl_flush (dpy);
1105
1106 if (rh->gl_fbo_p && src->texture && src != dst) {
1107 bind_drawable_fbo (rh, dst);
1108 finish_bind_drawable (dpy, dst);
1109 rh->current_drawable = NULL;
1110
1111 jwxyz_gl_set_gc (dpy, gc);
1112
1113 glBindTexture (GL_TEXTURE_2D, src->texture);
1114
1115 jwxyz_gl_draw_image (dpy, gc, GL_TEXTURE_2D, to_pow2(src->frame.width),
1116 to_pow2(src->frame.height),
1117 src_x, src->frame.height - src_y - height,
1118 jwxyz_drawable_depth (src), width, height,
1119 dst_x, dst_y, False);
1120 return;
1121 }
1122
1123 #if 1
1124 // Kumppa: 0.24 FPS
1125 // Hilarious display corruption ahoy! (Note to self: it's on the emulator.)
1126 // TODO for Dave: Recheck behavior on the emulator with the better Pixmap support.
1127
1128 rh->current_drawable = NULL;
1129 if (rh->gl_fbo_p)
1130 bind_drawable_fbo (rh, src);
1131 else
1132 eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx);
1133
1134 jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y,
1135 width, height, dst_x, dst_y);
1136
1137 if (rh->gl_fbo_p)
1138 bind_drawable_fbo (rh, dst);
1139 finish_bind_drawable (dpy, dst);
1140
1141 jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
1142 jwxyz_drawable_depth (src),
1143 width, height, dst_x, dst_y);
1144
1145 #else
1146 // Kumppa: 0.17 FPS
1147 jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
1148 width, height, dst_x, dst_y);
1149 #endif
1150 jwxyz_assert_gl ();
1151 }
1152
1153
1154 void
jwxyz_assert_drawable(Window main_window,Drawable d)1155 jwxyz_assert_drawable (Window main_window, Drawable d)
1156 {
1157 check_gl_error("jwxyz_assert_drawable");
1158 }
1159
1160
1161 void
jwxyz_assert_gl(void)1162 jwxyz_assert_gl (void)
1163 {
1164 check_gl_error("jwxyz_assert_gl");
1165 }
1166
1167
1168 /***************************************************************************
1169 Backend functions for jwxyz-image.c
1170 */
1171
1172 ptrdiff_t
jwxyz_image_pitch(Drawable d)1173 jwxyz_image_pitch (Drawable d)
1174 {
1175 return d->frame.width * 4;
1176 }
1177
1178 void *
jwxyz_image_data(Drawable d)1179 jwxyz_image_data (Drawable d)
1180 {
1181 Assert (d->image_data, "no image storage (i.e. keep Xlib off the Window)");
1182 return d->image_data;
1183 }
1184
1185
1186 const XRectangle *
jwxyz_frame(Drawable d)1187 jwxyz_frame (Drawable d)
1188 {
1189 return &d->frame;
1190 }
1191
1192
1193 unsigned int
jwxyz_drawable_depth(Drawable d)1194 jwxyz_drawable_depth (Drawable d)
1195 {
1196 return (d->type == WINDOW
1197 ? visual_depth (NULL, NULL)
1198 : d->pixmap.depth);
1199 }
1200
1201
1202 void
jwxyz_get_pos(Window w,XPoint * xvpos,XPoint * xp)1203 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
1204 {
1205 xvpos->x = 0;
1206 xvpos->y = 0;
1207
1208 if (xp) {
1209 xp->x = w->window.last_mouse_x;
1210 xp->y = w->window.last_mouse_y;
1211 }
1212 }
1213
1214
1215 static void
screenhack_do_fps(Display * dpy,Window w,fps_state * fpst,void * closure)1216 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
1217 {
1218 fps_compute (fpst, 0, -1);
1219 fps_draw (fpst);
1220 }
1221
1222
1223 Pixmap
XCreatePixmap(Display * dpy,Drawable d,unsigned int width,unsigned int height,unsigned int depth)1224 XCreatePixmap (Display *dpy, Drawable d,
1225 unsigned int width, unsigned int height, unsigned int depth)
1226 {
1227 Window win = XRootWindow(dpy, 0);
1228
1229 Pixmap p = malloc(sizeof(*p));
1230 p->type = PIXMAP;
1231 p->frame.x = 0;
1232 p->frame.y = 0;
1233 p->frame.width = width;
1234 p->frame.height = height;
1235
1236 Assert(depth == 1 || depth == visual_depth(NULL, NULL),
1237 "XCreatePixmap: bad depth");
1238 p->pixmap.depth = depth;
1239
1240 create_pixmap (win, p);
1241
1242 /* For debugging. */
1243 # if 0
1244 jwxyz_bind_drawable (dpy, win, p);
1245 glClearColor (frand(1), frand(1), frand(1), 0);
1246 glClear (GL_COLOR_BUFFER_BIT);
1247 # endif
1248
1249 return p;
1250 }
1251
1252
1253 int
XFreePixmap(Display * d,Pixmap p)1254 XFreePixmap (Display *d, Pixmap p)
1255 {
1256 struct running_hack *rh = XRootWindow(d, 0)->window.rh;
1257
1258 if (rh->jwxyz_gl_p) {
1259 jwxyz_gl_flush (d);
1260
1261 if (rh->current_drawable == p)
1262 rh->current_drawable = NULL;
1263 }
1264
1265 free_pixmap (rh, p);
1266 free (p);
1267 return 0;
1268 }
1269
1270
1271 double
current_device_rotation(void)1272 current_device_rotation (void)
1273 {
1274 return current_rotation;
1275 }
1276
1277 Bool
ignore_rotation_p(Display * dpy)1278 ignore_rotation_p (Display *dpy)
1279 {
1280 struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
1281 return rh->ignore_rotation_p;
1282 }
1283
1284
1285 static char *
jstring_dup(JNIEnv * env,jstring str)1286 jstring_dup (JNIEnv *env, jstring str)
1287 {
1288 Assert (str, "expected jstring, not null");
1289 const char *cstr = (*env)->GetStringUTFChars (env, str, 0);
1290 size_t len = (*env)->GetStringUTFLength (env, str) + 1;
1291 char *result = malloc (len);
1292 if (result) {
1293 memcpy (result, cstr, len);
1294 }
1295 (*env)->ReleaseStringUTFChars (env, str, cstr);
1296 return result;
1297 }
1298
1299
1300 static char *
get_string_resource_window(Window window,char * name)1301 get_string_resource_window (Window window, char *name)
1302 {
1303 JNIEnv *env = window->window.rh->jni_env;
1304 jobject obj = window->window.rh->jobject;
1305
1306 if ((*env)->ExceptionOccurred(env)) abort();
1307 jstring jstr = (*env)->NewStringUTF (env, name);
1308 jclass c = (*env)->GetObjectClass (env, obj);
1309 jmethodID m = (*env)->GetMethodID (env, c, "getStringResource",
1310 "(Ljava/lang/String;)Ljava/lang/String;");
1311 if ((*env)->ExceptionOccurred(env)) abort();
1312
1313 jstring jvalue = (m
1314 ? (*env)->CallObjectMethod (env, obj, m, jstr)
1315 : NULL);
1316 (*env)->DeleteLocalRef (env, c);
1317 (*env)->DeleteLocalRef (env, jstr);
1318 char *ret = 0;
1319 if (jvalue)
1320 ret = jstring_dup (env, jvalue);
1321
1322 Log("pref %s = %s", name, (ret ? ret : "(null)"));
1323 return ret;
1324 }
1325
1326
1327 char *
get_string_resource(Display * dpy,char * name,char * class)1328 get_string_resource (Display *dpy, char *name, char *class)
1329 {
1330 return get_string_resource_window (RootWindow (dpy, 0), name);
1331 }
1332
1333
1334 /* Returns the contents of the URL. */
1335 char *
textclient_mobile_url_string(Display * dpy,const char * url)1336 textclient_mobile_url_string (Display *dpy, const char *url)
1337 {
1338 Window window = RootWindow (dpy, 0);
1339 JNIEnv *env = window->window.rh->jni_env;
1340 jobject obj = window->window.rh->jobject;
1341
1342 jstring jstr = (*env)->NewStringUTF (env, url);
1343 jclass c = (*env)->GetObjectClass (env, obj);
1344 jmethodID m = (*env)->GetMethodID (env, c, "loadURL",
1345 "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
1346 if ((*env)->ExceptionOccurred(env)) abort();
1347 jobject buf = (m
1348 ? (*env)->CallObjectMethod (env, obj, m, jstr)
1349 : NULL);
1350 (*env)->DeleteLocalRef (env, c);
1351 (*env)->DeleteLocalRef (env, jstr);
1352
1353 char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1354 char *body2;
1355 if (body) {
1356 int L = (*env)->GetDirectBufferCapacity (env, buf);
1357 body2 = malloc (L + 1);
1358 memcpy (body2, body, L);
1359 body2[L] = 0;
1360 } else {
1361 body2 = strdup ("ERROR");
1362 }
1363
1364 if (buf)
1365 (*env)->DeleteLocalRef (env, buf);
1366
1367 return body2;
1368 }
1369
1370
1371 float
jwxyz_scale(Window main_window)1372 jwxyz_scale (Window main_window)
1373 {
1374 // TODO: Use the actual device resolution.
1375 return 2;
1376 }
1377
1378
1379 const char *
jwxyz_default_font_family(int require)1380 jwxyz_default_font_family (int require)
1381 {
1382 /* Font families in XLFDs are totally ignored (for now). */
1383 return "sans-serif";
1384 }
1385
1386
1387 void *
jwxyz_load_native_font(Window window,int traits_jwxyz,int mask_jwxyz,const char * font_name_ptr,size_t font_name_length,int font_name_type,float size,char ** family_name_ret,int * ascent_ret,int * descent_ret)1388 jwxyz_load_native_font (Window window,
1389 int traits_jwxyz, int mask_jwxyz,
1390 const char *font_name_ptr, size_t font_name_length,
1391 int font_name_type, float size,
1392 char **family_name_ret,
1393 int *ascent_ret, int *descent_ret)
1394 {
1395 JNIEnv *env = window->window.rh->jni_env;
1396 jobject obj = window->window.rh->jobject;
1397
1398 jstring jname = NULL;
1399 if (font_name_ptr) {
1400 char *name_nul = malloc(font_name_length + 1);
1401 memcpy(name_nul, font_name_ptr, font_name_length);
1402 name_nul[font_name_length] = 0;
1403 jname = (*env)->NewStringUTF (env, name_nul);
1404 free(name_nul);
1405 }
1406
1407 jclass c = (*env)->GetObjectClass (env, obj);
1408 jmethodID m = (*env)->GetMethodID (env, c, "loadFont",
1409 "(IILjava/lang/String;IF)[Ljava/lang/Object;");
1410 if ((*env)->ExceptionOccurred(env)) abort();
1411
1412 jobjectArray array = (m
1413 ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
1414 (jint)traits_jwxyz, jname,
1415 (jint)font_name_type, (jfloat)size)
1416 : NULL);
1417
1418 (*env)->DeleteLocalRef (env, c);
1419
1420 if (array) {
1421 jobject font = (*env)->GetObjectArrayElement (env, array, 0);
1422 jobject family_name =
1423 (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
1424 jobject asc = (*env)->GetObjectArrayElement (env, array, 2);
1425 jobject desc = (*env)->GetObjectArrayElement (env, array, 3);
1426 if ((*env)->ExceptionOccurred(env)) abort();
1427
1428 if (family_name_ret)
1429 *family_name_ret = jstring_dup (env, family_name);
1430
1431 jobject paint = (*env)->NewGlobalRef (env, font);
1432 if ((*env)->ExceptionOccurred(env)) abort();
1433
1434 c = (*env)->GetObjectClass(env, asc);
1435 m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1436 if ((*env)->ExceptionOccurred(env)) abort();
1437
1438 *ascent_ret = (int) (*env)->CallFloatMethod (env, asc, m);
1439 *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1440
1441 return (void *) paint;
1442 } else {
1443 return 0;
1444 }
1445 }
1446
1447
1448 void
jwxyz_release_native_font(Display * dpy,void * native_font)1449 jwxyz_release_native_font (Display *dpy, void *native_font)
1450 {
1451 Window window = RootWindow (dpy, 0);
1452 JNIEnv *env = window->window.rh->jni_env;
1453 if ((*env)->ExceptionOccurred(env)) abort();
1454 (*env)->DeleteGlobalRef (env, (jobject) native_font);
1455 if ((*env)->ExceptionOccurred(env)) abort();
1456 }
1457
1458
1459 /* If the local reference table fills up, use this to figure out where
1460 you missed a call to DeleteLocalRef. */
1461 /*
1462 static void dump_reference_tables(JNIEnv *env)
1463 {
1464 jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1465 jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1466 "()V");
1467 (*env)->CallStaticVoidMethod (env, c, m);
1468 (*env)->DeleteLocalRef (env, c);
1469 }
1470 */
1471
1472
1473 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1474 // string. If pixmap_ret is provided, also renders the text.
1475 //
1476 void
jwxyz_render_text(Display * dpy,void * native_font,const char * str,size_t len,Bool utf8,Bool antialias_p,XCharStruct * cs,char ** pixmap_ret)1477 jwxyz_render_text (Display *dpy, void *native_font,
1478 const char *str, size_t len, Bool utf8, Bool antialias_p,
1479 XCharStruct *cs, char **pixmap_ret)
1480 {
1481 Window window = RootWindow (dpy, 0);
1482 JNIEnv *env = window->window.rh->jni_env;
1483 jobject obj = window->window.rh->jobject;
1484
1485 char *s2;
1486
1487 if (utf8) {
1488 s2 = malloc (len + 1);
1489 memcpy (s2, str, len);
1490 s2[len] = 0;
1491 } else { // Convert Latin1 to UTF8
1492 s2 = malloc (len * 2 + 1);
1493 unsigned char *s3 = (unsigned char *) s2;
1494 int i;
1495 for (i = 0; i < len; i++) {
1496 unsigned char c = ((unsigned char *) str)[i];
1497 if (! (c & 0x80)) {
1498 *s3++ = c;
1499 } else {
1500 *s3++ = (0xC0 | (0x03 & (c >> 6)));
1501 *s3++ = (0x80 | (0x3F & c));
1502 }
1503 }
1504 *s3 = 0;
1505 }
1506
1507 jstring jstr = (*env)->NewStringUTF (env, s2);
1508 jclass c = (*env)->GetObjectClass (env, obj);
1509 jmethodID m = (*env)->GetMethodID (env, c, "renderText",
1510 "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;");
1511 if ((*env)->ExceptionOccurred(env)) abort();
1512 jobject buf =
1513 (m
1514 ? (*env)->CallObjectMethod (env, obj, m,
1515 (jobject) native_font,
1516 jstr,
1517 (pixmap_ret ? JNI_TRUE : JNI_FALSE),
1518 antialias_p)
1519 : NULL);
1520 (*env)->DeleteLocalRef (env, c);
1521 (*env)->DeleteLocalRef (env, jstr);
1522 free (s2);
1523
1524 if ((*env)->ExceptionOccurred(env)) abort();
1525 unsigned char *bits = (unsigned char *)
1526 (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1527 if (bits) {
1528 int i = 0;
1529 int L = (*env)->GetDirectBufferCapacity (env, buf);
1530 if (L < 10) abort();
1531 cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1532 cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1533 cs->width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1534 cs->ascent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1535 cs->descent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1536
1537 if (pixmap_ret) {
1538 char *pix = malloc (L - i);
1539 if (! pix) abort();
1540 memcpy (pix, bits + i, L - i);
1541 *pixmap_ret = pix;
1542 }
1543 } else {
1544 memset (cs, 0, sizeof(*cs));
1545 if (pixmap_ret)
1546 *pixmap_ret = 0;
1547 }
1548
1549 if (buf)
1550 (*env)->DeleteLocalRef (env, buf);
1551 }
1552
1553
1554 char *
jwxyz_unicode_character_name(Display * dpy,Font fid,unsigned long uc)1555 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1556 {
1557 JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1558 /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1559 jclass
1560 c = (*env)->FindClass (env, "java/lang/Character"),
1561 c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
1562
1563 if ((*env)->ExceptionOccurred(env)) abort();
1564 jmethodID m = (*env)->GetStaticMethodID (
1565 env, c, "getName", "(I)Ljava/lang/String;");
1566 jthrowable exc = (*env)->ExceptionOccurred(env);
1567 if (exc) {
1568 if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1569 (*env)->ExceptionClear (env);
1570 Assert (!m, "jwxyz_unicode_character_name: m?");
1571 } else {
1572 abort();
1573 }
1574 }
1575
1576 char *ret = NULL;
1577
1578 if (m) {
1579 jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1580 if (name)
1581 ret = jstring_dup (env, name);
1582 }
1583
1584 if (!ret) {
1585 asprintf(&ret, "U+%.4lX", uc);
1586 }
1587
1588 return ret;
1589 }
1590
1591
1592 /* Called from utils/grabclient.c */
1593 char *
jwxyz_draw_random_image(Display * dpy,Drawable drawable,GC gc)1594 jwxyz_draw_random_image (Display *dpy, Drawable drawable, GC gc)
1595 {
1596 Window window = RootWindow (dpy, 0);
1597 struct running_hack *rh = window->window.rh;
1598 JNIEnv *env = rh->jni_env;
1599 jobject obj = rh->jobject;
1600
1601 Bool images_p =
1602 get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1603 Bool grab_p =
1604 get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1605 Bool rotate_p =
1606 get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1607
1608 if (!images_p && !grab_p)
1609 return 0;
1610
1611 if (grab_p && images_p) {
1612 grab_p = !(random() & 5); /* if both, screenshot 1/5th of the time */
1613 images_p = !grab_p;
1614 }
1615
1616 jclass c = (*env)->GetObjectClass (env, obj);
1617 jmethodID m = (*env)->GetMethodID (env, c,
1618 (grab_p
1619 ? "getScreenshot"
1620 : "checkThenLoadRandomImage"),
1621 "(IIZ)[Ljava/lang/Object;");
1622 if ((*env)->ExceptionOccurred(env)) abort();
1623 jobjectArray img_name = (
1624 m
1625 ? (*env)->CallObjectMethod (env, obj, m,
1626 drawable->frame.width, drawable->frame.height,
1627 (rotate_p ? JNI_TRUE : JNI_FALSE))
1628 : NULL);
1629 if ((*env)->ExceptionOccurred(env)) abort();
1630 (*env)->DeleteLocalRef (env, c);
1631
1632 if (!img_name) {
1633 fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image"));
1634 return NULL;
1635 }
1636
1637 jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 0);
1638
1639 AndroidBitmapInfo bmp_info;
1640 AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1641
1642 XImage *img = XCreateImage (dpy, NULL, visual_depth(NULL, NULL),
1643 ZPixmap, 0, NULL,
1644 bmp_info.width, bmp_info.height, 0,
1645 bmp_info.stride);
1646
1647 AndroidBitmap_lockPixels (env, jbitmap, (void **) &img->data);
1648
1649 XPutImage (dpy, drawable, gc, img, 0, 0,
1650 (drawable->frame.width - bmp_info.width) / 2,
1651 (drawable->frame.height - bmp_info.height) / 2,
1652 bmp_info.width, bmp_info.height);
1653
1654 AndroidBitmap_unlockPixels (env, jbitmap);
1655 img->data = NULL;
1656 XDestroyImage (img);
1657
1658 return jstring_dup (env, (*env)->GetObjectArrayElement (env, img_name, 1));
1659 }
1660
1661
1662 XImage *
jwxyz_png_to_ximage(Display * dpy,Visual * visual,const unsigned char * png_data,unsigned long data_size)1663 jwxyz_png_to_ximage (Display *dpy, Visual *visual,
1664 const unsigned char *png_data, unsigned long data_size)
1665 {
1666 Window window = RootWindow (dpy, 0);
1667 struct running_hack *rh = window->window.rh;
1668 JNIEnv *env = rh->jni_env;
1669 jobject obj = rh->jobject;
1670 jclass c = (*env)->GetObjectClass (env, obj);
1671 jmethodID m = (*env)->GetMethodID (env, c, "decodePNG",
1672 "([B)Landroid/graphics/Bitmap;");
1673 if ((*env)->ExceptionOccurred(env)) abort();
1674 jbyteArray jdata = (*env)->NewByteArray (env, data_size);
1675 (*env)->SetByteArrayRegion (env, jdata, 0,
1676 data_size, (const jbyte *) png_data);
1677 jobject jbitmap = (
1678 m
1679 ? (*env)->CallObjectMethod (env, obj, m, jdata)
1680 : NULL);
1681 if ((*env)->ExceptionOccurred(env)) abort();
1682 (*env)->DeleteLocalRef (env, c);
1683 (*env)->DeleteLocalRef (env, jdata);
1684 if (!jbitmap)
1685 return NULL;
1686
1687 AndroidBitmapInfo bmp_info;
1688 AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1689
1690 XImage *img = XCreateImage (dpy, NULL, 32, ZPixmap, 0, NULL,
1691 bmp_info.width, bmp_info.height, 8,
1692 bmp_info.stride);
1693 char *bits = 0;
1694 AndroidBitmap_lockPixels (env, jbitmap, (void **) &bits);
1695 img->data = (char *) calloc (img->bytes_per_line, img->height);
1696 memcpy (img->data, bits, img->bytes_per_line * img->height);
1697 AndroidBitmap_unlockPixels (env, jbitmap);
1698
1699 // Java should have returned ARGB data.
1700 // WTF, why isn't ANDROID_BITMAP_FORMAT_ARGB_8888 defined?
1701 if (bmp_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) abort();
1702 # ifndef __BYTE_ORDER__ // A GCC (and Clang)-ism.
1703 # error Need a __BYTE_ORDER__.
1704 # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1705 img->byte_order = img->bitmap_bit_order = LSBFirst;
1706 # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1707 img->byte_order = img->bitmap_bit_order = MSBFirst;
1708 # else
1709 # error Need a __BYTE_ORDER__.
1710 # endif
1711
1712 static const union {
1713 uint8_t bytes[4];
1714 uint32_t pixel;
1715 } c0 = {{0xff, 0x00, 0x00, 0x00}}, c1 = {{0x00, 0xff, 0x00, 0x00}},
1716 c2 = {{0x00, 0x00, 0xff, 0x00}};
1717
1718 img->red_mask = c0.pixel;
1719 img->green_mask = c1.pixel;
1720 img->blue_mask = c2.pixel;
1721
1722 return img;
1723 }
1724
1725 #endif /* HAVE_ANDROID */
1726