1 /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * xscreensaver, Copyright (c) 2016 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.view.KeyEvent;
25 import android.content.SharedPreferences;
26 import android.content.Context;
27 import android.content.ContentResolver;
28 import android.content.res.AssetManager;
29 import android.graphics.Typeface;
30 import android.graphics.Rect;
31 import android.graphics.Paint;
32 import android.graphics.Paint.FontMetrics;
33 import android.graphics.Bitmap;
34 import android.graphics.Canvas;
35 import android.graphics.Color;
36 import android.graphics.Matrix;
37 import android.net.Uri;
39 import java.nio.ByteBuffer;
41 import java.io.InputStream;
42 import java.io.FileOutputStream;
43 import android.database.Cursor;
44 import android.provider.MediaStore;
45 import android.provider.MediaStore.MediaColumns;
46 import android.media.ExifInterface;
47 import org.jwz.xscreensaver.TTFAnalyzer;
48 import android.util.Log;
52 private void LOG (String fmt, Object... args) {
53 Log.d ("xscreensaver",
54 this.getClass().getSimpleName() + ": " +
55 String.format (fmt, args));
58 private long nativeRunningHackPtr;
62 public final static int API_XLIB = 0;
63 public final static int API_GL = 1;
66 SharedPreferences prefs;
67 Hashtable<String, String> defaults = new Hashtable<String, String>();
69 // Maps numeric IDs to Java objects, so that C code can reference them
70 // even if GC relocates them.
71 private Hashtable<Long, Object> rootset = new Hashtable<Long, Object>();
72 private long rootset_id = 1000;
75 // Maps font names to either: String (system font) or Typeface (bundled).
76 private Hashtable<String, Object> all_fonts =
77 new Hashtable<String, Object>();
80 // These are defined in jwxyz-android.c:
82 private native void nativeInit (String hack, int api,
83 Hashtable<String,String> defaults,
85 public native void nativeResize (int w, int h, double rot);
86 public native void nativeRender ();
87 public native void nativeDone ();
88 public native void sendButtonEvent (int x, int y, boolean down);
89 public native void sendMotionEvent (int x, int y);
90 public native void sendKeyEvent (boolean down_p, int code, int mods);
93 public jwxyz (String hack, int api, Context app, Bitmap screenshot,
98 this.screenshot = screenshot;
100 // nativeInit populates 'defaults' with the default values for keys
101 // that are not overridden by SharedPreferences.
103 prefs = app.getSharedPreferences (hack, 0);
105 nativeInit (hack, api, defaults, w, h);
108 /* TODO: Can't do this yet; nativeDone requires the OpenGL context to be set.
109 protected void finalize() {
110 if (nativeRunningHackPtr != 0) {
116 public String getStringResource (String name) {
118 name = hack + "_" + name;
120 if (prefs.contains(name)) {
122 // SharedPreferences is very picky that you request the exact type that
123 // was stored: if it is a float and you ask for a string, you get an
124 // exception instead of the float converted to a string.
127 try { return prefs.getString (name, "");
128 } catch (Exception e) { }
130 try { return Float.toString (prefs.getFloat (name, 0));
131 } catch (Exception e) { }
133 try { return Long.toString (prefs.getLong (name, 0));
134 } catch (Exception e) { }
136 try { return Integer.toString (prefs.getInt (name, 0));
137 } catch (Exception e) { }
139 try { return (prefs.getBoolean (name, false) ? "true" : "false");
140 } catch (Exception e) { }
143 // If we got to here, it's not in there, so return the default.
144 return defaults.get (name);
148 private String mungeFontName (String name) {
149 // Roboto-ThinItalic => RobotoThin
150 // AndroidCock Regular => AndroidClock
151 String tails[] = { "Bold", "Italic", "Oblique", "Regular" };
152 for (String tail : tails) {
153 String pres[] = { " ", "-", "_", "" };
154 for (String pre : pres) {
155 int i = name.indexOf(pre + tail);
156 if (i > 0) name = name.substring (0, i);
163 private void scanSystemFonts() {
165 // First parse the system font directories for the global fonts.
167 String[] fontdirs = { "/system/fonts", "/system/font", "/data/fonts" };
168 TTFAnalyzer analyzer = new TTFAnalyzer();
169 for (String fontdir : fontdirs) {
170 File dir = new File(fontdir);
173 File[] files = dir.listFiles();
177 for (File file : files) {
178 String name = analyzer.getTtfFontName (file.getAbsolutePath());
180 // LOG ("unparsable system font: %s", file);
182 name = mungeFontName (name);
183 if (! all_fonts.contains (name)) {
184 // LOG ("system font \"%s\" %s", name, file);
185 all_fonts.put (name, name);
191 // Now parse our assets, for our bundled fonts.
193 AssetManager am = app.getAssets();
194 String dir = "fonts";
195 String[] files = null;
196 try { files = am.list(dir); }
197 catch (Exception e) { LOG("listing assets: %s", e.toString()); }
199 for (String fn : files) {
200 String fn2 = dir + "/" + fn;
201 Typeface t = Typeface.createFromAsset (am, fn2);
205 tmpfile = new File(app.getCacheDir(), fn);
206 if (tmpfile.createNewFile() == false) {
208 tmpfile.createNewFile();
211 InputStream in = am.open (fn2);
212 FileOutputStream out = new FileOutputStream (tmpfile);
213 byte[] buffer = new byte[1024 * 512];
214 while (in.read(buffer, 0, 1024 * 512) != -1) {
220 String name = analyzer.getTtfFontName (tmpfile.getAbsolutePath());
223 name = mungeFontName (name);
224 all_fonts.put (name, t);
225 // LOG ("asset font \"%s\" %s", name, fn);
226 } catch (Exception e) {
227 if (tmpfile != null) tmpfile.delete();
228 LOG ("error: %s", e.toString());
234 // Parses X Logical Font Descriptions, and a few standard X font names.
235 // Returns [ String name, Float size, Typeface ]
236 private Object[] parseXLFD (String name) {
238 boolean bold = false;
239 boolean italic = false;
240 boolean fixed = false;
241 boolean serif = false;
243 if (name.equals("6x10")) { size = 8; fixed = true; }
244 else if (name.equals("6x10bold")) { size = 8; fixed = true; bold = true; }
245 else if (name.equals("fixed")) { size = 12; fixed = true; }
246 else if (name.equals("9x15")) { size = 12; fixed = true; }
247 else if (name.equals("9x15bold")) { size = 12; fixed = true; bold = true; }
248 else if (name.equals("vga")) { size = 12; fixed = true; }
249 else if (name.equals("console")) { size = 12; fixed = true; }
250 else if (name.equals("gallant")) { size = 12; fixed = true; }
252 String[] tokens = name.split("-"); // XLFD
253 int L = tokens.length;
255 String foundry = (i < L ? tokens[i++] : "");
256 String family = (i < L ? tokens[i++] : "");
257 String weight = (i < L ? tokens[i++] : "");
258 String slant = (i < L ? tokens[i++] : "");
259 String setwidth = (i < L ? tokens[i++] : "");
260 String adstyle = (i < L ? tokens[i++] : "");
261 String pxsize = (i < L ? tokens[i++] : "");
262 String ptsize = (i < L ? tokens[i++] : "");
263 String resx = (i < L ? tokens[i++] : "");
264 String resy = (i < L ? tokens[i++] : "");
265 String spacing = (i < L ? tokens[i++] : "");
266 String avgw = (i < L ? tokens[i++] : "");
267 String charset = (i < L ? tokens[i++] : "");
268 String registry = (i < L ? tokens[i++] : "");
270 if (spacing.equals("m") ||
271 family.equals("fixed") ||
272 family.equals("courier") ||
273 family.equals("console") ||
274 family.equals("lucidatypewriter")) {
276 } else if (family.equals("times") ||
277 family.equals("georgia")) {
281 if (weight.equals("bold") || weight.equals("demibold")) {
285 if (slant.equals("i") || slant.equals("o")) {
289 // -*-courier-bold-r-*-*-14-*-*-*-*-*-*-* 14 px
290 // -*-courier-bold-r-*-*-*-140-*-*-m-*-*-* 14 pt
291 // -*-courier-bold-r-*-*-140-* 14 pt, via wildcard
292 // -*-courier-bold-r-*-140-* 14 pt, not handled
293 // -*-courier-bold-r-*-*-14-180-*-*-*-*-*-* error
295 if (!ptsize.equals("") && !ptsize.equals("*")) {
296 // It was in the ptsize field, so that's definitely what it is.
297 size = Float.valueOf(ptsize) / 10.0f;
298 } else if (!pxsize.equals("") && !pxsize.equals("*")) {
299 size = Float.valueOf(pxsize);
300 // If it's a fully qualified XLFD, then this really is the pxsize.
301 // Otherwise, this is probably point size with a multi-field wildcard.
302 if (registry.equals("")) // not a fully qualified XLFD
307 if (name.equals("random")) {
308 Random r = new Random();
309 serif = r.nextBoolean(); // Not much to randomize here...
310 fixed = (r.nextInt(8) == 0);
314 ? (serif ? "serif-monospace" : "monospace")
315 : (serif ? "serif" : "sans-serif"));
317 Typeface font = Typeface.create (name,
318 (bold && italic ? Typeface.BOLD_ITALIC :
319 bold ? Typeface.BOLD :
320 italic ? Typeface.ITALIC :
323 Object ret[] = { name, new Float(size), font };
328 // Parses "Native Font Name One 12, Native Font Name Two 14".
329 // Returns [ String name, Float size, Typeface ]
330 private Object[] parseNativeFont (String names) {
331 for (String name : names.split(",")) {
334 if (name.equals("")) continue;
335 int spc = name.lastIndexOf(" ");
337 size = Float.valueOf (name.substring (spc + 1));
338 name = name.substring (0, spc);
343 Object font = all_fonts.get (name);
344 if (font instanceof String)
345 font = Typeface.create (name, Typeface.NORMAL);
348 Object ret[] = { name, size, (Typeface) font };
357 // Returns [ Long font_id, String name, Float size, ascent, descent ]
358 public Object[] loadFont(String name) {
361 if (name.equals("")) return null;
363 if (name.contains(" ")) {
364 pair = parseNativeFont (name);
366 pair = parseXLFD (name);
369 if (pair == null) return null;
371 String name2 = (String) pair[0];
372 float size = (Float) pair[1];
373 Typeface font = (Typeface) pair[2];
377 name2 += (font.isBold() && font.isItalic() ? " bold italic" :
378 font.isBold() ? " bold" :
379 font.isItalic() ? " italic" :
381 Paint paint = new Paint();
382 paint.setTypeface (font);
383 paint.setTextSize (size);
384 paint.setColor (Color.argb (0xFF, 0xFF, 0xFF, 0xFF));
386 Long font_key = new Long(rootset_id++);
387 rootset.put (font_key, paint);
389 LOG ("load font %s, \"%s\" = \"%s %.1f\"",
390 font_key.toString(), name, name2, size);
392 FontMetrics fm = paint.getFontMetrics();
393 Object ret[] = { font_key, name2, new Float(size),
394 new Float(-fm.ascent), new Float(fm.descent) };
399 public void releaseFont(long font_id) {
400 rootset.remove (new Long(font_id));
403 /* Returns a byte[] array containing XCharStruct with an optional
404 bitmap appended to it.
405 lbearing, rbearing, width, ascent, descent: 2 bytes each.
406 Followed by a WxH pixmap, 32 bits per pixel.
408 public ByteBuffer renderText (long font_id, String text, boolean render_p) {
410 Paint paint = (Paint) rootset.get(new Long(font_id));
413 LOG ("no font %d", font_id);
417 /* Font metric terminology, as used by X11:
419 "lbearing" is the distance from the logical origin to the leftmost
420 pixel. If a character's ink extends to the left of the origin, it is
423 "rbearing" is the distance from the logical origin to the rightmost
426 "descent" is the distance from the logical origin to the bottommost
427 pixel. For characters with descenders, it is positive. For
428 superscripts, it is negative.
430 "ascent" is the distance from the logical origin to the topmost pixel.
431 It is the number of pixels above the baseline.
433 "width" is the distance from the logical origin to the position where
434 the logical origin of the next character should be placed.
436 If "rbearing" is greater than "width", then this character overlaps the
437 following character. If smaller, then there is trailing blank space.
439 The bbox coordinates returned by getTextBounds grow down and right:
440 for a character with ink both above and below the baseline, top is
441 negative and bottom is positive.
443 FontMetrics fm = paint.getFontMetrics();
444 Rect bbox = new Rect();
445 paint.getTextBounds (text, 0, text.length(), bbox);
447 /* The bbox returned by getTextBounds measures from the logical origin
448 with right and down being positive. This means most characters have
449 a negative top, and characters with descenders have a positive bottom.
451 int lbearing = bbox.left;
452 int rbearing = bbox.right;
453 int ascent = -bbox.top;
454 int descent = bbox.bottom;
455 int width = (int) paint.measureText (text);
457 int w = rbearing - lbearing;
458 int h = ascent + descent;
459 int size = 5 * 2 + (render_p ? w * h * 4 : 0);
461 ByteBuffer bits = ByteBuffer.allocateDirect (size);
463 bits.put ((byte) ((lbearing >> 8) & 0xFF));
464 bits.put ((byte) ( lbearing & 0xFF));
465 bits.put ((byte) ((rbearing >> 8) & 0xFF));
466 bits.put ((byte) ( rbearing & 0xFF));
467 bits.put ((byte) ((width >> 8) & 0xFF));
468 bits.put ((byte) ( width & 0xFF));
469 bits.put ((byte) ((ascent >> 8) & 0xFF));
470 bits.put ((byte) ( ascent & 0xFF));
471 bits.put ((byte) ((descent >> 8) & 0xFF));
472 bits.put ((byte) ( descent & 0xFF));
474 if (render_p && w > 0 && h > 0) {
475 Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
476 Canvas canvas = new Canvas (bitmap);
477 canvas.drawText (text, -lbearing, ascent, paint);
478 bitmap.copyPixelsToBuffer (bits);
486 // Returns the contents of the URL. Blocking load.
487 public ByteBuffer loadURL (String url) {
492 ByteBuffer body = ByteBuffer.allocateDirect (size);
495 LOG ("load URL: %s", url);
496 URL u = new URL(url);
497 InputStream s = u.openStream();
498 byte buf[] = new byte[10240];
500 int n = s.read (buf);
502 // LOG ("read %d", n);
503 if (count + n + 1 >= size) {
504 int size2 = (int) (size * 1.2 + size0);
505 // LOG ("expand %d -> %d", size, size2);
506 ByteBuffer body2 = ByteBuffer.allocateDirect (size2);
509 body2.position (count);
513 body.put (buf, 0, n);
516 } catch (Exception e) {
517 LOG ("load URL error: %s", e.toString());
519 body.put (e.toString().getBytes());
527 private ByteBuffer convertBitmap (String name, Bitmap bitmap,
528 int target_width, int target_height,
531 if (bitmap == null) return null;
535 int width = bitmap.getWidth();
536 int height = bitmap.getHeight();
538 LOG ("read image %s: %d x %d", name, width, height);
540 // First rotate the image as per EXIF.
544 switch (exif.getAttributeInt (ExifInterface.TAG_ORIENTATION,
545 ExifInterface.ORIENTATION_NORMAL)) {
546 case ExifInterface.ORIENTATION_ROTATE_90: deg = 90; break;
547 case ExifInterface.ORIENTATION_ROTATE_180: deg = 180; break;
548 case ExifInterface.ORIENTATION_ROTATE_270: deg = 270; break;
551 LOG ("%s: EXIF rotate %d", name, deg);
552 Matrix matrix = new Matrix();
553 matrix.preRotate (deg);
554 bitmap = Bitmap.createBitmap (bitmap, 0, 0, width, height,
556 width = bitmap.getWidth();
557 height = bitmap.getHeight();
561 // If the caller requested that we rotate the image to best fit the
562 // screen, rotate it again. (Could combine this with the above and
563 // avoid copying the image again, but I'm lazy.)
566 (width > height) != (target_width > target_height)) {
567 LOG ("%s: rotated to fit screen", name);
568 Matrix matrix = new Matrix();
569 matrix.preRotate (90);
570 bitmap = Bitmap.createBitmap (bitmap, 0, 0, width, height,
572 width = bitmap.getWidth();
573 height = bitmap.getHeight();
576 // Resize the image to be not larger than the screen, potentially
577 // copying it for the third time.
578 // Actually, always scale it, scaling up if necessary.
580 // if (width > target_width || height > target_height)
582 float r1 = target_width / (float) width;
583 float r2 = target_height / (float) height;
584 float r = (r1 > r2 ? r2 : r1);
585 LOG ("%s: resize %.1f: %d x %d => %d x %d", name,
586 r, width, height, (int) (width * r), (int) (height * r));
587 width = (int) (width * r);
588 height = (int) (height * r);
589 bitmap = Bitmap.createScaledBitmap (bitmap, width, height, true);
590 width = bitmap.getWidth();
591 height = bitmap.getHeight();
594 // Now convert it to a ByteBuffer in the form expected by the C caller.
596 byte[] nameb = name.getBytes("UTF-8");
597 int size = bitmap.getByteCount() + 4 + nameb.length + 1;
599 ByteBuffer bits = ByteBuffer.allocateDirect (size);
601 bits.put ((byte) ((width >> 8) & 0xFF));
602 bits.put ((byte) ( width & 0xFF));
603 bits.put ((byte) ((height >> 8) & 0xFF));
604 bits.put ((byte) ( height & 0xFF));
608 // The fourth of five copies. Good thing these are supercomputers.
609 bitmap.copyPixelsToBuffer (bits);
613 } catch (Exception e) {
614 LOG ("image %s unreadable: %s", name, e.toString());
621 public ByteBuffer loadRandomImage (int target_width, int target_height,
625 int max_size = 0x7FFF;
627 ArrayList<String> imgs = new ArrayList<String>();
629 ContentResolver cr = app.getContentResolver();
630 String[] cols = { MediaColumns.DATA,
631 MediaColumns.MIME_TYPE,
633 MediaColumns.HEIGHT };
635 android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI,
636 android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI };
638 for (int i = 0; i < uris.length; i++) {
639 Cursor cursor = cr.query (uris[i], cols, null, null, null);
641 int path_col = cursor.getColumnIndexOrThrow (cols[j++]);
642 int type_col = cursor.getColumnIndexOrThrow (cols[j++]);
643 int width_col = cursor.getColumnIndexOrThrow (cols[j++]);
644 int height_col = cursor.getColumnIndexOrThrow (cols[j++]);
645 while (cursor.moveToNext()) {
646 String path = cursor.getString(path_col);
647 String type = cursor.getString(type_col);
648 int w = Integer.parseInt (cursor.getString(width_col));
649 int h = Integer.parseInt (cursor.getString(height_col));
650 if (type.startsWith("image/") &&
651 w > min_size && h > min_size &&
652 w < max_size && h < max_size) {
661 int count = imgs.size();
667 int i = new Random().nextInt (count);
668 which = imgs.get (i);
669 LOG ("picked image %d of %d: %s", i, count, which);
671 Uri uri = Uri.fromFile (new File (which));
672 String name = uri.getLastPathSegment();
673 Bitmap bitmap = null;
674 ExifInterface exif = null;
677 bitmap = MediaStore.Images.Media.getBitmap (cr, uri);
678 } catch (Exception e) {
679 LOG ("image %s unloadable: %s", which, e.toString());
684 exif = new ExifInterface (uri.getPath()); // If it fails, who cares
685 } catch (Exception e) {
688 ByteBuffer bits = convertBitmap (name, bitmap,
689 target_width, target_height,
696 public ByteBuffer getScreenshot (int target_width, int target_height,
698 return convertBitmap ("Screenshot", screenshot,
699 target_width, target_height,
704 // Sadly duplicated from jwxyz.h (and thence X.h and keysymdef.h)
706 private static final int ShiftMask = (1<<0);
707 private static final int LockMask = (1<<1);
708 private static final int ControlMask = (1<<2);
709 private static final int Mod1Mask = (1<<3);
710 private static final int Mod2Mask = (1<<4);
711 private static final int Mod3Mask = (1<<5);
712 private static final int Mod4Mask = (1<<6);
713 private static final int Mod5Mask = (1<<7);
714 private static final int Button1Mask = (1<<8);
715 private static final int Button2Mask = (1<<9);
716 private static final int Button3Mask = (1<<10);
717 private static final int Button4Mask = (1<<11);
718 private static final int Button5Mask = (1<<12);
720 private static final int XK_Shift_L = 0xFFE1;
721 private static final int XK_Shift_R = 0xFFE2;
722 private static final int XK_Control_L = 0xFFE3;
723 private static final int XK_Control_R = 0xFFE4;
724 private static final int XK_Caps_Lock = 0xFFE5;
725 private static final int XK_Shift_Lock = 0xFFE6;
726 private static final int XK_Meta_L = 0xFFE7;
727 private static final int XK_Meta_R = 0xFFE8;
728 private static final int XK_Alt_L = 0xFFE9;
729 private static final int XK_Alt_R = 0xFFEA;
730 private static final int XK_Super_L = 0xFFEB;
731 private static final int XK_Super_R = 0xFFEC;
732 private static final int XK_Hyper_L = 0xFFED;
733 private static final int XK_Hyper_R = 0xFFEE;
735 private static final int XK_Home = 0xFF50;
736 private static final int XK_Left = 0xFF51;
737 private static final int XK_Up = 0xFF52;
738 private static final int XK_Right = 0xFF53;
739 private static final int XK_Down = 0xFF54;
740 private static final int XK_Prior = 0xFF55;
741 private static final int XK_Page_Up = 0xFF55;
742 private static final int XK_Next = 0xFF56;
743 private static final int XK_Page_Down = 0xFF56;
744 private static final int XK_End = 0xFF57;
745 private static final int XK_Begin = 0xFF58;
747 private static final int XK_F1 = 0xFFBE;
748 private static final int XK_F2 = 0xFFBF;
749 private static final int XK_F3 = 0xFFC0;
750 private static final int XK_F4 = 0xFFC1;
751 private static final int XK_F5 = 0xFFC2;
752 private static final int XK_F6 = 0xFFC3;
753 private static final int XK_F7 = 0xFFC4;
754 private static final int XK_F8 = 0xFFC5;
755 private static final int XK_F9 = 0xFFC6;
756 private static final int XK_F10 = 0xFFC7;
757 private static final int XK_F11 = 0xFFC8;
758 private static final int XK_F12 = 0xFFC9;
760 public void sendKeyEvent (KeyEvent event) {
761 int uc = event.getUnicodeChar();
762 int jcode = event.getKeyCode();
763 int jmods = event.getModifiers();
768 case KeyEvent.KEYCODE_SHIFT_LEFT: xcode = XK_Shift_L; break;
769 case KeyEvent.KEYCODE_SHIFT_RIGHT: xcode = XK_Shift_R; break;
770 case KeyEvent.KEYCODE_CTRL_LEFT: xcode = XK_Control_L; break;
771 case KeyEvent.KEYCODE_CTRL_RIGHT: xcode = XK_Control_R; break;
772 case KeyEvent.KEYCODE_CAPS_LOCK: xcode = XK_Caps_Lock; break;
773 case KeyEvent.KEYCODE_META_LEFT: xcode = XK_Meta_L; break;
774 case KeyEvent.KEYCODE_META_RIGHT: xcode = XK_Meta_R; break;
775 case KeyEvent.KEYCODE_ALT_LEFT: xcode = XK_Alt_L; break;
776 case KeyEvent.KEYCODE_ALT_RIGHT: xcode = XK_Alt_R; break;
778 case KeyEvent.KEYCODE_HOME: xcode = XK_Home; break;
779 case KeyEvent.KEYCODE_DPAD_LEFT: xcode = XK_Left; break;
780 case KeyEvent.KEYCODE_DPAD_UP: xcode = XK_Up; break;
781 case KeyEvent.KEYCODE_DPAD_RIGHT: xcode = XK_Right; break;
782 case KeyEvent.KEYCODE_DPAD_DOWN: xcode = XK_Down; break;
783 //case KeyEvent.KEYCODE_NAVIGATE_PREVIOUS: xcode = XK_Prior; break;
784 case KeyEvent.KEYCODE_PAGE_UP: xcode = XK_Page_Up; break;
785 //case KeyEvent.KEYCODE_NAVIGATE_NEXT: xcode = XK_Next; break;
786 case KeyEvent.KEYCODE_PAGE_DOWN: xcode = XK_Page_Down; break;
787 case KeyEvent.KEYCODE_MOVE_END: xcode = XK_End; break;
788 case KeyEvent.KEYCODE_MOVE_HOME: xcode = XK_Begin; break;
790 case KeyEvent.KEYCODE_F1: xcode = XK_F1; break;
791 case KeyEvent.KEYCODE_F2: xcode = XK_F2; break;
792 case KeyEvent.KEYCODE_F3: xcode = XK_F3; break;
793 case KeyEvent.KEYCODE_F4: xcode = XK_F4; break;
794 case KeyEvent.KEYCODE_F5: xcode = XK_F5; break;
795 case KeyEvent.KEYCODE_F6: xcode = XK_F6; break;
796 case KeyEvent.KEYCODE_F7: xcode = XK_F7; break;
797 case KeyEvent.KEYCODE_F8: xcode = XK_F8; break;
798 case KeyEvent.KEYCODE_F9: xcode = XK_F9; break;
799 case KeyEvent.KEYCODE_F10: xcode = XK_F10; break;
800 case KeyEvent.KEYCODE_F11: xcode = XK_F11; break;
801 case KeyEvent.KEYCODE_F12: xcode = XK_F12; break;
802 default: xcode = uc; break;
805 if (0 != (jmods & KeyEvent.META_SHIFT_ON)) xmods |= ShiftMask;
806 if (0 != (jmods & KeyEvent.META_CAPS_LOCK_ON)) xmods |= LockMask;
807 if (0 != (jmods & KeyEvent.META_CTRL_MASK)) xmods |= ControlMask;
808 if (0 != (jmods & KeyEvent.META_ALT_MASK)) xmods |= Mod1Mask;
809 if (0 != (jmods & KeyEvent.META_META_ON)) xmods |= Mod1Mask;
810 if (0 != (jmods & KeyEvent.META_SYM_ON)) xmods |= Mod2Mask;
811 if (0 != (jmods & KeyEvent.META_FUNCTION_ON)) xmods |= Mod3Mask;
813 /* If you touch and release Shift, you get no events.
814 If you type Shift-A, you get Shift down, A down, A up, Shift up.
815 So let's just ignore all lone modifier key events.
817 if (xcode >= XK_Shift_L && xcode <= XK_Hyper_R)
820 boolean down_p = event.getAction() == KeyEvent.ACTION_DOWN;
821 sendKeyEvent (down_p, xcode, xmods);