package abbot.tester; import java.lang.reflect.Field; import java.awt.*; import java.awt.Robot; import java.awt.event.*; import java.util.*; import java.io.*; import javax.swing.*; import javax.swing.text.*; import abbot.Log; import abbot.Platform; /** Provides read/write of locale-specific mappings for virtual keycode-based KeyStrokes to characters and vice versa. <p> If your locale's map is not present in src/abbot/tester/keymaps, please run this class's {@link #main(String[])} method to generate them and <a href="http://sourceforge.net/tracker/?group_id=50939&atid=461492">submit them to the project</a> for inclusion. <p> Variations among locales and OSes are expected; if a map for a locale+OS is not found, the system falls back to the locale map. */ public class MapGenerator extends KeyStrokeMap { private static boolean setModifiers(Robot robot, int mask, boolean press) { try { if ((mask & KeyEvent.SHIFT_MASK) != 0) { if(press) robot.keyPress(KeyEvent.VK_SHIFT); else robot.keyRelease(KeyEvent.VK_SHIFT); } if ((mask & KeyEvent.CTRL_MASK) != 0) { if(press) robot.keyPress(KeyEvent.VK_CONTROL); else robot.keyRelease(KeyEvent.VK_CONTROL); } if ((mask & KeyEvent.ALT_MASK) != 0) { if (press) robot.keyPress(KeyEvent.VK_ALT); else robot.keyRelease(KeyEvent.VK_ALT); } if ((mask & KeyEvent.META_MASK) != 0) { if (press) robot.keyPress(KeyEvent.VK_META); else robot.keyRelease(KeyEvent.VK_META); } if ((mask & KeyEvent.ALT_GRAPH_MASK) != 0) { if (press) robot.keyPress(KeyEvent.VK_ALT_GRAPH); else robot.keyRelease(KeyEvent.VK_ALT_GRAPH); } return true; } catch(IllegalArgumentException e) { // ignore these } catch(Exception e) { Log.warn(e); } return false; } private static class KeyWatcher extends KeyAdapter { public char keyChar; public boolean keyTyped; public boolean keyPressed; public String codeName; public void keyPressed(KeyEvent e) { keyPressed = true; // For debug only; activating this stuff tends to interfere with // key capture /* Document d = ((JTextComponent)e.getComponent()).getDocument(); try { String insert = codeName != null ? insert = "\n" + codeName + "=" : ""; d.insertString(d.getLength(), insert, null); } catch(BadLocationException ble) { } */ } public void keyTyped(KeyEvent e) { keyChar = e.getKeyChar(); keyTyped = true; // For debug only; activating this stuff tends to interfere with // key capture /* Document d = ((JTextComponent)e.getComponent()).getDocument(); char[] data = { keyChar }; try { String insert = new String(data) + " (" + String.valueOf((int)keyChar) + ")"; d.insertString(d.getLength(), insert, null); } catch(BadLocationException ble) { } */ codeName=null; } } private static KeyWatcher watcher = null; private static final int UNTYPED = -1; private static final int UNDEFINED = -2; private static final int ILLEGAL = -3; private static final int SYSTEM = -4; private static final int ERROR = -5; private static int generateKey(final Window w, final Component c, final Robot robot, Point p, String name, int code, final boolean refocus) { if (watcher == null) { watcher = new KeyWatcher(); c.addKeyListener(watcher); } try { robot.waitForIdle(); if (refocus) { SwingUtilities.invokeAndWait(new Runnable() { public void run() { w.show(); w.toFront(); c.requestFocus(); if (Platform.isWindows() || Platform.isMacintosh()) { robot.mouseMove(w.getX() + w.getWidth()/2, w.getY() + w.getHeight()/2); robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK); } } }); } robot.mouseMove(p.x, p.y); robot.waitForIdle(); try { watcher.codeName = name; watcher.keyTyped = watcher.keyPressed = false; robot.keyPress(code); robot.keyRelease(code); long start = System.currentTimeMillis(); while(!watcher.keyPressed || !watcher.keyTyped) { if (System.currentTimeMillis() - start > 500) break; robot.waitForIdle(); } if(!watcher.keyPressed) { // alt-tab, alt-f4 and the like which get eaten by the OS return SYSTEM; } else if(!watcher.keyTyped) // keys which result in KEY_TYPED event return UNTYPED; else if (watcher.keyChar == KeyEvent.CHAR_UNDEFINED) // usually the same as UNTYPED, but just in case return UNDEFINED; else return watcher.keyChar; } catch(IllegalArgumentException e) { // not supported on this system return ILLEGAL; } } catch(Exception e) { // usually a core library bug Log.warn(e); return ERROR; } } private static boolean isFunctionKey(String name) { if (name.startsWith("VK_F")) { try { Integer.parseInt(name.substring(4)); return true; } catch(NumberFormatException e) { } } return false; } private static final Comparator FIELD_COMPARATOR = new Comparator() { public int compare(Object o1, Object o2) { try { String n1 = ((Field)o1).getName(); String n2 = ((Field)o2).getName(); return n1.compareTo(n2); } catch(Exception e) { return 0; } } }; // From a VK_ code + modifiers, produce a simluated KEY_TYPED // From a keychar, determine the necessary VK_ code + modifiers private static void generateKeyStrokeMap(Window w, JTextComponent c) { // TODO: invoke modifiers for multi-byte input sequences? // Skip known modifiers and locking keys Collection skip = Arrays.asList(new String[] { "VK_UNDEFINED", // modifiers "VK_SHIFT", "VK_CONTROL", "VK_META", "VK_ALT", "VK_ALT_GRAPH", // special-function keys "VK_CAPS_LOCK", "VK_NUM_LOCK", "VK_SCROLL_LOCK", // Misc other function keys "VK_KANA", "VK_KANJI", "VK_ALPHANUMERIC", "VK_KATAKANA", "VK_HIRAGANA", "VK_FULL_WIDTH", "VK_HALF_WIDTH", "VK_ROMAN_CHARACTERS", "VK_ALL_CANDIDATES", "VK_PREVIOUS_CANDIDATE", "VK_CODE_INPUT", "VK_JAPANESE_KATAKANA", "VK_JAPANESE_HIRAGANA", "VK_JAPANESE_ROMAN", "VK_KANA_LOCK", "VK_INPUT_METHOD_ON_OFF", }); System.out.println("Generating keystroke map"); try { Robot robot = new Robot(); // Make sure the window is ready for input if (!RobotVerifier.verify(robot)) { System.err.println("Robot non-functional, can't generate map"); System.exit(1); } robot.delay(500); Field[] fields = KeyEvent.class.getDeclaredFields(); Set codes = new TreeSet(FIELD_COMPARATOR); for(int i=0;i < fields.length;i++) { String name = fields[i].getName(); if(name.startsWith("VK_") && !skip.contains(name) && !name.startsWith("VK_DEAD_") && !isFunctionKey(name)) { codes.add(fields[i]); } } System.out.println("Total VK_ fields read: " + codes.size()); Point p = c.getLocationOnScreen(); p.x += c.getWidth()/2; p.y += c.getHeight()/2; // for now, only do reasonable modifiers; add more if the need // arises int[] modifierCombos = { 0, KeyEvent.SHIFT_MASK, KeyEvent.CTRL_MASK, KeyEvent.META_MASK, KeyEvent.ALT_MASK, KeyEvent.ALT_GRAPH_MASK, }; String[] MODIFIERS = { "none", "shift", "control", "meta", "alt", "alt graph", }; // These modifiers might trigger window manager functions int needRefocus = KeyEvent.META_MASK|KeyEvent.ALT_MASK; Map[] maps = new Map[modifierCombos.length]; for(int m=0;m < modifierCombos.length;m++) { Map map = new TreeMap(FIELD_COMPARATOR); int mask = modifierCombos[m]; if(!setModifiers(robot, mask, true)) { System.out.println("Modifier " + MODIFIERS[m] + " is not currently valid"); continue; } System.out.println("Generating keys with mask=" + MODIFIERS[m]); Iterator iter = codes.iterator(); // Always try to fix the focus; who knows what keys have // been mapped to the WM boolean focus = true; while(iter.hasNext()) { Field f = (Field)iter.next(); int code = f.getInt(null); //System.out.println(f.getName() + "."); System.out.print("."); int value = generateKey(w, c, robot, p, f.getName(), code, focus || (mask & needRefocus) != 0); map.put(f, new Integer(value)); } setModifiers(robot, modifierCombos[m], false); System.out.println(""); maps[m] = map; } Properties props = new Properties(); Iterator iter = maps[0].keySet().iterator(); while (iter.hasNext()) { Field key = (Field)iter.next(); for(int m=0;m < modifierCombos.length;m++) { Map map = maps[m]; if (map == null) continue; String name = key.getName().substring(3); name += "." + Integer.toHexString(modifierCombos[m]); Integer v = (Integer)map.get(key); int value = v.intValue(); String hex; switch(value) { case UNTYPED: hex = "untyped"; break; case UNDEFINED: hex = "undefined"; break; case ILLEGAL: hex = "illegal"; break; case SYSTEM: hex = "system"; break; case ERROR: hex = "error"; break; default: hex = Integer.toHexString(value); break; } props.setProperty(name, hex); } } String[] names = getMapNames(); String[] desc = getMapDescriptions(); for (int i=0;i < names.length;i++) { String fn = getFilename(names[i]); System.out.println("Saving " + names[i] + " as " + fn); FileOutputStream fos = new FileOutputStream(fn); props.store(fos, "Key mappings for " + desc[i]); } } catch(AWTException e) { System.err.println("Robot not available, can't generate map"); } catch(Exception e) { System.err.println("Error: " + e); } } /** Run this to generate the full set of mappings for a given locale. */ public static void main(String[] args) { String language = System.getProperty("abbot.locale.language"); if (language != null) { String country = System.getProperty("abbot.locale.country", ""); String variant = System.getProperty("abbot.locale.variant", ""); Locale.setDefault(new Locale(language, country, variant)); } final JFrame frame = new JFrame("KeyStroke mapping generator"); final JTextArea text = new JTextArea(); // Remove all action mappings; we want to receive *all* keystrokes text.setInputMap(JTextArea.WHEN_FOCUSED, new InputMap()); frame.getContentPane().add(new JScrollPane(text)); frame.setLocation(100, 100); frame.setSize(250, 90); frame.addWindowListener(new WindowAdapter() { public void windowClosing(final WindowEvent e) { SwingUtilities.invokeLater(new Runnable() { public void run() { e.getWindow().show(); } }); } }); frame.show(); if (Platform.isOSX()) { //NOT Supported in Mac Java5+ // // avoid exit on cmd-Q // com.apple.mrj.MRJApplicationUtils.registerQuitHandler(new com.apple.mrj.MRJQuitHandler() { // public void handleQuit() { } // }); } SwingUtilities.invokeLater(new Runnable() { public void run() { new Thread("keymap generator") { public void run() { generateKeyStrokeMap(frame, text); System.exit(0); }; }.start(); } }); } }