view src/com/five_ten_sg/connectbot/service/TerminalKeyListener.java @ 0:0ce5cc452d02

initial version
author Carl Byington <carl@five-ten-sg.com>
date Thu, 22 May 2014 10:41:19 -0700
parents
children 7ac846a07ed4
line wrap: on
line source

/*
 * ConnectBot: simple, powerful, open-source SSH client for Android
 * Copyright 2010 Kenny Root, Jeffrey Sharkey
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.five_ten_sg.connectbot.service;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.List;

import com.five_ten_sg.connectbot.R;
import com.five_ten_sg.connectbot.TerminalView;
import com.five_ten_sg.connectbot.bean.SelectionArea;
import com.five_ten_sg.connectbot.util.PreferenceConstants;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Configuration;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.text.ClipboardManager;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import de.mud.terminal.VDUBuffer;
import de.mud.terminal.vt320;

/**
 * @author kenny
 *
 */
@SuppressWarnings("deprecation")  // for ClipboardManager
public class TerminalKeyListener implements OnKeyListener, OnSharedPreferenceChangeListener {
    private static final String TAG = "ConnectBot.OnKeyListener";

    public final static int META_CTRL_ON = 0x01;
    public final static int META_CTRL_LOCK = 0x02;
    public final static int META_ALT_ON = 0x04;
    public final static int META_ALT_LOCK = 0x08;
    public final static int META_SHIFT_ON = 0x10;
    public final static int META_SHIFT_LOCK = 0x20;
    public final static int META_SLASH = 0x40;
    public final static int META_TAB = 0x80;

    // The bit mask of momentary and lock states for each
    public final static int META_CTRL_MASK = META_CTRL_ON | META_CTRL_LOCK;
    public final static int META_ALT_MASK = META_ALT_ON | META_ALT_LOCK;
    public final static int META_SHIFT_MASK = META_SHIFT_ON | META_SHIFT_LOCK;

    // backport constants from api level 11
    public final static int KEYCODE_ESCAPE = 111;
    public final static int HC_META_CTRL_ON = 4096;
    public final static int KEYCODE_PAGE_UP = 92;
    public final static int KEYCODE_PAGE_DOWN = 93;

    // All the transient key codes
    public final static int META_TRANSIENT = META_CTRL_ON | META_ALT_ON
            | META_SHIFT_ON;

    private final TerminalManager manager;
    private final TerminalBridge bridge;
    private final vt320 buffer;

    private String keymode = null;
    private boolean hardKeyboard = false;
    private String customKeyboard = null;

    private int metaState = 0;

    private int mDeadKey = 0;

    // TODO add support for the new API.
    private ClipboardManager clipboard = null;

    private boolean selectingForCopy = false;
    private final SelectionArea selectionArea;

    private String encoding;

    private final SharedPreferences prefs;

    private Toast debugToast = null;
    private Toast metakeyToast = null;

    public TerminalKeyListener(TerminalManager manager,
                               TerminalBridge bridge,
                               vt320 buffer,
                               String encoding) {
        this.manager = manager;
        this.bridge = bridge;
        this.buffer = buffer;
        this.encoding = encoding;
        selectionArea = new SelectionArea();
        prefs = PreferenceManager.getDefaultSharedPreferences(manager);
        prefs.registerOnSharedPreferenceChangeListener(this);
        hardKeyboard = (manager.res.getConfiguration().keyboard
                        == Configuration.KEYBOARD_QWERTY);
        updateKeymode();
        updateCustomKeymap();
    }

    public void sendEscape() {
        buffer.write(0x1b);
    }

