package jackpal.androidterm.emulatorview; import jackpal.androidterm.emulatorview.compat.AndroidCompat; import jackpal.androidterm.emulatorview.compat.KeyCharacterMapCompat; import java.io.IOException; import java.util.Map; import java.util.HashMap; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import static jackpal.androidterm.emulatorview.compat.KeycodeConstants.*; /** * An ASCII key listener. Supports control characters and escape. Keeps track of * the current state of the alt, shift, fn, and control keys. * */ class TermKeyListener { private final static String TAG = "TermKeyListener"; private static final boolean LOG_MISC = false; private static final boolean LOG_KEYS = false; private static final boolean LOG_COMBINING_ACCENT = false; /** Disabled for now because it interferes with ALT processing on phones with physical keyboards. */ private final static boolean SUPPORT_8_BIT_META = false; private static final int KEYMOD_ALT = 0x80000000; private static final int KEYMOD_CTRL = 0x40000000; private static final int KEYMOD_SHIFT = 0x20000000; /** Means this maps raw scancode */ private static final int KEYMOD_SCAN = 0x10000000; private static Map<Integer, String> mKeyMap; private String[] mKeyCodes = new String[256]; private String[] mAppKeyCodes = new String[256]; private void initKeyCodes() { mKeyMap = new HashMap<Integer, String>(); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;2D"); mKeyMap.put(KEYMOD_ALT | KEYCODE_DPAD_LEFT, "\033[1;3D"); mKeyMap.put(KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;4D"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_DPAD_LEFT, "\033[1;5D"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;6D"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYCODE_DPAD_LEFT, "\033[1;7D"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;8D"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT, "\033[1;2C"); mKeyMap.put(KEYMOD_ALT | KEYCODE_DPAD_RIGHT, "\033[1;3C"); mKeyMap.put(KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT, "\033[1;4C"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_DPAD_RIGHT, "\033[1;5C"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT, "\033[1;6C"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYCODE_DPAD_RIGHT, "\033[1;7C"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT, "\033[1;8C"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_DPAD_UP, "\033[1;2A"); mKeyMap.put(KEYMOD_ALT | KEYCODE_DPAD_UP, "\033[1;3A"); mKeyMap.put(KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_UP, "\033[1;4A"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_DPAD_UP, "\033[1;5A"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_SHIFT | KEYCODE_DPAD_UP, "\033[1;6A"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYCODE_DPAD_UP, "\033[1;7A"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_UP, "\033[1;8A"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_DPAD_DOWN, "\033[1;2B"); mKeyMap.put(KEYMOD_ALT | KEYCODE_DPAD_DOWN, "\033[1;3B"); mKeyMap.put(KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_DOWN, "\033[1;4B"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_DPAD_DOWN, "\033[1;5B"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_SHIFT | KEYCODE_DPAD_DOWN, "\033[1;6B"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYCODE_DPAD_DOWN, "\033[1;7B"); mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_DOWN, "\033[1;8B"); //^[[3~ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_FORWARD_DEL, "\033[3;2~"); mKeyMap.put(KEYMOD_ALT | KEYCODE_FORWARD_DEL, "\033[3;3~"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_FORWARD_DEL, "\033[3;5~"); //^[[2~ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_INSERT, "\033[2;2~"); mKeyMap.put(KEYMOD_ALT | KEYCODE_INSERT, "\033[2;3~"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_INSERT, "\033[2;5~"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_MOVE_HOME, "\033[1;5H"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_MOVE_END, "\033[1;5F"); mKeyMap.put(KEYMOD_ALT | KEYCODE_ENTER, "\033\r"); mKeyMap.put(KEYMOD_CTRL | KEYCODE_ENTER, "\n"); // Duh, so special... mKeyMap.put(KEYMOD_CTRL | KEYCODE_SPACE, "\000"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F1, "\033[1;2P"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F2, "\033[1;2Q"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F3, "\033[1;2R"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F4, "\033[1;2S"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F5, "\033[15;2~"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F6, "\033[17;2~"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F7, "\033[18;2~"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F8, "\033[19;2~"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F9, "\033[20;2~"); mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F10, "\033[21;2~"); mKeyCodes[KEYCODE_DPAD_CENTER] = "\015"; mKeyCodes[KEYCODE_DPAD_UP] = "\033[A"; mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B"; mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C"; mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D"; setFnKeys("vt100"); mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~"; mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break mKeyCodes[KEYCODE_TAB] = "\011"; mKeyCodes[KEYCODE_ENTER] = "\015"; mKeyCodes[KEYCODE_ESCAPE] = "\033"; mKeyCodes[KEYCODE_INSERT] = "\033[2~"; mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~"; // Home/End keys are set by setFnKeys() mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~"; mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~"; mKeyCodes[KEYCODE_DEL]= "\177"; mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP"; mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/"; mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*"; mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-"; mKeyCodes[KEYCODE_NUMPAD_ADD] = "+"; mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015"; mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "="; mKeyCodes[KEYCODE_NUMPAD_COMMA] = ","; /* mKeyCodes[KEYCODE_NUMPAD_DOT] = "."; mKeyCodes[KEYCODE_NUMPAD_0] = "0"; mKeyCodes[KEYCODE_NUMPAD_1] = "1"; mKeyCodes[KEYCODE_NUMPAD_2] = "2"; mKeyCodes[KEYCODE_NUMPAD_3] = "3"; mKeyCodes[KEYCODE_NUMPAD_4] = "4"; mKeyCodes[KEYCODE_NUMPAD_5] = "5"; mKeyCodes[KEYCODE_NUMPAD_6] = "6"; mKeyCodes[KEYCODE_NUMPAD_7] = "7"; mKeyCodes[KEYCODE_NUMPAD_8] = "8"; mKeyCodes[KEYCODE_NUMPAD_9] = "9"; */ // Keypad is used for cursor/func keys mKeyCodes[KEYCODE_NUMPAD_DOT] = mKeyCodes[KEYCODE_FORWARD_DEL]; mKeyCodes[KEYCODE_NUMPAD_0] = mKeyCodes[KEYCODE_INSERT]; mKeyCodes[KEYCODE_NUMPAD_1] = mKeyCodes[KEYCODE_MOVE_END]; mKeyCodes[KEYCODE_NUMPAD_2] = mKeyCodes[KEYCODE_DPAD_DOWN]; mKeyCodes[KEYCODE_NUMPAD_3] = mKeyCodes[KEYCODE_PAGE_DOWN]; mKeyCodes[KEYCODE_NUMPAD_4] = mKeyCodes[KEYCODE_DPAD_LEFT]; mKeyCodes[KEYCODE_NUMPAD_5] = "5"; mKeyCodes[KEYCODE_NUMPAD_6] = mKeyCodes[KEYCODE_DPAD_RIGHT]; mKeyCodes[KEYCODE_NUMPAD_7] = mKeyCodes[KEYCODE_MOVE_HOME]; mKeyCodes[KEYCODE_NUMPAD_8] = mKeyCodes[KEYCODE_DPAD_UP]; mKeyCodes[KEYCODE_NUMPAD_9] = mKeyCodes[KEYCODE_PAGE_UP]; // mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA"; // mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB"; // mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC"; // mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD"; mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo"; mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj"; mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om"; mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok"; mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM"; mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX"; mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On"; mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol"; mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op"; mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq"; mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or"; mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os"; mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot"; mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou"; mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov"; mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow"; mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox"; mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy"; } public void setCursorKeysApplicationMode(boolean val) { if (LOG_MISC) { Log.d(EmulatorDebug.LOG_TAG, "CursorKeysApplicationMode=" + val); } if (val) { mKeyCodes[KEYCODE_NUMPAD_8] = mKeyCodes[KEYCODE_DPAD_UP] = "\033OA"; mKeyCodes[KEYCODE_NUMPAD_2] = mKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB"; mKeyCodes[KEYCODE_NUMPAD_6] = mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC"; mKeyCodes[KEYCODE_NUMPAD_4] = mKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD"; } else { mKeyCodes[KEYCODE_NUMPAD_8] = mKeyCodes[KEYCODE_DPAD_UP] = "\033[A"; mKeyCodes[KEYCODE_NUMPAD_2] = mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B"; mKeyCodes[KEYCODE_NUMPAD_6] = mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C"; mKeyCodes[KEYCODE_NUMPAD_4] = mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D"; } } /** * The state engine for a modifier key. Can be pressed, released, locked, * and so on. * */ private class ModifierKey { private int mState; private static final int UNPRESSED = 0; private static final int PRESSED = 1; private static final int RELEASED = 2; private static final int USED = 3; private static final int LOCKED = 4; /** * Construct a modifier key. UNPRESSED by default. * */ public ModifierKey() { mState = UNPRESSED; } public void onPress() { switch (mState) { case PRESSED: // This is a repeat before use break; case RELEASED: mState = LOCKED; break; case USED: // This is a repeat after use break; case LOCKED: mState = UNPRESSED; break; default: mState = PRESSED; break; } } public void onRelease() { switch (mState) { case USED: mState = UNPRESSED; break; case PRESSED: mState = RELEASED; break; default: // Leave state alone break; } } public void adjustAfterKeypress() { switch (mState) { case PRESSED: mState = USED; break; case RELEASED: mState = UNPRESSED; break; default: // Leave state alone break; } } public boolean isActive() { return mState != UNPRESSED; } public int getUIMode() { switch (mState) { default: case UNPRESSED: return TextRenderer.MODE_OFF; case PRESSED: case RELEASED: case USED: return TextRenderer.MODE_ON; case LOCKED: return TextRenderer.MODE_LOCKED; } } } private ModifierKey mAltKey = new ModifierKey(); private ModifierKey mCapKey = new ModifierKey(); private ModifierKey mControlKey = new ModifierKey(); private ModifierKey mFnKey = new ModifierKey(); private int mCursorMode; private boolean mHardwareControlKey; private TermSession mTermSession; private int mBackKeyCode; private boolean mAltSendsEsc; private int mCombiningAccent; // Map keycodes out of (above) the Unicode code point space. static public final int KEYCODE_OFFSET = 0xA00000; /** * Construct a term key listener. * */ public TermKeyListener(TermSession termSession) { mTermSession = termSession; initKeyCodes(); updateCursorMode(); } public void setBackKeyCharacter(int code) { mBackKeyCode = code; } public void setAltSendsEsc(boolean flag) { mAltSendsEsc = flag; } public void handleHardwareControlKey(boolean down) { mHardwareControlKey = down; } public void onPause() { // Ensure we don't have any left-over modifier state when switching // views. mHardwareControlKey = false; } public void onResume() { // Nothing special. } public void handleControlKey(boolean down) { if (down) { mControlKey.onPress(); } else { mControlKey.onRelease(); } updateCursorMode(); } public void handleFnKey(boolean down) { if (down) { mFnKey.onPress(); } else { mFnKey.onRelease(); } updateCursorMode(); } public void setTermType(String termType) { setFnKeys(termType); } private void setFnKeys(String termType) { // These key assignments taken from the debian squeeze terminfo database. if (termType.equals("xterm")) { mKeyCodes[KEYCODE_NUMPAD_7] = mKeyCodes[KEYCODE_MOVE_HOME] = "\033OH"; mKeyCodes[KEYCODE_NUMPAD_1] = mKeyCodes[KEYCODE_MOVE_END] = "\033OF"; } else { mKeyCodes[KEYCODE_NUMPAD_7] = mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~"; mKeyCodes[KEYCODE_NUMPAD_1] = mKeyCodes[KEYCODE_MOVE_END] = "\033[4~"; } if (termType.equals("vt100")) { mKeyCodes[KEYCODE_F1] = "\033OP"; // VT100 PF1 mKeyCodes[KEYCODE_F2] = "\033OQ"; // VT100 PF2 mKeyCodes[KEYCODE_F3] = "\033OR"; // VT100 PF3 mKeyCodes[KEYCODE_F4] = "\033OS"; // VT100 PF4 // the following keys are in the database, but aren't on a real vt100. mKeyCodes[KEYCODE_F5] = "\033Ot"; mKeyCodes[KEYCODE_F6] = "\033Ou"; mKeyCodes[KEYCODE_F7] = "\033Ov"; mKeyCodes[KEYCODE_F8] = "\033Ol"; mKeyCodes[KEYCODE_F9] = "\033Ow"; mKeyCodes[KEYCODE_F10] = "\033Ox"; // The following keys are not in database. mKeyCodes[KEYCODE_F11] = "\033[23~"; mKeyCodes[KEYCODE_F12] = "\033[24~"; } else if (termType.startsWith("linux")) { mKeyCodes[KEYCODE_F1] = "\033[[A"; mKeyCodes[KEYCODE_F2] = "\033[[B"; mKeyCodes[KEYCODE_F3] = "\033[[C"; mKeyCodes[KEYCODE_F4] = "\033[[D"; mKeyCodes[KEYCODE_F5] = "\033[[E"; mKeyCodes[KEYCODE_F6] = "\033[17~"; mKeyCodes[KEYCODE_F7] = "\033[18~"; mKeyCodes[KEYCODE_F8] = "\033[19~"; mKeyCodes[KEYCODE_F9] = "\033[20~"; mKeyCodes[KEYCODE_F10] = "\033[21~"; mKeyCodes[KEYCODE_F11] = "\033[23~"; mKeyCodes[KEYCODE_F12] = "\033[24~"; } else { // default // screen, screen-256colors, xterm, anything new mKeyCodes[KEYCODE_F1] = "\033OP"; // VT100 PF1 mKeyCodes[KEYCODE_F2] = "\033OQ"; // VT100 PF2 mKeyCodes[KEYCODE_F3] = "\033OR"; // VT100 PF3 mKeyCodes[KEYCODE_F4] = "\033OS"; // VT100 PF4 mKeyCodes[KEYCODE_F5] = "\033[15~"; mKeyCodes[KEYCODE_F6] = "\033[17~"; mKeyCodes[KEYCODE_F7] = "\033[18~"; mKeyCodes[KEYCODE_F8] = "\033[19~"; mKeyCodes[KEYCODE_F9] = "\033[20~"; mKeyCodes[KEYCODE_F10] = "\033[21~"; mKeyCodes[KEYCODE_F11] = "\033[23~"; mKeyCodes[KEYCODE_F12] = "\033[24~"; } } public int mapControlChar(int ch) { return mapControlChar(mHardwareControlKey || mControlKey.isActive(), mFnKey.isActive(), ch); } public int mapControlChar(boolean control, boolean fn, int ch) { int result = ch; if (control) { // Search is the control key. if (result >= 'a' && result <= 'z') { result = (char) (result - 'a' + '\001'); } else if (result >= 'A' && result <= 'Z') { result = (char) (result - 'A' + '\001'); } else if (result == ' ' || result == '2') { result = 0; } else if (result == '[' || result == '3') { result = 27; // ^[ (Esc) } else if (result == '\\' || result == '4') { result = 28; } else if (result == ']' || result == '5') { result = 29; } else if (result == '^' || result == '6') { result = 30; // control-^ } else if (result == '_' || result == '7') { result = 31; } else if (result == '8') { result = 127; // DEL } else if (result == '9') { result = KEYCODE_OFFSET + KEYCODE_F11; } else if (result == '0') { result = KEYCODE_OFFSET + KEYCODE_F12; } } else if (fn) { if (result == 'w' || result == 'W') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; } else if (result == 'a' || result == 'A') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; } else if (result == 's' || result == 'S') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; } else if (result == 'd' || result == 'D') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; } else if (result == 'p' || result == 'P') { result = KEYCODE_OFFSET + KEYCODE_PAGE_UP; } else if (result == 'n' || result == 'N') { result = KEYCODE_OFFSET + KEYCODE_PAGE_DOWN; } else if (result == 't' || result == 'T') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB; } else if (result == 'l' || result == 'L') { result = '|'; } else if (result == 'u' || result == 'U') { result = '_'; } else if (result == 'e' || result == 'E') { result = 27; // ^[ (Esc) } else if (result == '.') { result = 28; // ^\ } else if (result > '0' && result <= '9') { // F1-F9 result = (char)(result + KEYCODE_OFFSET + KEYCODE_F1 - 1); } else if (result == '0') { result = KEYCODE_OFFSET + KEYCODE_F10; } else if (result == 'i' || result == 'I') { result = KEYCODE_OFFSET + KEYCODE_INSERT; } else if (result == 'x' || result == 'X') { result = KEYCODE_OFFSET + KEYCODE_FORWARD_DEL; } else if (result == 'h' || result == 'H') { result = KEYCODE_OFFSET + KEYCODE_MOVE_HOME; } else if (result == 'f' || result == 'F') { result = KEYCODE_OFFSET + KEYCODE_MOVE_END; } } if (result > -1) { mAltKey.adjustAfterKeypress(); mCapKey.adjustAfterKeypress(); mControlKey.adjustAfterKeypress(); mFnKey.adjustAfterKeypress(); updateCursorMode(); } return result; } /** * Handle a keyDown event. * * @param keyCode the keycode of the keyDown event * */ public void keyDown(int keyCode, KeyEvent event, boolean appMode, boolean allowToggle) throws IOException { if (LOG_KEYS) { Log.i(TAG, "keyDown(" + keyCode + "," + event + "," + appMode + "," + allowToggle + ")"); } if (handleKeyCode(keyCode, event, appMode)) { return; } int result = -1; boolean chordedCtrl = false; boolean setHighBit = false; switch (keyCode) { case KeyEvent.KEYCODE_ALT_RIGHT: case KeyEvent.KEYCODE_ALT_LEFT: if (allowToggle) { mAltKey.onPress(); updateCursorMode(); } break; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: if (allowToggle) { mCapKey.onPress(); updateCursorMode(); } break; case KEYCODE_CTRL_LEFT: case KEYCODE_CTRL_RIGHT: // Ignore the control key. return; case KEYCODE_CAPS_LOCK: // Ignore the capslock key. return; case KEYCODE_FUNCTION: // Ignore the function key. return; case KeyEvent.KEYCODE_BACK: result = mBackKeyCode; break; default: { int metaState = event.getMetaState(); chordedCtrl = ((META_CTRL_ON & metaState) != 0); boolean effectiveCaps = allowToggle && (mCapKey.isActive()); boolean effectiveAlt = allowToggle && mAltKey.isActive(); int effectiveMetaState = metaState & (~META_CTRL_MASK); if (effectiveCaps) { effectiveMetaState |= KeyEvent.META_SHIFT_ON; } if (!allowToggle && (effectiveMetaState & META_ALT_ON) != 0) { effectiveAlt = true; } if (effectiveAlt) { if (mAltSendsEsc) { mTermSession.write(new byte[]{0x1b},0,1); effectiveMetaState &= ~KeyEvent.META_ALT_MASK; } else if (SUPPORT_8_BIT_META) { setHighBit = true; effectiveMetaState &= ~KeyEvent.META_ALT_MASK; } else { // Legacy behavior: Pass Alt through to allow composing characters. effectiveMetaState |= KeyEvent.META_ALT_ON; } } // Note: The Hacker keyboard IME key labeled Alt actually sends Meta. if ((metaState & KeyEvent.META_META_ON) != 0) { if (mAltSendsEsc) { mTermSession.write(new byte[]{0x1b},0,1); effectiveMetaState &= ~KeyEvent.META_META_MASK; } else { if (SUPPORT_8_BIT_META) { setHighBit = true; effectiveMetaState &= ~KeyEvent.META_META_MASK; } } } result = event.getUnicodeChar(effectiveMetaState); if ((result & KeyCharacterMap.COMBINING_ACCENT) != 0) { if (LOG_COMBINING_ACCENT) { Log.i(TAG, "Got combining accent " + result); } mCombiningAccent = result & KeyCharacterMap.COMBINING_ACCENT_MASK; return; } if (mCombiningAccent != 0) { int unaccentedChar = result; result = KeyCharacterMap.getDeadChar(mCombiningAccent, unaccentedChar); if (LOG_COMBINING_ACCENT) { Log.i(TAG, "getDeadChar(" + mCombiningAccent + ", " + unaccentedChar + ") -> " + result); } mCombiningAccent = 0; } break; } } boolean effectiveControl = chordedCtrl || mHardwareControlKey || (allowToggle && mControlKey.isActive()); boolean effectiveFn = allowToggle && mFnKey.isActive(); result = mapControlChar(effectiveControl, effectiveFn, result); if (result >= KEYCODE_OFFSET) { handleKeyCode(result - KEYCODE_OFFSET, null, appMode); } else if (result >= 0) { if (setHighBit) { result |= 0x80; } mTermSession.write(result); } } public int getCombiningAccent() { return mCombiningAccent; } public int getCursorMode() { return mCursorMode; } private void updateCursorMode() { mCursorMode = getCursorModeHelper(mCapKey, TextRenderer.MODE_SHIFT_SHIFT) | getCursorModeHelper(mAltKey, TextRenderer.MODE_ALT_SHIFT) | getCursorModeHelper(mControlKey, TextRenderer.MODE_CTRL_SHIFT) | getCursorModeHelper(mFnKey, TextRenderer.MODE_FN_SHIFT); } private static int getCursorModeHelper(ModifierKey key, int shift) { return key.getUIMode() << shift; } static boolean isEventFromToggleDevice(KeyEvent event) { if (AndroidCompat.SDK < 11) { return true; } KeyCharacterMapCompat kcm = KeyCharacterMapCompat.wrap( KeyCharacterMap.load(event.getDeviceId())); return kcm.getModifierBehaviour() == KeyCharacterMapCompat.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED; } public boolean handleKeyCode(int keyCode, KeyEvent event, boolean appMode) throws IOException { String code = null; if (event != null) { int keyMod = 0; // META_CTRL_ON was added only in API 11, so don't use it, // use our own tracking of Ctrl key instead. // (event.getMetaState() & META_CTRL_ON) != 0 if (mHardwareControlKey || mControlKey.isActive()) { keyMod |= KEYMOD_CTRL; } if ((event.getMetaState() & META_ALT_ON) != 0) { keyMod |= KEYMOD_ALT; } if ((event.getMetaState() & META_SHIFT_ON) != 0) { keyMod |= KEYMOD_SHIFT; } // First try to map scancode code = mKeyMap.get(event.getScanCode() | KEYMOD_SCAN | keyMod); if (code == null) { code = mKeyMap.get(keyCode | keyMod); } } if (code == null && keyCode >= 0 && keyCode < mKeyCodes.length) { if (appMode) { code = mAppKeyCodes[keyCode]; } if (code == null) { code = mKeyCodes[keyCode]; } } if (code != null) { if (EmulatorDebug.LOG_CHARACTERS_FLAG) { byte[] bytes = code.getBytes(); Log.d(EmulatorDebug.LOG_TAG, "Out: '" + EmulatorDebug.bytesToString(bytes, 0, bytes.length) + "'"); } mTermSession.write(code); return true; } return false; } /** * Handle a keyUp event. * * @param keyCode the keyCode of the keyUp event */ public void keyUp(int keyCode, KeyEvent event) { boolean allowToggle = isEventFromToggleDevice(event); switch (keyCode) { case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: if (allowToggle) { mAltKey.onRelease(); updateCursorMode(); } break; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: if (allowToggle) { mCapKey.onRelease(); updateCursorMode(); } break; case KEYCODE_CTRL_LEFT: case KEYCODE_CTRL_RIGHT: // ignore control keys. break; default: // Ignore other keyUps break; } } public boolean getAltSendsEsc() { return mAltSendsEsc; } public boolean isAltActive() { return mAltKey.isActive(); } public boolean isCtrlActive() { return mControlKey.isActive(); } }