1 /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * xscreensaver, Copyright (c) 2016-2017 Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
12 * This class is how the C implementation of jwxyz calls back into Java
13 * to do things that OpenGL does not have access to without Java-based APIs.
14 * It is the Java companion to jwxyz-android.c and screenhack-android.c.
17 package org.jwz.xscreensaver;
20 import java.util.HashMap;
21 import java.util.Hashtable;
22 import java.util.ArrayList;
23 import java.util.Random;
24 import android.app.AlertDialog;
25 import android.view.KeyEvent;
26 import android.content.SharedPreferences;
27 import android.content.Context;
28 import android.content.ContentResolver;
29 import android.content.DialogInterface;
30 import android.content.res.AssetManager;
31 import android.graphics.Typeface;
32 import android.graphics.Rect;
33 import android.graphics.Paint;
34 import android.graphics.Paint.FontMetrics;
35 import android.graphics.Bitmap;
36 import android.graphics.Canvas;
37 import android.graphics.Color;
38 import android.graphics.Matrix;
39 import android.net.Uri;
40 import android.view.GestureDetector;
41 import android.view.KeyEvent;
42 import android.view.MotionEvent;
44 import java.nio.ByteBuffer;
46 import java.io.InputStream;
47 import java.io.FileOutputStream;
48 import java.lang.InterruptedException;
49 import java.lang.Runnable;
50 import java.lang.Thread;
51 import java.util.TimerTask;
52 import android.database.Cursor;
53 import android.provider.MediaStore;
54 import android.provider.MediaStore.MediaColumns;
55 import android.media.ExifInterface;
56 import org.jwz.xscreensaver.TTFAnalyzer;
57 import android.util.Log;
58 import android.view.Surface;
62 implements GestureDetector.OnGestureListener,
63 GestureDetector.OnDoubleTapListener {
65 private class PrefListener
66 implements SharedPreferences.OnSharedPreferenceChangeListener {
69 public void onSharedPreferenceChanged (SharedPreferences sharedPreferences, String key)
71 if (key.startsWith(hack + "_")) {
73 boolean was_animating;
74 synchronized (render) {
75 was_animating = animating_p;
85 private static class SurfaceLost extends Exception {
87 super("surface lost");
90 SurfaceLost (String detailMessage) {
95 public final static int STYLE_BOLD = 1;
96 public final static int STYLE_ITALIC = 2;
97 public final static int STYLE_MONOSPACE = 4;
99 public final static int FONT_FAMILY = 0;
100 public final static int FONT_FACE = 1;
101 public final static int FONT_RANDOM = 2;
103 private long nativeRunningHackPtr;
107 private Bitmap screenshot;
109 SharedPreferences prefs;
110 SharedPreferences.OnSharedPreferenceChangeListener pref_listener;
111 Hashtable<String, String> defaults = new Hashtable<String, String>();
114 // Maps font names to either: String (system font) or Typeface (bundled).
115 private Hashtable<String, Object> all_fonts =
116 new Hashtable<String, Object>();
122 // Doubles as the mutex controlling width/height/animating_p.
123 private Thread render;
125 private Runnable on_quit;
126 boolean button_down_p;
128 // These are defined in jwxyz-android.c:
130 private native long nativeInit (String hack,
131 Hashtable<String,String> defaults,
132 int w, int h, Surface window)
134 private native void nativeResize (int w, int h, double rot);
135 private native long nativeRender ();
136 private native void nativeDone ();
137 public native void sendButtonEvent (int x, int y, boolean down);
138 public native void sendMotionEvent (int x, int y);
139 public native void sendKeyEvent (boolean down_p, int code, int mods);
141 private void LOG (String fmt, Object... args) {
142 Log.d ("xscreensaver", hack + ": " + String.format (fmt, args));
145 static public String saverNameOf (Object obj) {
146 // Extract the saver name from e.g. "gen.Daydream$BouncingCow"
147 String name = obj.getClass().getSimpleName();
148 int index = name.lastIndexOf('$');
151 name = name.substring (index, name.length() - index);
153 return name.toLowerCase();
157 public jwxyz (String hack, Context app, Bitmap screenshot, int w, int h,
158 Surface surface, Runnable on_quit) {
162 this.screenshot = screenshot;
163 this.on_quit = on_quit;
166 this.surface = surface;
168 // nativeInit populates 'defaults' with the default values for keys
169 // that are not overridden by SharedPreferences.
171 prefs = app.getSharedPreferences (hack, 0);
173 // Keep a strong reference to pref_listener, because
174 // registerOnSharedPreferenceChangeListener only uses a weak reference.
175 pref_listener = new PrefListener();
176 prefs.registerOnSharedPreferenceChangeListener (pref_listener);
181 protected void finalize() {
182 if (render != null) {
183 LOG ("jwxyz finalized without close. This might be OK.");
189 public String getStringResource (String name) {
191 name = hack + "_" + name;
193 if (prefs.contains(name)) {
195 // SharedPreferences is very picky that you request the exact type that
196 // was stored: if it is a float and you ask for a string, you get an
197 // exception instead of the float converted to a string.
200 try { return prefs.getString (name, "");
201 } catch (Exception e) { }
203 try { return Float.toString (prefs.getFloat (name, 0));
204 } catch (Exception e) { }
206 try { return Long.toString (prefs.getLong (name, 0));
207 } catch (Exception e) { }
209 try { return Integer.toString (prefs.getInt (name, 0));
210 } catch (Exception e) { }
212 try { return (prefs.getBoolean (name, false) ? "true" : "false");
213 } catch (Exception e) { }
216 // If we got to here, it's not in there, so return the default.
217 return defaults.get (name);
221 private String mungeFontName (String name) {
222 // Roboto-ThinItalic => RobotoThin
223 // AndroidCock Regular => AndroidClock
224 String tails[] = { "Bold", "Italic", "Oblique", "Regular" };
225 for (String tail : tails) {
226 String pres[] = { " ", "-", "_", "" };
227 for (String pre : pres) {
228 int i = name.indexOf(pre + tail);
229 if (i > 0) name = name.substring (0, i);
236 private void scanSystemFonts() {
238 // First parse the system font directories for the global fonts.
240 String[] fontdirs = { "/system/fonts", "/system/font", "/data/fonts" };
241 TTFAnalyzer analyzer = new TTFAnalyzer();
242 for (String fontdir : fontdirs) {
243 File dir = new File(fontdir);
246 File[] files = dir.listFiles();
250 for (File file : files) {
251 String name = analyzer.getTtfFontName (file.getAbsolutePath());
253 // LOG ("unparsable system font: %s", file);
255 name = mungeFontName (name);
256 if (! all_fonts.contains (name)) {
257 // LOG ("system font \"%s\" %s", name, file);
258 all_fonts.put (name, name);
264 // Now parse our assets, for our bundled fonts.
266 AssetManager am = app.getAssets();
267 String dir = "fonts";
268 String[] files = null;
269 try { files = am.list(dir); }
270 catch (Exception e) { LOG("listing assets: %s", e.toString()); }
272 for (String fn : files) {
273 String fn2 = dir + "/" + fn;
274 Typeface t = Typeface.createFromAsset (am, fn2);
278 tmpfile = new File(app.getCacheDir(), fn);
279 if (tmpfile.createNewFile() == false) {
281 tmpfile.createNewFile();
284 InputStream in = am.open (fn2);
285 FileOutputStream out = new FileOutputStream (tmpfile);
286 byte[] buffer = new byte[1024 * 512];
287 while (in.read(buffer, 0, 1024 * 512) != -1) {
293 String name = analyzer.getTtfFontName (tmpfile.getAbsolutePath());
296 name = mungeFontName (name);
297 all_fonts.put (name, t);
298 // LOG ("asset font \"%s\" %s", name, fn);
299 } catch (Exception e) {
300 if (tmpfile != null) tmpfile.delete();
301 LOG ("error: %s", e.toString());
307 // Parses family names from X Logical Font Descriptions, including a few
308 // standard X font names that aren't handled by try_xlfd_font().
309 // Returns [ String name, Typeface ]
310 private Object[] parseXLFD (int mask, int traits,
311 String name, int name_type) {
312 boolean fixed = false;
313 boolean serif = false;
315 int style_jwxyz = mask & traits;
317 if (name_type != FONT_RANDOM) {
318 if ((style_jwxyz & STYLE_BOLD) != 0 ||
319 name.equals("fixed") ||
320 name.equals("courier") ||
321 name.equals("console") ||
322 name.equals("lucidatypewriter") ||
323 name.equals("monospace")) {
325 } else if (name.equals("times") ||
326 name.equals("georgia") ||
327 name.equals("serif")) {
329 } else if (name.equals("serif-monospace")) {
334 Random r = new Random();
335 serif = r.nextBoolean(); // Not much to randomize here...
336 fixed = (r.nextInt(8) == 0);
340 ? (serif ? "serif-monospace" : "monospace")
341 : (serif ? "serif" : "sans-serif"));
343 int style_android = 0;
344 if ((style_jwxyz & STYLE_BOLD) != 0)
345 style_android |= Typeface.BOLD;
346 if ((style_jwxyz & STYLE_ITALIC) != 0)
347 style_android |= Typeface.ITALIC;
349 return new Object[] { name, Typeface.create(name, style_android) };
353 // Parses "Native Font Name One 12, Native Font Name Two 14".
354 // Returns [ String name, Typeface ]
355 private Object[] parseNativeFont (String name) {
356 Object font2 = all_fonts.get (name);
357 if (font2 instanceof String)
358 font2 = Typeface.create (name, Typeface.NORMAL);
359 return new Object[] { name, (Typeface)font2 };
363 // Returns [ Paint paint, String family_name, Float ascent, Float descent ]
364 public Object[] loadFont(int mask, int traits, String name, int name_type,
368 if (name_type != FONT_RANDOM && name.equals("")) return null;
370 if (name_type == FONT_FACE) {
371 pair = parseNativeFont (name);
373 pair = parseXLFD (mask, traits, name, name_type);
376 String name2 = (String) pair[0];
377 Typeface font = (Typeface) pair[1];
381 String suffix = (font.isBold() && font.isItalic() ? " bold italic" :
382 font.isBold() ? " bold" :
383 font.isItalic() ? " italic" :
385 Paint paint = new Paint();
386 paint.setTypeface (font);
387 paint.setTextSize (size);
388 paint.setColor (Color.argb (0xFF, 0xFF, 0xFF, 0xFF));
390 LOG ("load font \"%s\" = \"%s %.1f\"", name, name2 + suffix, size);
392 FontMetrics fm = paint.getFontMetrics();
393 return new Object[] { paint, name2, -fm.ascent, fm.descent };
397 /* Returns a byte[] array containing XCharStruct with an optional
398 bitmap appended to it.
399 lbearing, rbearing, width, ascent, descent: 2 bytes each.
400 Followed by a WxH pixmap, 32 bits per pixel.
402 public ByteBuffer renderText (Paint paint, String text, boolean render_p,
403 boolean antialias_p) {
410 /* Font metric terminology, as used by X11:
412 "lbearing" is the distance from the logical origin to the leftmost
413 pixel. If a character's ink extends to the left of the origin, it is
416 "rbearing" is the distance from the logical origin to the rightmost
419 "descent" is the distance from the logical origin to the bottommost
420 pixel. For characters with descenders, it is positive. For
421 superscripts, it is negative.
423 "ascent" is the distance from the logical origin to the topmost pixel.
424 It is the number of pixels above the baseline.
426 "width" is the distance from the logical origin to the position where
427 the logical origin of the next character should be placed.
429 If "rbearing" is greater than "width", then this character overlaps the
430 following character. If smaller, then there is trailing blank space.
432 The bbox coordinates returned by getTextBounds grow down and right:
433 for a character with ink both above and below the baseline, top is
434 negative and bottom is positive.
436 paint.setAntiAlias (antialias_p);
437 FontMetrics fm = paint.getFontMetrics();
438 Rect bbox = new Rect();
439 paint.getTextBounds (text, 0, text.length(), bbox);
441 /* The bbox returned by getTextBounds measures from the logical origin
442 with right and down being positive. This means most characters have
443 a negative top, and characters with descenders have a positive bottom.
445 int lbearing = bbox.left;
446 int rbearing = bbox.right;
447 int ascent = -bbox.top;
448 int descent = bbox.bottom;
449 int width = (int) paint.measureText (text);
451 int w = rbearing - lbearing;
452 int h = ascent + descent;
453 int size = 5 * 2 + (render_p ? w * h * 4 : 0);
455 ByteBuffer bits = ByteBuffer.allocateDirect (size);
457 bits.put ((byte) ((lbearing >> 8) & 0xFF));
458 bits.put ((byte) ( lbearing & 0xFF));
459 bits.put ((byte) ((rbearing >> 8) & 0xFF));
460 bits.put ((byte) ( rbearing & 0xFF));
461 bits.put ((byte) ((width >> 8) & 0xFF));
462 bits.put ((byte) ( width & 0xFF));
463 bits.put ((byte) ((ascent >> 8) & 0xFF));
464 bits.put ((byte) ( ascent & 0xFF));
465 bits.put ((byte) ((descent >> 8) & 0xFF));
466 bits.put ((byte) ( descent & 0xFF));
468 if (render_p && w > 0 && h > 0) {
469 Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
470 Canvas canvas = new Canvas (bitmap);
471 canvas.drawText (text, -lbearing, ascent, paint);
472 bitmap.copyPixelsToBuffer (bits);
480 /* Returns the contents of the URL.
481 Loads the URL in a background thread: if the URL has not yet loaded,
482 this will return null. Once the URL has completely loaded, the full
483 contents will be returned. Calling this again after that starts the
486 private String loading_url = null;
487 private ByteBuffer loaded_url_body = null;
489 public synchronized ByteBuffer loadURL (String url) {
491 if (loaded_url_body != null) { // Thread finished
493 // LOG ("textclient finished %s", loading_url);
495 ByteBuffer bb = loaded_url_body;
497 loaded_url_body = null;
500 } else if (loading_url != null) { // Waiting on thread
501 // LOG ("textclient waiting...");
504 } else { // Launch thread
507 LOG ("textclient launching %s...", url);
509 new Thread (new Runnable() {
514 ByteBuffer body = ByteBuffer.allocateDirect (size);
517 URL u = new URL (loading_url);
518 // LOG ("textclient thread loading: %s", u.toString());
519 InputStream s = u.openStream();
520 byte buf[] = new byte[10240];
522 int n = s.read (buf);
524 // LOG ("textclient thread read %d", n);
525 if (count + n + 1 >= size) {
526 int size2 = (int) (size * 1.2 + size0);
527 // LOG ("textclient thread expand %d -> %d", size, size2);
528 ByteBuffer body2 = ByteBuffer.allocateDirect (size2);
531 body2.position (count);
535 body.put (buf, 0, n);
538 } catch (Exception e) {
539 LOG ("load URL error: %s", e.toString());
541 body.put (e.toString().getBytes());
545 // LOG ("textclient thread finished %s (%d)", loading_url, size);
546 loaded_url_body = body;
555 private ByteBuffer convertBitmap (String name, Bitmap bitmap,
556 int target_width, int target_height,
559 if (bitmap == null) return null;
563 int width = bitmap.getWidth();
564 int height = bitmap.getHeight();
566 LOG ("read image %s: %d x %d", name, width, height);
568 // First rotate the image as per EXIF.
572 switch (exif.getAttributeInt (ExifInterface.TAG_ORIENTATION,
573 ExifInterface.ORIENTATION_NORMAL)) {
574 case ExifInterface.ORIENTATION_ROTATE_90: deg = 90; break;
575 case ExifInterface.ORIENTATION_ROTATE_180: deg = 180; break;
576 case ExifInterface.ORIENTATION_ROTATE_270: deg = 270; break;
579 LOG ("%s: EXIF rotate %d", name, deg);
580 Matrix matrix = new Matrix();
581 matrix.preRotate (deg);
582 bitmap = Bitmap.createBitmap (bitmap, 0, 0, width, height,
584 width = bitmap.getWidth();
585 height = bitmap.getHeight();
589 // If the caller requested that we rotate the image to best fit the
590 // screen, rotate it again. (Could combine this with the above and
591 // avoid copying the image again, but I'm lazy.)
594 (width > height) != (target_width > target_height)) {
595 LOG ("%s: rotated to fit screen", name);
596 Matrix matrix = new Matrix();
597 matrix.preRotate (90);
598 bitmap = Bitmap.createBitmap (bitmap, 0, 0, width, height,
600 width = bitmap.getWidth();
601 height = bitmap.getHeight();
604 // Resize the image to be not larger than the screen, potentially
605 // copying it for the third time.
606 // Actually, always scale it, scaling up if necessary.
608 // if (width > target_width || height > target_height)
610 float r1 = target_width / (float) width;
611 float r2 = target_height / (float) height;
612 float r = (r1 > r2 ? r2 : r1);
613 LOG ("%s: resize %.1f: %d x %d => %d x %d", name,
614 r, width, height, (int) (width * r), (int) (height * r));
615 width = (int) (width * r);
616 height = (int) (height * r);
617 bitmap = Bitmap.createScaledBitmap (bitmap, width, height, true);
618 width = bitmap.getWidth();
619 height = bitmap.getHeight();
622 // Now convert it to a ByteBuffer in the form expected by the C caller.
624 byte[] nameb = name.getBytes("UTF-8");
625 int size = bitmap.getByteCount() + 4 + nameb.length + 1;
627 ByteBuffer bits = ByteBuffer.allocateDirect (size);
629 bits.put ((byte) ((width >> 8) & 0xFF));
630 bits.put ((byte) ( width & 0xFF));
631 bits.put ((byte) ((height >> 8) & 0xFF));
632 bits.put ((byte) ( height & 0xFF));
636 // The fourth of five copies. Good thing these are supercomputers.
637 bitmap.copyPixelsToBuffer (bits);
641 } catch (Exception e) {
642 LOG ("image %s unreadable: %s", name, e.toString());
649 public ByteBuffer loadRandomImage (int target_width, int target_height,
653 int max_size = 0x7FFF;
655 ArrayList<String> imgs = new ArrayList<String>();
657 ContentResolver cr = app.getContentResolver();
658 String[] cols = { MediaColumns.DATA,
659 MediaColumns.MIME_TYPE,
661 MediaColumns.HEIGHT };
663 android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI,
664 android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI };
666 for (int i = 0; i < uris.length; i++) {
667 Cursor cursor = cr.query (uris[i], cols, null, null, null);
669 int path_col = cursor.getColumnIndexOrThrow (cols[j++]);
670 int type_col = cursor.getColumnIndexOrThrow (cols[j++]);
671 int width_col = cursor.getColumnIndexOrThrow (cols[j++]);
672 int height_col = cursor.getColumnIndexOrThrow (cols[j++]);
673 while (cursor.moveToNext()) {
674 String path = cursor.getString(path_col);
675 String type = cursor.getString(type_col);
676 int w = Integer.parseInt (cursor.getString(width_col));
677 int h = Integer.parseInt (cursor.getString(height_col));
678 if (type.startsWith("image/") &&
679 w > min_size && h > min_size &&
680 w < max_size && h < max_size) {
689 int count = imgs.size();
695 int i = new Random().nextInt (count);
696 which = imgs.get (i);
697 LOG ("picked image %d of %d: %s", i, count, which);
699 Uri uri = Uri.fromFile (new File (which));
700 String name = uri.getLastPathSegment();
701 Bitmap bitmap = null;
702 ExifInterface exif = null;
705 bitmap = MediaStore.Images.Media.getBitmap (cr, uri);
706 } catch (Exception e) {
707 LOG ("image %s unloadable: %s", which, e.toString());
712 exif = new ExifInterface (uri.getPath()); // If it fails, who cares
713 } catch (Exception e) {
716 ByteBuffer bits = convertBitmap (name, bitmap,
717 target_width, target_height,
724 public ByteBuffer getScreenshot (int target_width, int target_height,
726 return convertBitmap ("Screenshot", screenshot,
727 target_width, target_height,
732 // Sadly duplicated from jwxyz.h (and thence X.h and keysymdef.h)
734 private static final int ShiftMask = (1<<0);
735 private static final int LockMask = (1<<1);
736 private static final int ControlMask = (1<<2);
737 private static final int Mod1Mask = (1<<3);
738 private static final int Mod2Mask = (1<<4);
739 private static final int Mod3Mask = (1<<5);
740 private static final int Mod4Mask = (1<<6);
741 private static final int Mod5Mask = (1<<7);
742 private static final int Button1Mask = (1<<8);
743 private static final int Button2Mask = (1<<9);
744 private static final int Button3Mask = (1<<10);
745 private static final int Button4Mask = (1<<11);
746 private static final int Button5Mask = (1<<12);
748 private static final int XK_Shift_L = 0xFFE1;
749 private static final int XK_Shift_R = 0xFFE2;
750 private static final int XK_Control_L = 0xFFE3;
751 private static final int XK_Control_R = 0xFFE4;
752 private static final int XK_Caps_Lock = 0xFFE5;
753 private static final int XK_Shift_Lock = 0xFFE6;
754 private static final int XK_Meta_L = 0xFFE7;
755 private static final int XK_Meta_R = 0xFFE8;
756 private static final int XK_Alt_L = 0xFFE9;
757 private static final int XK_Alt_R = 0xFFEA;
758 private static final int XK_Super_L = 0xFFEB;
759 private static final int XK_Super_R = 0xFFEC;
760 private static final int XK_Hyper_L = 0xFFED;
761 private static final int XK_Hyper_R = 0xFFEE;
763 private static final int XK_Home = 0xFF50;
764 private static final int XK_Left = 0xFF51;
765 private static final int XK_Up = 0xFF52;
766 private static final int XK_Right = 0xFF53;
767 private static final int XK_Down = 0xFF54;
768 private static final int XK_Prior = 0xFF55;
769 private static final int XK_Page_Up = 0xFF55;
770 private static final int XK_Next = 0xFF56;
771 private static final int XK_Page_Down = 0xFF56;
772 private static final int XK_End = 0xFF57;
773 private static final int XK_Begin = 0xFF58;
775 private static final int XK_F1 = 0xFFBE;
776 private static final int XK_F2 = 0xFFBF;
777 private static final int XK_F3 = 0xFFC0;
778 private static final int XK_F4 = 0xFFC1;
779 private static final int XK_F5 = 0xFFC2;
780 private static final int XK_F6 = 0xFFC3;
781 private static final int XK_F7 = 0xFFC4;
782 private static final int XK_F8 = 0xFFC5;
783 private static final int XK_F9 = 0xFFC6;
784 private static final int XK_F10 = 0xFFC7;
785 private static final int XK_F11 = 0xFFC8;
786 private static final int XK_F12 = 0xFFC9;
788 public void sendKeyEvent (KeyEvent event) {
789 int uc = event.getUnicodeChar();
790 int jcode = event.getKeyCode();
791 int jmods = event.getModifiers();
796 case KeyEvent.KEYCODE_SHIFT_LEFT: xcode = XK_Shift_L; break;
797 case KeyEvent.KEYCODE_SHIFT_RIGHT: xcode = XK_Shift_R; break;
798 case KeyEvent.KEYCODE_CTRL_LEFT: xcode = XK_Control_L; break;
799 case KeyEvent.KEYCODE_CTRL_RIGHT: xcode = XK_Control_R; break;
800 case KeyEvent.KEYCODE_CAPS_LOCK: xcode = XK_Caps_Lock; break;
801 case KeyEvent.KEYCODE_META_LEFT: xcode = XK_Meta_L; break;
802 case KeyEvent.KEYCODE_META_RIGHT: xcode = XK_Meta_R; break;
803 case KeyEvent.KEYCODE_ALT_LEFT: xcode = XK_Alt_L; break;
804 case KeyEvent.KEYCODE_ALT_RIGHT: xcode = XK_Alt_R; break;
806 case KeyEvent.KEYCODE_HOME: xcode = XK_Home; break;
807 case KeyEvent.KEYCODE_DPAD_LEFT: xcode = XK_Left; break;
808 case KeyEvent.KEYCODE_DPAD_UP: xcode = XK_Up; break;
809 case KeyEvent.KEYCODE_DPAD_RIGHT: xcode = XK_Right; break;
810 case KeyEvent.KEYCODE_DPAD_DOWN: xcode = XK_Down; break;
811 //case KeyEvent.KEYCODE_NAVIGATE_PREVIOUS: xcode = XK_Prior; break;
812 case KeyEvent.KEYCODE_PAGE_UP: xcode = XK_Page_Up; break;
813 //case KeyEvent.KEYCODE_NAVIGATE_NEXT: xcode = XK_Next; break;
814 case KeyEvent.KEYCODE_PAGE_DOWN: xcode = XK_Page_Down; break;
815 case KeyEvent.KEYCODE_MOVE_END: xcode = XK_End; break;
816 case KeyEvent.KEYCODE_MOVE_HOME: xcode = XK_Begin; break;
818 case KeyEvent.KEYCODE_F1: xcode = XK_F1; break;
819 case KeyEvent.KEYCODE_F2: xcode = XK_F2; break;
820 case KeyEvent.KEYCODE_F3: xcode = XK_F3; break;
821 case KeyEvent.KEYCODE_F4: xcode = XK_F4; break;
822 case KeyEvent.KEYCODE_F5: xcode = XK_F5; break;
823 case KeyEvent.KEYCODE_F6: xcode = XK_F6; break;
824 case KeyEvent.KEYCODE_F7: xcode = XK_F7; break;
825 case KeyEvent.KEYCODE_F8: xcode = XK_F8; break;
826 case KeyEvent.KEYCODE_F9: xcode = XK_F9; break;
827 case KeyEvent.KEYCODE_F10: xcode = XK_F10; break;
828 case KeyEvent.KEYCODE_F11: xcode = XK_F11; break;
829 case KeyEvent.KEYCODE_F12: xcode = XK_F12; break;
830 default: xcode = uc; break;
833 if (0 != (jmods & KeyEvent.META_SHIFT_ON)) xmods |= ShiftMask;
834 if (0 != (jmods & KeyEvent.META_CAPS_LOCK_ON)) xmods |= LockMask;
835 if (0 != (jmods & KeyEvent.META_CTRL_MASK)) xmods |= ControlMask;
836 if (0 != (jmods & KeyEvent.META_ALT_MASK)) xmods |= Mod1Mask;
837 if (0 != (jmods & KeyEvent.META_META_ON)) xmods |= Mod1Mask;
838 if (0 != (jmods & KeyEvent.META_SYM_ON)) xmods |= Mod2Mask;
839 if (0 != (jmods & KeyEvent.META_FUNCTION_ON)) xmods |= Mod3Mask;
841 /* If you touch and release Shift, you get no events.
842 If you type Shift-A, you get Shift down, A down, A up, Shift up.
843 So let's just ignore all lone modifier key events.
845 if (xcode >= XK_Shift_L && xcode <= XK_Hyper_R)
848 boolean down_p = event.getAction() == KeyEvent.ACTION_DOWN;
849 sendKeyEvent (down_p, xcode, xmods);
853 if (render == null) {
855 render = new Thread(new Runnable() {
859 int currentWidth, currentHeight;
860 synchronized (render) {
862 while (!animating_p || width == 0 || height == 0) {
865 } catch(InterruptedException exc) {
871 nativeInit (hack, defaults, width, height, surface);
872 currentWidth = width;
873 currentHeight= height;
875 } catch (SurfaceLost exc) {
884 synchronized (render) {
887 while (!animating_p) {
890 } catch(InterruptedException exc) {
895 if (currentWidth != width || currentHeight != height) {
896 currentWidth = width;
897 currentHeight = height;
898 nativeResize (width, height, 0);
902 long delay = nativeRender();
904 synchronized (render) {
907 render.wait(delay / 1000, (int)(delay % 1000) * 1000);
908 } catch (InterruptedException exc) {
912 if (Thread.interrupted ()) {
919 assert nativeRunningHackPtr != 0;
926 synchronized(render) {
936 synchronized (render) {
945 synchronized (render) {
951 } catch (InterruptedException exc) {
956 void resize (int w, int h) {
959 if (render != null) {
960 synchronized (render) {
972 /* We distinguish between taps and drags.
974 - Drags/pans (down, motion, up) are sent to the saver to handle.
975 - Single-taps exit the saver.
976 - Long-press single-taps are sent to the saver as ButtonPress/Release;
977 - Double-taps are sent to the saver as a "Space" keypress.
980 - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow.
984 public boolean onSingleTapConfirmed (MotionEvent event) {
991 public boolean onDoubleTap (MotionEvent event) {
992 sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN,
993 KeyEvent.KEYCODE_SPACE));
998 public void onLongPress (MotionEvent event) {
999 if (! button_down_p) {
1000 int x = (int) event.getX (event.getPointerId (0));
1001 int y = (int) event.getY (event.getPointerId (0));
1002 sendButtonEvent (x, y, true);
1003 sendButtonEvent (x, y, false);
1008 public void onShowPress (MotionEvent event) {
1009 if (! button_down_p) {
1010 button_down_p = true;
1011 int x = (int) event.getX (event.getPointerId (0));
1012 int y = (int) event.getY (event.getPointerId (0));
1013 sendButtonEvent (x, y, true);
1018 public boolean onScroll (MotionEvent e1, MotionEvent e2,
1019 float distanceX, float distanceY) {
1020 // LOG ("onScroll: %d", button_down_p ? 1 : 0);
1022 sendMotionEvent ((int) e2.getX (e2.getPointerId (0)),
1023 (int) e2.getY (e2.getPointerId (0)));
1027 // If you drag too fast, you get a single onFling event instead of a
1028 // succession of onScroll events. I can't figure out how to disable it.
1030 public boolean onFling (MotionEvent e1, MotionEvent e2,
1031 float velocityX, float velocityY) {
1035 public boolean dragEnded (MotionEvent event) {
1036 if (button_down_p) {
1037 int x = (int) event.getX (event.getPointerId (0));
1038 int y = (int) event.getY (event.getPointerId (0));
1039 sendButtonEvent (x, y, false);
1040 button_down_p = false;
1046 public boolean onDown (MotionEvent event) {
1051 public boolean onSingleTapUp (MotionEvent event) {
1056 public boolean onDoubleTapEvent (MotionEvent event) {
1062 System.loadLibrary ("xscreensaver");
1065 Thread.setDefaultUncaughtExceptionHandler(
1066 new Thread.UncaughtExceptionHandler() {
1067 Thread.UncaughtExceptionHandler old_handler =
1068 Thread.currentThread().getUncaughtExceptionHandler();
1071 public void uncaughtException (Thread thread, Throwable ex) {
1072 String err = ex.toString();
1073 Log.d ("xscreensaver", "Caught exception: " + err);
1074 old_handler.uncaughtException (thread, ex);