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 java.lang.Runnable;
44 import java.lang.Thread;
45 import android.database.Cursor;
46 import android.provider.MediaStore;
47 import android.provider.MediaStore.MediaColumns;
48 import android.media.ExifInterface;
49 import org.jwz.xscreensaver.TTFAnalyzer;
50 import android.util.Log;
54 private void LOG (String fmt, Object... args) {
55 Log.d ("xscreensaver",
56 this.getClass().getSimpleName() + ": " +
57 String.format (fmt, args));
60 private long nativeRunningHackPtr;
64 public final static int API_XLIB = 0;
65 public final static int API_GL = 1;
68 SharedPreferences prefs;
69 Hashtable<String, String> defaults = new Hashtable<String, String>();
72 // Maps font names to either: String (system font) or Typeface (bundled).
73 private Hashtable<String, Object> all_fonts =
74 new Hashtable<String, Object>();
77 // These are defined in jwxyz-android.c:
79 private native void nativeInit (String hack, int api,
80 Hashtable<String,String> defaults,
82 public native void nativeResize (int w, int h, double rot);
83 public native long nativeRender ();
84 public native void nativeDone ();
85 public native void sendButtonEvent (int x, int y, boolean down);
86 public native void sendMotionEvent (int x, int y);
87 public native void sendKeyEvent (boolean down_p, int code, int mods);
90 public jwxyz (String hack, int api, Context app, Bitmap screenshot,
95 this.screenshot = screenshot;
97 // nativeInit populates 'defaults' with the default values for keys
98 // that are not overridden by SharedPreferences.
100 prefs = app.getSharedPreferences (hack, 0);
102 nativeInit (hack, api, defaults, w, h);
105 /* TODO: Can't do this yet; nativeDone requires the OpenGL context to be set.
106 protected void finalize() {
107 if (nativeRunningHackPtr != 0) {
113 public String getStringResource (String name) {
115 name = hack + "_" + name;
117 if (prefs.contains(name)) {
119 // SharedPreferences is very picky that you request the exact type that
120 // was stored: if it is a float and you ask for a string, you get an
121 // exception instead of the float converted to a string.
124 try { return prefs.getString (name, "");
125 } catch (Exception e) { }
127 try { return Float.toString (prefs.getFloat (name, 0));
128 } catch (Exception e) { }
130 try { return Long.toString (prefs.getLong (name, 0));
131 } catch (Exception e) { }
133 try { return Integer.toString (prefs.getInt (name, 0));
134 } catch (Exception e) { }
136 try { return (prefs.getBoolean (name, false) ? "true" : "false");
137 } catch (Exception e) { }
140 // If we got to here, it's not in there, so return the default.
141 return defaults.get (name);
145 private String mungeFontName (String name) {
146 // Roboto-ThinItalic => RobotoThin
147 // AndroidCock Regular => AndroidClock
148 String tails[] = { "Bold", "Italic", "Oblique", "Regular" };
149 for (String tail : tails) {
150 String pres[] = { " ", "-", "_", "" };
151 for (String pre : pres) {
152 int i = name.indexOf(pre + tail);
153 if (i > 0) name = name.substring (0, i);
160 private void scanSystemFonts() {
162 // First parse the system font directories for the global fonts.
164 String[] fontdirs = { "/system/fonts", "/system/font", "/data/fonts" };
165 TTFAnalyzer analyzer = new TTFAnalyzer();
166 for (String fontdir : fontdirs) {
167 File dir = new File(fontdir);
170 File[] files = dir.listFiles();
174 for (File file : files) {
175 String name = analyzer.getTtfFontName (file.getAbsolutePath());
177 // LOG ("unparsable system font: %s", file);
179 name = mungeFontName (name);
180 if (! all_fonts.contains (name)) {
181 // LOG ("system font \"%s\" %s", name, file);
182 all_fonts.put (name, name);
188 // Now parse our assets, for our bundled fonts.
190 AssetManager am = app.getAssets();
191 String dir = "fonts";
192 String[] files = null;
193 try { files = am.list(dir); }
194 catch (Exception e) { LOG("listing assets: %s", e.toString()); }
196 for (String fn : files) {
197 String fn2 = dir + "/" + fn;
198 Typeface t = Typeface.createFromAsset (am, fn2);
202 tmpfile = new File(app.getCacheDir(), fn);
203 if (tmpfile.createNewFile() == false) {
205 tmpfile.createNewFile();
208 InputStream in = am.open (fn2);
209 FileOutputStream out = new FileOutputStream (tmpfile);
210 byte[] buffer = new byte[1024 * 512];
211 while (in.read(buffer, 0, 1024 * 512) != -1) {
217 String name = analyzer.getTtfFontName (tmpfile.getAbsolutePath());
220 name = mungeFontName (name);
221 all_fonts.put (name, t);
222 // LOG ("asset font \"%s\" %s", name, fn);
223 } catch (Exception e) {
224 if (tmpfile != null) tmpfile.delete();
225 LOG ("error: %s", e.toString());
231 // Parses X Logical Font Descriptions, and a few standard X font names.
232 // Returns [ String name, Float size, Typeface ]
233 private Object[] parseXLFD (String name) {
235 boolean bold = false;
236 boolean italic = false;
237 boolean fixed = false;
238 boolean serif = false;
240 if (name.equals("6x10")) { size = 8; fixed = true; }
241 else if (name.equals("6x10bold")) { size = 8; fixed = true; bold = true; }
242 else if (name.equals("fixed")) { size = 12; fixed = true; }
243 else if (name.equals("9x15")) { size = 12; fixed = true; }
244 else if (name.equals("9x15bold")) { size = 12; fixed = true; bold = true; }
245 else if (name.equals("vga")) { size = 12; fixed = true; }
246 else if (name.equals("console")) { size = 12; fixed = true; }
247 else if (name.equals("gallant")) { size = 12; fixed = true; }
249 String[] tokens = name.split("-"); // XLFD
250 int L = tokens.length;
252 String foundry = (i < L ? tokens[i++] : "");
253 String family = (i < L ? tokens[i++] : "");
254 String weight = (i < L ? tokens[i++] : "");
255 String slant = (i < L ? tokens[i++] : "");
256 String setwidth = (i < L ? tokens[i++] : "");
257 String adstyle = (i < L ? tokens[i++] : "");
258 String pxsize = (i < L ? tokens[i++] : "");
259 String ptsize = (i < L ? tokens[i++] : "");
260 String resx = (i < L ? tokens[i++] : "");
261 String resy = (i < L ? tokens[i++] : "");
262 String spacing = (i < L ? tokens[i++] : "");
263 String avgw = (i < L ? tokens[i++] : "");
264 String charset = (i < L ? tokens[i++] : "");
265 String registry = (i < L ? tokens[i++] : "");
267 if (spacing.equals("m") ||
268 family.equals("fixed") ||
269 family.equals("courier") ||
270 family.equals("console") ||
271 family.equals("lucidatypewriter")) {
273 } else if (family.equals("times") ||
274 family.equals("georgia")) {
278 if (weight.equals("bold") || weight.equals("demibold")) {
282 if (slant.equals("i") || slant.equals("o")) {
286 // -*-courier-bold-r-*-*-14-*-*-*-*-*-*-* 14 px
287 // -*-courier-bold-r-*-*-*-140-*-*-m-*-*-* 14 pt
288 // -*-courier-bold-r-*-*-140-* 14 pt, via wildcard
289 // -*-courier-bold-r-*-140-* 14 pt, not handled
290 // -*-courier-bold-r-*-*-14-180-*-*-*-*-*-* error
292 if (!ptsize.equals("") && !ptsize.equals("*")) {
293 // It was in the ptsize field, so that's definitely what it is.
294 size = Float.valueOf(ptsize) / 10.0f;
295 } else if (!pxsize.equals("") && !pxsize.equals("*")) {
296 size = Float.valueOf(pxsize);
297 // If it's a fully qualified XLFD, then this really is the pxsize.
298 // Otherwise, this is probably point size with a multi-field wildcard.
299 if (registry.equals("")) // not a fully qualified XLFD
304 if (name.equals("random")) {
305 Random r = new Random();
306 serif = r.nextBoolean(); // Not much to randomize here...
307 fixed = (r.nextInt(8) == 0);
311 ? (serif ? "serif-monospace" : "monospace")
312 : (serif ? "serif" : "sans-serif"));
314 Typeface font = Typeface.create (name,
315 (bold && italic ? Typeface.BOLD_ITALIC :
316 bold ? Typeface.BOLD :
317 italic ? Typeface.ITALIC :
320 Object ret[] = { name, new Float(size), font };
325 // Parses "Native Font Name One 12, Native Font Name Two 14".
326 // Returns [ String name, Float size, Typeface ]
327 private Object[] parseNativeFont (String names) {
328 for (String name : names.split(",")) {
331 if (name.equals("")) continue;
332 int spc = name.lastIndexOf(" ");
334 size = Float.valueOf (name.substring (spc + 1));
335 name = name.substring (0, spc);
340 Object font = all_fonts.get (name);
341 if (font instanceof String)
342 font = Typeface.create (name, Typeface.NORMAL);
345 Object ret[] = { name, size, (Typeface) font };
354 // Returns [ Paint paint, String name, Float size, ascent, descent ]
355 public Object[] loadFont(String name) {
358 if (name.equals("")) return null;
360 if (name.contains(" ")) {
361 pair = parseNativeFont (name);
363 pair = parseXLFD (name);
366 if (pair == null) return null;
368 String name2 = (String) pair[0];
369 float size = (Float) pair[1];
370 Typeface font = (Typeface) pair[2];
374 name2 += (font.isBold() && font.isItalic() ? " bold italic" :
375 font.isBold() ? " bold" :
376 font.isItalic() ? " italic" :
378 Paint paint = new Paint();
379 paint.setTypeface (font);
380 paint.setTextSize (size);
381 paint.setColor (Color.argb (0xFF, 0xFF, 0xFF, 0xFF));
383 LOG ("load font \"%s\" = \"%s %.1f\"", name, name2, size);
385 FontMetrics fm = paint.getFontMetrics();
386 Object ret[] = { paint, name2, new Float(size),
387 new Float(-fm.ascent), new Float(fm.descent) };
392 /* Returns a byte[] array containing XCharStruct with an optional
393 bitmap appended to it.
394 lbearing, rbearing, width, ascent, descent: 2 bytes each.
395 Followed by a WxH pixmap, 32 bits per pixel.
397 public ByteBuffer renderText (Paint paint, String text, boolean render_p) {
404 /* Font metric terminology, as used by X11:
406 "lbearing" is the distance from the logical origin to the leftmost
407 pixel. If a character's ink extends to the left of the origin, it is
410 "rbearing" is the distance from the logical origin to the rightmost
413 "descent" is the distance from the logical origin to the bottommost
414 pixel. For characters with descenders, it is positive. For
415 superscripts, it is negative.
417 "ascent" is the distance from the logical origin to the topmost pixel.
418 It is the number of pixels above the baseline.
420 "width" is the distance from the logical origin to the position where
421 the logical origin of the next character should be placed.
423 If "rbearing" is greater than "width", then this character overlaps the
424 following character. If smaller, then there is trailing blank space.
426 The bbox coordinates returned by getTextBounds grow down and right:
427 for a character with ink both above and below the baseline, top is
428 negative and bottom is positive.
430 FontMetrics fm = paint.getFontMetrics();
431 Rect bbox = new Rect();
432 paint.getTextBounds (text, 0, text.length(), bbox);
434 /* The bbox returned by getTextBounds measures from the logical origin
435 with right and down being positive. This means most characters have
436 a negative top, and characters with descenders have a positive bottom.
438 int lbearing = bbox.left;
439 int rbearing = bbox.right;
440 int ascent = -bbox.top;
441 int descent = bbox.bottom;
442 int width = (int) paint.measureText (text);
444 int w = rbearing - lbearing;
445 int h = ascent + descent;
446 int size = 5 * 2 + (render_p ? w * h * 4 : 0);
448 ByteBuffer bits = ByteBuffer.allocateDirect (size);
450 bits.put ((byte) ((lbearing >> 8) & 0xFF));
451 bits.put ((byte) ( lbearing & 0xFF));
452 bits.put ((byte) ((rbearing >> 8) & 0xFF));
453 bits.put ((byte) ( rbearing & 0xFF));
454 bits.put ((byte) ((width >> 8) & 0xFF));
455 bits.put ((byte) ( width & 0xFF));
456 bits.put ((byte) ((ascent >> 8) & 0xFF));
457 bits.put ((byte) ( ascent & 0xFF));
458 bits.put ((byte) ((descent >> 8) & 0xFF));
459 bits.put ((byte) ( descent & 0xFF));
461 if (render_p && w > 0 && h > 0) {
462 Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
463 Canvas canvas = new Canvas (bitmap);
464 canvas.drawText (text, -lbearing, ascent, paint);
465 bitmap.copyPixelsToBuffer (bits);
473 /* Returns the contents of the URL.
474 Loads the URL in a background thread: if the URL has not yet loaded,
475 this will return null. Once the URL has completely loaded, the full
476 contents will be returned. Calling this again after that starts the
479 private String loading_url = null;
480 private ByteBuffer loaded_url_body = null;
482 public synchronized ByteBuffer loadURL (String url) {
484 if (loaded_url_body != null) { // Thread finished
486 // LOG ("textclient finished %s", loading_url);
488 ByteBuffer bb = loaded_url_body;
490 loaded_url_body = null;
493 } else if (loading_url != null) { // Waiting on thread
494 // LOG ("textclient waiting...");
497 } else { // Launch thread
500 LOG ("textclient launching %s...", url);
502 new Thread (new Runnable() {
507 ByteBuffer body = ByteBuffer.allocateDirect (size);
510 URL u = new URL (loading_url);
511 // LOG ("textclient thread loading: %s", u.toString());
512 InputStream s = u.openStream();
513 byte buf[] = new byte[10240];
515 int n = s.read (buf);
517 // LOG ("textclient thread read %d", n);
518 if (count + n + 1 >= size) {
519 int size2 = (int) (size * 1.2 + size0);
520 // LOG ("textclient thread expand %d -> %d", size, size2);
521 ByteBuffer body2 = ByteBuffer.allocateDirect (size2);
524 body2.position (count);
528 body.put (buf, 0, n);
531 } catch (Exception e) {
532 LOG ("load URL error: %s", e.toString());
534 body.put (e.toString().getBytes());
538 // LOG ("textclient thread finished %s (%d)", loading_url, size);
539 loaded_url_body = body;
548 private ByteBuffer convertBitmap (String name, Bitmap bitmap,
549 int target_width, int target_height,
552 if (bitmap == null) return null;
556 int width = bitmap.getWidth();
557 int height = bitmap.getHeight();
559 LOG ("read image %s: %d x %d", name, width, height);
561 // First rotate the image as per EXIF.
565 switch (exif.getAttributeInt (ExifInterface.TAG_ORIENTATION,
566 ExifInterface.ORIENTATION_NORMAL)) {
567 case ExifInterface.ORIENTATION_ROTATE_90: deg = 90; break;
568 case ExifInterface.ORIENTATION_ROTATE_180: deg = 180; break;
569 case ExifInterface.ORIENTATION_ROTATE_270: deg = 270; break;
572 LOG ("%s: EXIF rotate %d", name, deg);
573 Matrix matrix = new Matrix();
574 matrix.preRotate (deg);
575 bitmap = Bitmap.createBitmap (bitmap, 0, 0, width, height,
577 width = bitmap.getWidth();
578 height = bitmap.getHeight();
582 // If the caller requested that we rotate the image to best fit the
583 // screen, rotate it again. (Could combine this with the above and
584 // avoid copying the image again, but I'm lazy.)
587 (width > height) != (target_width > target_height)) {
588 LOG ("%s: rotated to fit screen", name);
589 Matrix matrix = new Matrix();
590 matrix.preRotate (90);
591 bitmap = Bitmap.createBitmap (bitmap, 0, 0, width, height,
593 width = bitmap.getWidth();
594 height = bitmap.getHeight();
597 // Resize the image to be not larger than the screen, potentially
598 // copying it for the third time.
599 // Actually, always scale it, scaling up if necessary.
601 // if (width > target_width || height > target_height)
603 float r1 = target_width / (float) width;
604 float r2 = target_height / (float) height;
605 float r = (r1 > r2 ? r2 : r1);
606 LOG ("%s: resize %.1f: %d x %d => %d x %d", name,
607 r, width, height, (int) (width * r), (int) (height * r));
608 width = (int) (width * r);
609 height = (int) (height * r);
610 bitmap = Bitmap.createScaledBitmap (bitmap, width, height, true);
611 width = bitmap.getWidth();
612 height = bitmap.getHeight();
615 // Now convert it to a ByteBuffer in the form expected by the C caller.
617 byte[] nameb = name.getBytes("UTF-8");
618 int size = bitmap.getByteCount() + 4 + nameb.length + 1;
620 ByteBuffer bits = ByteBuffer.allocateDirect (size);
622 bits.put ((byte) ((width >> 8) & 0xFF));
623 bits.put ((byte) ( width & 0xFF));
624 bits.put ((byte) ((height >> 8) & 0xFF));
625 bits.put ((byte) ( height & 0xFF));
629 // The fourth of five copies. Good thing these are supercomputers.
630 bitmap.copyPixelsToBuffer (bits);
634 } catch (Exception e) {
635 LOG ("image %s unreadable: %s", name, e.toString());
642 public ByteBuffer loadRandomImage (int target_width, int target_height,
646 int max_size = 0x7FFF;
648 ArrayList<String> imgs = new ArrayList<String>();
650 ContentResolver cr = app.getContentResolver();
651 String[] cols = { MediaColumns.DATA,
652 MediaColumns.MIME_TYPE,
654 MediaColumns.HEIGHT };
656 android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI,
657 android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI };
659 for (int i = 0; i < uris.length; i++) {
660 Cursor cursor = cr.query (uris[i], cols, null, null, null);
662 int path_col = cursor.getColumnIndexOrThrow (cols[j++]);
663 int type_col = cursor.getColumnIndexOrThrow (cols[j++]);
664 int width_col = cursor.getColumnIndexOrThrow (cols[j++]);
665 int height_col = cursor.getColumnIndexOrThrow (cols[j++]);
666 while (cursor.moveToNext()) {
667 String path = cursor.getString(path_col);
668 String type = cursor.getString(type_col);
669 int w = Integer.parseInt (cursor.getString(width_col));
670 int h = Integer.parseInt (cursor.getString(height_col));
671 if (type.startsWith("image/") &&
672 w > min_size && h > min_size &&
673 w < max_size && h < max_size) {
682 int count = imgs.size();
688 int i = new Random().nextInt (count);
689 which = imgs.get (i);
690 LOG ("picked image %d of %d: %s", i, count, which);
692 Uri uri = Uri.fromFile (new File (which));
693 String name = uri.getLastPathSegment();
694 Bitmap bitmap = null;
695 ExifInterface exif = null;
698 bitmap = MediaStore.Images.Media.getBitmap (cr, uri);
699 } catch (Exception e) {
700 LOG ("image %s unloadable: %s", which, e.toString());
705 exif = new ExifInterface (uri.getPath()); // If it fails, who cares
706 } catch (Exception e) {
709 ByteBuffer bits = convertBitmap (name, bitmap,
710 target_width, target_height,
717 public ByteBuffer getScreenshot (int target_width, int target_height,
719 return convertBitmap ("Screenshot", screenshot,
720 target_width, target_height,
725 // Sadly duplicated from jwxyz.h (and thence X.h and keysymdef.h)
727 private static final int ShiftMask = (1<<0);
728 private static final int LockMask = (1<<1);
729 private static final int ControlMask = (1<<2);
730 private static final int Mod1Mask = (1<<3);
731 private static final int Mod2Mask = (1<<4);
732 private static final int Mod3Mask = (1<<5);
733 private static final int Mod4Mask = (1<<6);
734 private static final int Mod5Mask = (1<<7);
735 private static final int Button1Mask = (1<<8);
736 private static final int Button2Mask = (1<<9);
737 private static final int Button3Mask = (1<<10);
738 private static final int Button4Mask = (1<<11);
739 private static final int Button5Mask = (1<<12);
741 private static final int XK_Shift_L = 0xFFE1;
742 private static final int XK_Shift_R = 0xFFE2;
743 private static final int XK_Control_L = 0xFFE3;
744 private static final int XK_Control_R = 0xFFE4;
745 private static final int XK_Caps_Lock = 0xFFE5;
746 private static final int XK_Shift_Lock = 0xFFE6;
747 private static final int XK_Meta_L = 0xFFE7;
748 private static final int XK_Meta_R = 0xFFE8;
749 private static final int XK_Alt_L = 0xFFE9;
750 private static final int XK_Alt_R = 0xFFEA;
751 private static final int XK_Super_L = 0xFFEB;
752 private static final int XK_Super_R = 0xFFEC;
753 private static final int XK_Hyper_L = 0xFFED;
754 private static final int XK_Hyper_R = 0xFFEE;
756 private static final int XK_Home = 0xFF50;
757 private static final int XK_Left = 0xFF51;
758 private static final int XK_Up = 0xFF52;
759 private static final int XK_Right = 0xFF53;
760 private static final int XK_Down = 0xFF54;
761 private static final int XK_Prior = 0xFF55;
762 private static final int XK_Page_Up = 0xFF55;
763 private static final int XK_Next = 0xFF56;
764 private static final int XK_Page_Down = 0xFF56;
765 private static final int XK_End = 0xFF57;
766 private static final int XK_Begin = 0xFF58;
768 private static final int XK_F1 = 0xFFBE;
769 private static final int XK_F2 = 0xFFBF;
770 private static final int XK_F3 = 0xFFC0;
771 private static final int XK_F4 = 0xFFC1;
772 private static final int XK_F5 = 0xFFC2;
773 private static final int XK_F6 = 0xFFC3;
774 private static final int XK_F7 = 0xFFC4;
775 private static final int XK_F8 = 0xFFC5;
776 private static final int XK_F9 = 0xFFC6;
777 private static final int XK_F10 = 0xFFC7;
778 private static final int XK_F11 = 0xFFC8;
779 private static final int XK_F12 = 0xFFC9;
781 public void sendKeyEvent (KeyEvent event) {
782 int uc = event.getUnicodeChar();
783 int jcode = event.getKeyCode();
784 int jmods = event.getModifiers();
789 case KeyEvent.KEYCODE_SHIFT_LEFT: xcode = XK_Shift_L; break;
790 case KeyEvent.KEYCODE_SHIFT_RIGHT: xcode = XK_Shift_R; break;
791 case KeyEvent.KEYCODE_CTRL_LEFT: xcode = XK_Control_L; break;
792 case KeyEvent.KEYCODE_CTRL_RIGHT: xcode = XK_Control_R; break;
793 case KeyEvent.KEYCODE_CAPS_LOCK: xcode = XK_Caps_Lock; break;
794 case KeyEvent.KEYCODE_META_LEFT: xcode = XK_Meta_L; break;
795 case KeyEvent.KEYCODE_META_RIGHT: xcode = XK_Meta_R; break;
796 case KeyEvent.KEYCODE_ALT_LEFT: xcode = XK_Alt_L; break;
797 case KeyEvent.KEYCODE_ALT_RIGHT: xcode = XK_Alt_R; break;
799 case KeyEvent.KEYCODE_HOME: xcode = XK_Home; break;
800 case KeyEvent.KEYCODE_DPAD_LEFT: xcode = XK_Left; break;
801 case KeyEvent.KEYCODE_DPAD_UP: xcode = XK_Up; break;
802 case KeyEvent.KEYCODE_DPAD_RIGHT: xcode = XK_Right; break;
803 case KeyEvent.KEYCODE_DPAD_DOWN: xcode = XK_Down; break;
804 //case KeyEvent.KEYCODE_NAVIGATE_PREVIOUS: xcode = XK_Prior; break;
805 case KeyEvent.KEYCODE_PAGE_UP: xcode = XK_Page_Up; break;
806 //case KeyEvent.KEYCODE_NAVIGATE_NEXT: xcode = XK_Next; break;
807 case KeyEvent.KEYCODE_PAGE_DOWN: xcode = XK_Page_Down; break;
808 case KeyEvent.KEYCODE_MOVE_END: xcode = XK_End; break;
809 case KeyEvent.KEYCODE_MOVE_HOME: xcode = XK_Begin; break;
811 case KeyEvent.KEYCODE_F1: xcode = XK_F1; break;
812 case KeyEvent.KEYCODE_F2: xcode = XK_F2; break;
813 case KeyEvent.KEYCODE_F3: xcode = XK_F3; break;
814 case KeyEvent.KEYCODE_F4: xcode = XK_F4; break;
815 case KeyEvent.KEYCODE_F5: xcode = XK_F5; break;
816 case KeyEvent.KEYCODE_F6: xcode = XK_F6; break;
817 case KeyEvent.KEYCODE_F7: xcode = XK_F7; break;
818 case KeyEvent.KEYCODE_F8: xcode = XK_F8; break;
819 case KeyEvent.KEYCODE_F9: xcode = XK_F9; break;
820 case KeyEvent.KEYCODE_F10: xcode = XK_F10; break;
821 case KeyEvent.KEYCODE_F11: xcode = XK_F11; break;
822 case KeyEvent.KEYCODE_F12: xcode = XK_F12; break;
823 default: xcode = uc; break;
826 if (0 != (jmods & KeyEvent.META_SHIFT_ON)) xmods |= ShiftMask;
827 if (0 != (jmods & KeyEvent.META_CAPS_LOCK_ON)) xmods |= LockMask;
828 if (0 != (jmods & KeyEvent.META_CTRL_MASK)) xmods |= ControlMask;
829 if (0 != (jmods & KeyEvent.META_ALT_MASK)) xmods |= Mod1Mask;
830 if (0 != (jmods & KeyEvent.META_META_ON)) xmods |= Mod1Mask;
831 if (0 != (jmods & KeyEvent.META_SYM_ON)) xmods |= Mod2Mask;
832 if (0 != (jmods & KeyEvent.META_FUNCTION_ON)) xmods |= Mod3Mask;
834 /* If you touch and release Shift, you get no events.
835 If you type Shift-A, you get Shift down, A down, A up, Shift up.
836 So let's just ignore all lone modifier key events.
838 if (xcode >= XK_Shift_L && xcode <= XK_Hyper_R)
841 boolean down_p = event.getAction() == KeyEvent.ACTION_DOWN;
842 sendKeyEvent (down_p, xcode, xmods);