    /**
     * Handle onKey() events coming down from a {@link com.five_ten_sg.connectbot.TerminalView} above us.
     * Modify the keys to make more sense to a host then pass it to the vt320.
     */
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        try {
            // skip keys if we aren't connected yet or have been disconnected
            if (bridge.isDisconnected() || bridge.transport == null)
                return false;

            final boolean hardKeyboardHidden = manager.hardKeyboardHidden;

            // Ignore all key-up events except for the special keys
            if (event.getAction() == KeyEvent.ACTION_UP) {
                // There's nothing here for virtual keyboard users.
                if (!hardKeyboard || (hardKeyboard && hardKeyboardHidden))
                    return false;

                // if keycode debugging enabled, log and print the pressed key
                if (prefs.getBoolean(PreferenceConstants.DEBUG_KEYCODES, false)) {
                    String keyCodeString = String.format(": %d", keyCode);
                    String toastText = v.getContext().getString(R.string.keycode_pressed) + keyCodeString;

                    if (debugToast == null)
                        debugToast = Toast.makeText(v.getContext(), toastText, Toast.LENGTH_LONG);
                    else
                        debugToast.setText(toastText);

                    debugToast.show();
                }

                if (fullKeyboard()) {
                    switch (keyCode) {
                        case KeyEvent.KEYCODE_CTRL_LEFT:
                        case KeyEvent.KEYCODE_CTRL_RIGHT:
                            metaKeyUp(META_CTRL_ON);
                            return true;

                        case KeyEvent.KEYCODE_ALT_LEFT:
                        case KeyEvent.KEYCODE_ALT_RIGHT:
                            metaKeyUp(META_ALT_ON);
                            return true;

                        case KeyEvent.KEYCODE_SHIFT_LEFT:
                        case KeyEvent.KEYCODE_SHIFT_RIGHT:
                            metaKeyUp(META_SHIFT_ON);
                            return true;

                        default:
                    }
                }
                else if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) {
                    if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT
                            && (metaState & META_SLASH) != 0) {
                        metaState &= ~(META_SLASH | META_TRANSIENT);
                        buffer.write('/');
                        return true;
                    }
                    else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT
                             && (metaState & META_TAB) != 0) {
                        metaState &= ~(META_TAB | META_TRANSIENT);
                        buffer.write(0x09);
                        return true;
                    }
                }
                else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) {
                    if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
                            && (metaState & META_SLASH) != 0) {
                        metaState &= ~(META_SLASH | META_TRANSIENT);
                        buffer.write('/');
                        return true;
                    }
                    else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
                             && (metaState & META_TAB) != 0) {
                        metaState &= ~(META_TAB | META_TRANSIENT);
                        buffer.write(0x09);
                        return true;
                    }
                }

                return false;
            }

            bridge.resetScrollPosition();

            if (keyCode == KeyEvent.KEYCODE_UNKNOWN &&
                    event.getAction() == KeyEvent.ACTION_MULTIPLE) {
                byte[] input = event.getCharacters().getBytes(encoding);
                buffer.write(input);
                return true;
            }

            int curMetaState = event.getMetaState();
            final int orgMetaState = curMetaState;

            if ((metaState & META_SHIFT_MASK) != 0) {
                curMetaState |= KeyEvent.META_SHIFT_ON;
            }

            if ((metaState & META_ALT_MASK) != 0) {
                curMetaState |= KeyEvent.META_ALT_ON;
            }

            int uchar = event.getUnicodeChar(curMetaState);

            // no hard keyboard?  ALT-k should pass through to below
            if ((orgMetaState & KeyEvent.META_ALT_ON) != 0 &&
                    (!hardKeyboard || hardKeyboardHidden)) {
                uchar = 0;
            }

            if ((uchar & KeyCharacterMap.COMBINING_ACCENT) != 0) {
                mDeadKey = uchar & KeyCharacterMap.COMBINING_ACCENT_MASK;
                return true;
            }

            if (mDeadKey != 0 && uchar != 0) {
                uchar = KeyCharacterMap.getDeadChar(mDeadKey, uchar);
                mDeadKey = 0;
            }

            // handle customized keymaps
            if (customKeymapAction(v, keyCode, event))
                return true;

            if (v != null) {
                //Show up the CharacterPickerDialog when the SYM key is pressed
                if ((isSymKey(keyCode) || uchar == KeyCharacterMap.PICKER_DIALOG_INPUT)) {
                    bridge.showCharPickerDialog();

                    if (metaState == 4) { // reset fn-key state
                        metaState = 0;
                        bridge.redraw();
                    }

                    return true;
                }
                else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
                    //Show up the URL scan dialog when the search key is pressed
                    urlScan(v);
                    return true;
                }
            }

            // otherwise pass through to existing session
            // print normal keys
            if (uchar > 0x00 && keyCode != KeyEvent.KEYCODE_ENTER) {
                metaState &= ~(META_SLASH | META_TAB);
                // Remove shift and alt modifiers
                final int lastMetaState = metaState;
                metaState &= ~(META_SHIFT_ON | META_ALT_ON);

                if (metaState != lastMetaState) {
                    bridge.redraw();
                }

                if ((metaState & META_CTRL_MASK) != 0) {
                    metaState &= ~META_CTRL_ON;
                    bridge.redraw();

                    // If there is no hard keyboard or there is a hard keyboard currently hidden,
                    // CTRL-1 through CTRL-9 will send F1 through F9
                    if ((!hardKeyboard || (hardKeyboard && hardKeyboardHidden))
                            && sendFunctionKey(keyCode))
                        return true;

                    uchar = keyAsControl(uchar);
                }

                // handle pressing f-keys
                if ((hardKeyboard && !hardKeyboardHidden)
                        && (curMetaState & KeyEvent.META_ALT_ON) != 0
                        && (curMetaState & KeyEvent.META_SHIFT_ON) != 0
                        && sendFunctionKey(keyCode))
                    return true;

                if (uchar < 0x80)
                    buffer.write(uchar);
                else
                    // TODO write encoding routine that doesn't allocate each time
                    buffer.write(new String(Character.toChars(uchar))
                                 .getBytes(encoding));

                return true;
            }

            // send ctrl and meta-keys as appropriate
            if (!hardKeyboard || hardKeyboardHidden) {
                int k = event.getUnicodeChar(0);
                int k0 = k;
                boolean sendCtrl = false;
                boolean sendMeta = false;

                if (k != 0) {
                    if ((orgMetaState & HC_META_CTRL_ON) != 0) {
                        k = keyAsControl(k);

                        if (k != k0)
                            sendCtrl = true;

                        // send F1-F10 via CTRL-1 through CTRL-0
                        if (!sendCtrl && sendFunctionKey(keyCode))
                            return true;
                    }
                    else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) {
                        sendMeta = true;
                        buffer.write(0x1b);
                    }

                    if (sendMeta || sendCtrl) {
                        buffer.write(k);
                        return true;
                    }
                }
            }

            // handle meta and f-keys for full hardware keyboard
            if (hardKeyboard && !hardKeyboardHidden && fullKeyboard()) {
                int k = event.getUnicodeChar(orgMetaState & KeyEvent.META_SHIFT_ON);
                int k0 = k;

                if (k != 0) {
                    if ((orgMetaState & HC_META_CTRL_ON) != 0) {
                        k = keyAsControl(k);

                        if (k != k0)
                            buffer.write(k);

                        return true;
                    }
                    else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) {
                        buffer.write(0x1b);
                        buffer.write(k);
                        return true;
                    }
                }

                if (sendFullSpecialKey(keyCode))
                    return true;
            }

            // try handling keymode shortcuts
            if (hardKeyboard && !hardKeyboardHidden &&
                    event.getRepeatCount() == 0) {
                if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) {
                    switch (keyCode) {
                        case KeyEvent.KEYCODE_ALT_RIGHT:
                            metaState |= META_SLASH;
                            return true;

                        case KeyEvent.KEYCODE_SHIFT_RIGHT:
                            metaState |= META_TAB;
                            return true;

                        case KeyEvent.KEYCODE_SHIFT_LEFT:
                            metaPress(META_SHIFT_ON);
                            return true;

                        case KeyEvent.KEYCODE_ALT_LEFT:
                            metaPress(META_ALT_ON);
                            return true;
                    }
                }
                else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) {
                    switch (keyCode) {
                        case KeyEvent.KEYCODE_ALT_LEFT:
                            metaState |= META_SLASH;
                            return true;

                        case KeyEvent.KEYCODE_SHIFT_LEFT:
                            metaState |= META_TAB;
                            return true;

                        case KeyEvent.KEYCODE_SHIFT_RIGHT:
                            metaPress(META_SHIFT_ON);
                            return true;

                        case KeyEvent.KEYCODE_ALT_RIGHT:
                            metaPress(META_ALT_ON);
                            return true;
                    }
                }
                else {
                    switch (keyCode) {
                        case KeyEvent.KEYCODE_ALT_RIGHT:
                        case KeyEvent.KEYCODE_ALT_LEFT:
                            metaPress(META_ALT_ON);
                            return true;

                        case KeyEvent.KEYCODE_SHIFT_LEFT:
                        case KeyEvent.KEYCODE_SHIFT_RIGHT:
                            metaPress(META_SHIFT_ON);
                            return true;
                    }
                }

                // Handle hardware CTRL keys
                if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT ||
                        keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) {
                    ctrlKeySpecial();
                    return true;
                }
            }

            // look for special chars
            switch (keyCode) {
                case KEYCODE_ESCAPE:
                    buffer.write(0x1b);
                    return true;

                case KeyEvent.KEYCODE_TAB:
                    buffer.write(0x09);
                    return true;

                case KEYCODE_PAGE_DOWN:
                    buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ', getStateForBuffer());
                    metaState &= ~META_TRANSIENT;
                    bridge.tryKeyVibrate();
                    return true;

                case KEYCODE_PAGE_UP:
                    buffer.keyPressed(vt320.KEY_PAGE_UP, ' ', getStateForBuffer());
                    metaState &= ~META_TRANSIENT;
                    bridge.tryKeyVibrate();
                    return true;

                case KeyEvent.KEYCODE_MOVE_HOME:
                    buffer.keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer());
                    metaState &= ~META_TRANSIENT;
                    bridge.tryKeyVibrate();
                    return true;

                case KeyEvent.KEYCODE_MOVE_END:
                    buffer.keyPressed(vt320.KEY_END, ' ', getStateForBuffer());
                    metaState &= ~META_TRANSIENT;
                    bridge.tryKeyVibrate();
                    return true;

                case KeyEvent.KEYCODE_CAMERA:
                    // check to see which shortcut the camera button triggers
                    String hwbuttonShortcut = manager.prefs.getString(
                                                  PreferenceConstants.CAMERA,
                                                  PreferenceConstants.HWBUTTON_SCREEN_CAPTURE);
                    return (handleShortcut(v, hwbuttonShortcut));

                case KeyEvent.KEYCODE_VOLUME_UP:
                    // check to see which shortcut the camera button triggers
                    hwbuttonShortcut = manager.prefs.getString(
                                           PreferenceConstants.VOLUP,
                                           PreferenceConstants.HWBUTTON_CTRL);
                    return (handleShortcut(v, hwbuttonShortcut));

                case KeyEvent.KEYCODE_VOLUME_DOWN:
                    // check to see which shortcut the camera button triggers
                    hwbuttonShortcut = manager.prefs.getString(
                                           PreferenceConstants.VOLDN,
                                           PreferenceConstants.HWBUTTON_TAB);
                    return (handleShortcut(v, hwbuttonShortcut));

                case KeyEvent.KEYCODE_SEARCH:
                    // check to see which shortcut the camera button triggers
                    hwbuttonShortcut = manager.prefs.getString(
                                           PreferenceConstants.SEARCH,
                                           PreferenceConstants.HWBUTTON_ESC);
                    return (handleShortcut(v, hwbuttonShortcut));

                case KeyEvent.KEYCODE_DEL:
                    if ((metaState & META_ALT_MASK) != 0) {
                        buffer.keyPressed(vt320.KEY_INSERT, ' ',
                                          getStateForBuffer());
                    }
                    else {
                        buffer.keyPressed(vt320.KEY_BACK_SPACE, ' ',
                                          getStateForBuffer());
                    }

                    metaState &= ~META_TRANSIENT;
                    return true;

                case KeyEvent.KEYCODE_ENTER:
                    buffer.write('\r');
                    metaState &= ~META_TRANSIENT;
                    return true;

                case KeyEvent.KEYCODE_DPAD_LEFT:
                    if (selectingForCopy) {
                        selectionArea.decrementColumn();
                        bridge.redraw();
                    }
                    else {
                        if ((metaState & META_ALT_MASK) != 0) {
                            buffer.keyPressed(vt320.KEY_HOME, ' ',
                                              getStateForBuffer());
                        }
                        else {
                            buffer.keyPressed(vt320.KEY_LEFT, ' ',
                                              getStateForBuffer());
                        }

                        metaState &= ~META_TRANSIENT;
                        bridge.tryKeyVibrate();
                    }

                    return true;

                case KeyEvent.KEYCODE_DPAD_UP:
                    if (selectingForCopy) {
                        selectionArea.decrementRow();
                        bridge.redraw();
                    }
                    else {
                        if ((metaState & META_ALT_MASK) != 0) {
                            buffer.keyPressed(vt320.KEY_PAGE_UP, ' ',
                                              getStateForBuffer());
                        }
                        else {
                            buffer.keyPressed(vt320.KEY_UP, ' ',
                                              getStateForBuffer());
                        }

                        metaState &= ~META_TRANSIENT;
                        bridge.tryKeyVibrate();
                    }

                    return true;

                case KeyEvent.KEYCODE_DPAD_DOWN:
                    if (selectingForCopy) {
                        selectionArea.incrementRow();
                        bridge.redraw();
                    }
                    else {
                        if ((metaState & META_ALT_MASK) != 0) {
                            buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ',
                                              getStateForBuffer());
                        }
                        else {
                            buffer.keyPressed(vt320.KEY_DOWN, ' ',
                                              getStateForBuffer());
                        }

                        metaState &= ~META_TRANSIENT;
                        bridge.tryKeyVibrate();
                    }

                    return true;

                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (selectingForCopy) {
                        selectionArea.incrementColumn();
                        bridge.redraw();
                    }
                    else {
                        if ((metaState & META_ALT_MASK) != 0) {
                            buffer.keyPressed(vt320.KEY_END, ' ',
                                              getStateForBuffer());
                        }
                        else {
                            buffer.keyPressed(vt320.KEY_RIGHT, ' ',
                                              getStateForBuffer());
                        }

                        metaState &= ~META_TRANSIENT;
                        bridge.tryKeyVibrate();
                    }

                    return true;

                case KeyEvent.KEYCODE_DPAD_CENTER:
                    ctrlKeySpecial();
                    return true;
            }
        }
        catch (IOException e) {
            Log.e(TAG, "Problem while trying to handle an onKey() event", e);

            try {
                bridge.transport.flush();
            }
            catch (IOException ioe) {
                Log.d(TAG, "Our transport was closed, dispatching disconnect event");
                bridge.dispatchDisconnect(false);
            }
        }
        catch (NullPointerException npe) {
            Log.d(TAG, "Input before connection established ignored.");
            return true;
        }

        return false;
    }

    private boolean handleShortcut(View v, String shortcut) {
        if (PreferenceConstants.HWBUTTON_SCREEN_CAPTURE.equals(shortcut)) {
            bridge.captureScreen();
        }
        else if (PreferenceConstants.HWBUTTON_CTRL.equals(shortcut)) {
            showMetakeyToast(v, PreferenceConstants.HWBUTTON_CTRL);
            metaPress(META_CTRL_ON);
        }
        else if (PreferenceConstants.HWBUTTON_TAB.equals(shortcut)) {
            buffer.write(0x09);
        }
        else if (PreferenceConstants.HWBUTTON_CTRLA_SPACE.equals(shortcut)) {
            buffer.write(0x01);
            buffer.write(' ');
        }
        else if (PreferenceConstants.HWBUTTON_CTRLA.equals(shortcut)) {
            buffer.write(0x01);
        }
        else if (PreferenceConstants.HWBUTTON_ESC.equals(shortcut)) {
            showMetakeyToast(v, PreferenceConstants.HWBUTTON_ESC);
            buffer.write(0x1b);
        }
        else if (PreferenceConstants.HWBUTTON_ESC_A.equals(shortcut)) {
            buffer.write(0x1b);
            buffer.write('a');
        }
        else {
            return (false);
        }

        return (true);
    }

    private void showMetakeyToast(View v, String keyname) {
        if (metakeyToast == null)
            metakeyToast = Toast.makeText(v.getContext(), keyname, Toast.LENGTH_LONG);
        else
            metakeyToast.setText(keyname);

        metakeyToast.setGravity(Gravity.TOP | Gravity.RIGHT, 0, 0);
        metakeyToast.show();
    }

    public int keyAsControl(int key) {
        // Support CTRL-a through CTRL-z
        if (key >= 0x60 && key <= 0x7A)
            key -= 0x60;
        // Support CTRL-A through CTRL-_
        else if (key >= 0x40 && key <= 0x5F)
            key -= 0x40;
        // CTRL-space sends NULL
        else if (key == 0x20)
            key = 0x00;
        // CTRL-? sends DEL
        else if (key == 0x3F)
            key = 0x7F;

        return key;
    }

    /**
     * @param key
     * @return successful
     */
    private boolean sendFunctionKey(int keyCode) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_1:
                buffer.keyPressed(vt320.KEY_F1, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_2:
                buffer.keyPressed(vt320.KEY_F2, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_3:
                buffer.keyPressed(vt320.KEY_F3, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_4:
                buffer.keyPressed(vt320.KEY_F4, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_5:
                buffer.keyPressed(vt320.KEY_F5, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_6:
                buffer.keyPressed(vt320.KEY_F6, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_7:
                buffer.keyPressed(vt320.KEY_F7, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_8:
                buffer.keyPressed(vt320.KEY_F8, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_9:
                buffer.keyPressed(vt320.KEY_F9, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_0:
                buffer.keyPressed(vt320.KEY_F10, ' ', 0);
                return true;

            default:
                return false;
        }
    }

    private boolean sendFullSpecialKey(int keyCode) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_F1:
                buffer.keyPressed(vt320.KEY_F1, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F2:
                buffer.keyPressed(vt320.KEY_F2, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F3:
                buffer.keyPressed(vt320.KEY_F3, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F4:
                buffer.keyPressed(vt320.KEY_F4, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F5:
                buffer.keyPressed(vt320.KEY_F5, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F6:
                buffer.keyPressed(vt320.KEY_F6, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F7:
                buffer.keyPressed(vt320.KEY_F7, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F8:
                buffer.keyPressed(vt320.KEY_F8, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F9:
                buffer.keyPressed(vt320.KEY_F9, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F10:
                buffer.keyPressed(vt320.KEY_F10, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F11:
                buffer.keyPressed(vt320.KEY_F10, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_F12:
                buffer.keyPressed(vt320.KEY_F10, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_INSERT:
                buffer.keyPressed(vt320.KEY_INSERT, ' ', 0);
                return true;

            case KeyEvent.KEYCODE_FORWARD_DEL:
                buffer.keyPressed(vt320.KEY_DELETE, ' ', 0);
                return true;

            /*
                    case KeyEvent.KEYCODE_PAGE_UP:
                        buffer.keyPressed(vt320.KEY_PAGE_UP, ' ', 0);
                        return true;
                    case KeyEvent.KEYCODE_PAGE_DOWN:
                        buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0);
                        return true;
                    case KeyEvent.KEYCODE_MOVE_HOME:
                        buffer.keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer());
                        return true;
                    case KeyEvent.KEYCODE_MOVE_END:
                        buffer.keyPressed(vt320.KEY_END, ' ', getStateForBuffer());
                        return true;
            */
            default:
                return false;
        }
    }

    /**
     * Handle meta key presses for full hardware keyboard
     */
    private void metaKeyDown(int code) {
        if ((metaState & code) == 0) {
            metaState |= code;
            bridge.redraw();
        }
    }

    private void metaKeyUp(int code) {
        if ((metaState & code) != 0) {
            metaState &= ~code;
            bridge.redraw();
        }
    }

    /**
     * Handle meta key presses where the key can be locked on.
     * <p>
     * 1st press: next key to have meta state<br />
     * 2nd press: meta state is locked on<br />
     * 3rd press: disable meta state
     *
     * @param code
     */
    public void metaPress(int code) {
        if ((metaState & (code << 1)) != 0) {
            metaState &= ~(code << 1);
        }
        else if ((metaState & code) != 0) {
            metaState &= ~code;

            if (!fullKeyboard())
                metaState |= code << 1;
        }
        else
            metaState |= code;

        bridge.redraw();
    }

    public void setTerminalKeyMode(String keymode) {
        this.keymode = keymode;
    }

    private int getStateForBuffer() {
        int bufferState = 0;

        if ((metaState & META_CTRL_MASK) != 0)
            bufferState |= vt320.KEY_CONTROL;

        if ((metaState & META_SHIFT_MASK) != 0)
            bufferState |= vt320.KEY_SHIFT;

        if ((metaState & META_ALT_MASK) != 0)
            bufferState |= vt320.KEY_ALT;

        return bufferState;
    }

    public int getMetaState() {
        return metaState;
    }

    public int getDeadKey() {
        return mDeadKey;
    }

    public void setClipboardManager(ClipboardManager clipboard) {
        this.clipboard = clipboard;
    }

    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
                                          String key) {
        if (PreferenceConstants.KEYMODE.equals(key)) {
            updateKeymode();
        }
        else if (PreferenceConstants.CUSTOM_KEYMAP.equals(key)) {
            updateCustomKeymap();
        }
    }

    private void updateKeymode() {
        keymode = prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT);
    }

    private void updateCustomKeymap() {
        customKeyboard = prefs.getString(PreferenceConstants.CUSTOM_KEYMAP,
                                         PreferenceConstants.CUSTOM_KEYMAP_DISABLED);
    }

    public void setCharset(String encoding) {
        this.encoding = encoding;
    }



    private void ctrlKeySpecial() {
        if (selectingForCopy) {
            if (selectionArea.isSelectingOrigin())
                selectionArea.finishSelectingOrigin();
            else {
                if (clipboard != null) {
                    // copy selected area to clipboard
                    String copiedText = selectionArea.copyFrom(buffer);
                    clipboard.setText(copiedText);
                    // XXX STOPSHIP
//                  manager.notifyUser(manager.getString(
//                          R.string.console_copy_done,
//                          copiedText.length()));
                    selectingForCopy = false;
                    selectionArea.reset();
                }
            }
        }
        else {
            if ((metaState & META_CTRL_ON) != 0) {
                buffer.write(0x1b);
                metaState &= ~META_CTRL_ON;
            }
            else
                metaPress(META_CTRL_ON);
        }

        bridge.redraw();
    }

    private boolean customKeymapAction(View v, int keyCode, KeyEvent event) {
        if (bridge == null || customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_DISABLED))
            return false;

        byte c = 0x00;
        int termKey = 0;

        if (fullKeyboard()) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_CTRL_LEFT:
                case KeyEvent.KEYCODE_CTRL_RIGHT:
                    metaKeyDown(META_CTRL_ON);
                    return true;

                case KeyEvent.KEYCODE_ALT_LEFT:
                case KeyEvent.KEYCODE_ALT_RIGHT:
                    metaKeyDown(META_ALT_ON);
                    return true;

                case KeyEvent.KEYCODE_SHIFT_LEFT:
                case KeyEvent.KEYCODE_SHIFT_RIGHT:
                    metaKeyDown(META_SHIFT_ON);
                    return true;

                case KeyEvent.KEYCODE_BACK:
                    if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF)) {
                        // Check to see whether this is the back button on the
                        // screen (-1) or the Asus Transformer Keyboard Dock.
                        // Treat the HW button as ESC.
                        if (event.getDeviceId() > 0) {
                            buffer.write(0x1b);
                            return true;
                        }
                    }

                default:
            }
        }

        if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF)) {
            if ((metaState & META_ALT_MASK) != 0
                    && (metaState & META_SHIFT_MASK) != 0
                    && sendFunctionKey(keyCode))
                return true;
        }
        else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SE_XPPRO)) {
            // Sony Ericsson Xperia pro (MK16i) and Xperia mini Pro (SK17i)
            // Language key acts as CTRL
            if (keyCode == KeyEvent.KEYCODE_SWITCH_CHARSET) {
                ctrlKeySpecial();
                return true;
            }

            if ((metaState & META_ALT_MASK) != 0) {
                if ((metaState & META_SHIFT_MASK) != 0) {
                    // ALT + shift + key
                    switch (keyCode) {
                        case KeyEvent.KEYCODE_U:
                            c = 0x5B;
                            break;

                        case KeyEvent.KEYCODE_I:
                            c = 0x5D;
                            break;

                        case KeyEvent.KEYCODE_O:
                            c = 0x7B;
                            break;

                        case KeyEvent.KEYCODE_P:
                            c = 0x7D;
                            break;
                    }
                }
                else {
                    // ALT + key
                    switch (keyCode) {
                        case KeyEvent.KEYCODE_S:
                            c = 0x7c;
                            break;

                        case KeyEvent.KEYCODE_Z:
                            c = 0x5c;
                            break;

                        case KeyEvent.KEYCODE_DEL:
                            termKey = vt320.KEY_DELETE;
                            break;
                    }
                }
            }
            else if ((metaState & META_SHIFT_MASK) != 0) {
                // shift + key
                switch (keyCode) {
                    case KeyEvent.KEYCODE_AT:
                        c = 0x3c;
                        break;

                    case KeyEvent.KEYCODE_COMMA:
                        c = 0x3e;
                        break;

                    case KeyEvent.KEYCODE_PERIOD:
                        c = 0x5e;
                        break;

                    case KeyEvent.KEYCODE_GRAVE:
                        c = 0x60;
                        break;

                    case KeyEvent.KEYCODE_APOSTROPHE:
                        c = 0x7e;
                        break;

                    case KeyEvent.KEYCODE_DEL:
                        termKey = vt320.KEY_BACK_SPACE;
                        break;
                }
            }
        }
        else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927)) {
            // Samsung Captivate Glide (SGH-i927)
            if (keyCode == 115) {
                // .com key = ESC
                c = 0x1b;
                return true;
            }
            else if (keyCode == 116) {
                // Microphone key = TAB
                c = 0x09;
            }
            else if ((metaState & META_ALT_MASK) != 0 && (metaState & META_SHIFT_MASK) != 0) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_O:
                        c = 0x5B;
                        break;

                    case KeyEvent.KEYCODE_P:
                        c = 0x5D;
                        break;

                    case KeyEvent.KEYCODE_A:
                        c = 0x3C;
                        break;

                    case KeyEvent.KEYCODE_D:
                        c = 0x3E;
                        break;
                }
            }
        }
        else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927_ICS)) {
            // Samsung Captivate Glide (SGH-i927) Ice Cream Sandwich (4.0.x)
            if (keyCode == 226) {
                // .com key = ESC
                c = 0x1b;
            }
            else if (keyCode == 220) {
                // Microphone key = TAB
                c = 0x09;
            }
            else if ((metaState & META_ALT_MASK) != 0 && (metaState & META_SHIFT_MASK) != 0) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_O:
                        c = 0x5B;
                        break;

                    case KeyEvent.KEYCODE_P:
                        c = 0x5D;
                        break;

                    case KeyEvent.KEYCODE_A:
                        c = 0x3C;
                        break;

                    case KeyEvent.KEYCODE_D:
                        c = 0x3E;
                        break;
                }
            }
        }

        if ((c != 0x00) || termKey != 0) {
            if (c != 0x00)
                buffer.write(c);
            else
                buffer.keyPressed(termKey, ' ', 0);

            metaState &= ~(META_SHIFT_ON | META_ALT_ON);
            bridge.redraw();
            return true;
        }

        return false;
    }

    public void urlScan(View v) {
        //final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
        List<String> urls = bridge.scanForURLs();
        Dialog urlDialog = new Dialog(v.getContext());
        urlDialog.setTitle(R.string.console_menu_urlscan);
        ListView urlListView = new ListView(v.getContext());
        URLItemListener urlListener = new URLItemListener(v.getContext());
        urlListView.setOnItemClickListener(urlListener);
        urlListView.setAdapter(new ArrayAdapter<String> (v.getContext(), android.R.layout.simple_list_item_1, urls));
        urlDialog.setContentView(urlListView);
        urlDialog.show();
    }

    public boolean isSymKey(int keyCode) {
        if (keyCode == KeyEvent.KEYCODE_SYM ||
                keyCode == KeyEvent.KEYCODE_PICTSYMBOLS)
            return true;

        if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927_ICS) &&
                keyCode == 227)
            return true;

        return false;
    }

    private boolean fullKeyboard() {
        if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_FULL) ||
                (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF)))
            return true;

        return false;
    }

    private class URLItemListener implements OnItemClickListener {
        private WeakReference<Context> contextRef;

        URLItemListener(Context context) {
            this.contextRef = new WeakReference<Context> (context);
        }

        public void onItemClick(AdapterView<?> arg0, View view, int position,
                                long id) {
            Context context = contextRef.get();

            if (context == null)
                return;

            try {
                TextView urlView = (TextView) view;
                String url = urlView.getText().toString();

                if (url.indexOf("://") < 0)
                    url = "http://" + url;

                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                context.startActivity(intent);
            }
            catch (Exception e) {
                Log.e(TAG, "couldn't open URL", e);
                // We should probably tell the user that we couldn't find a
                // handler...
            }
        }
    }
}