From 515c3eab54db468865985299471a6785e3aebb28 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Sat, 13 Mar 2010 06:24:39 +0000 Subject: Refactor keyboard management into separate class git-svn-id: https://connectbot.googlecode.com/svn/trunk/connectbot@490 df292f66-193f-0410-a5fc-6d59da041ff2 --- src/org/connectbot/ConsoleActivity.java | 3 +- src/org/connectbot/TerminalView.java | 27 +- src/org/connectbot/service/TerminalBridge.java | 472 ++------------------ .../connectbot/service/TerminalKeyListener.java | 486 +++++++++++++++++++++ src/org/connectbot/service/TerminalManager.java | 10 +- 5 files changed, 554 insertions(+), 444 deletions(-) create mode 100644 src/org/connectbot/service/TerminalKeyListener.java (limited to 'src/org') diff --git a/src/org/connectbot/ConsoleActivity.java b/src/org/connectbot/ConsoleActivity.java index 234ef43..9f61105 100644 --- a/src/org/connectbot/ConsoleActivity.java +++ b/src/org/connectbot/ConsoleActivity.java @@ -1032,6 +1032,8 @@ public class ConsoleActivity extends Activity { bound.setResizeAllowed(false); else bound.setResizeAllowed(true); + + bound.hardKeyboardHidden = (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES); } } @@ -1044,7 +1046,6 @@ public class ConsoleActivity extends Activity { private int addNewTerminalView(TerminalBridge bridge) { // let them know about our prompt handler services bridge.promptHelper.setHandler(promptHandler); - bridge.refreshKeymode(); // inflate each terminal view RelativeLayout view = (RelativeLayout)inflater.inflate(R.layout.item_terminal, flip, false); diff --git a/src/org/connectbot/TerminalView.java b/src/org/connectbot/TerminalView.java index 4e1fefc..35a3c56 100644 --- a/src/org/connectbot/TerminalView.java +++ b/src/org/connectbot/TerminalView.java @@ -20,6 +20,7 @@ package org.connectbot; import org.connectbot.bean.SelectionArea; import org.connectbot.service.FontSizeChangedListener; import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalKeyListener; import android.app.Activity; import android.content.Context; @@ -110,7 +111,7 @@ public class TerminalView extends View implements FontSizeChangedListener { bridge.addFontSizeChangedListener(this); // connect our view up to the bridge - setOnKeyListener(bridge); + setOnKeyListener(bridge.getKeyHandler()); } public void destroy() { @@ -149,14 +150,18 @@ public class TerminalView extends View implements FontSizeChangedListener { // also draw cursor if visible if (bridge.buffer.isCursorVisible()) { int cursorColumn = bridge.buffer.getCursorColumn(); - int columns = bridge.buffer.getColumns(); + final int cursorRow = bridge.buffer.getCursorRow(); + + final int columns = bridge.buffer.getColumns(); if (cursorColumn == columns) cursorColumn = columns - 1; + if (cursorColumn < 0 || cursorRow < 0) + return; + int currentAttribute = bridge.buffer.getAttributes( - cursorColumn, - bridge.buffer.getCursorRow()); + cursorColumn, cursorRow); boolean onWideCharacter = (currentAttribute & VDUBuffer.FULLWIDTH) != 0; int x = cursorColumn * bridge.charWidth; @@ -176,21 +181,21 @@ public class TerminalView extends View implements FontSizeChangedListener { // Make sure we scale our decorations to the correct size. canvas.concat(scaleMatrix); - int metaState = bridge.getMetaState(); + int metaState = bridge.getKeyHandler().getMetaState(); - if ((metaState & TerminalBridge.META_SHIFT_ON) != 0) + if ((metaState & TerminalKeyListener.META_SHIFT_ON) != 0) canvas.drawPath(shiftCursor, cursorStrokePaint); - else if ((metaState & TerminalBridge.META_SHIFT_LOCK) != 0) + else if ((metaState & TerminalKeyListener.META_SHIFT_LOCK) != 0) canvas.drawPath(shiftCursor, cursorPaint); - if ((metaState & TerminalBridge.META_ALT_ON) != 0) + if ((metaState & TerminalKeyListener.META_ALT_ON) != 0) canvas.drawPath(altCursor, cursorStrokePaint); - else if ((metaState & TerminalBridge.META_ALT_LOCK) != 0) + else if ((metaState & TerminalKeyListener.META_ALT_LOCK) != 0) canvas.drawPath(altCursor, cursorPaint); - if ((metaState & TerminalBridge.META_CTRL_ON) != 0) + if ((metaState & TerminalKeyListener.META_CTRL_ON) != 0) canvas.drawPath(ctrlCursor, cursorStrokePaint); - else if ((metaState & TerminalBridge.META_CTRL_LOCK) != 0) + else if ((metaState & TerminalKeyListener.META_CTRL_LOCK) != 0) canvas.drawPath(ctrlCursor, cursorPaint); // Restore previous clip region diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index 9a2a946..2c1c1e1 100644 --- a/src/org/connectbot/service/TerminalBridge.java +++ b/src/org/connectbot/service/TerminalBridge.java @@ -32,10 +32,8 @@ import org.connectbot.bean.SelectionArea; import org.connectbot.transport.AbsTransport; import org.connectbot.transport.TransportFactory; import org.connectbot.util.HostDatabase; -import org.connectbot.util.PreferenceConstants; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -45,10 +43,6 @@ import android.graphics.Bitmap.Config; import android.graphics.Paint.FontMetrics; import android.text.ClipboardManager; import android.util.Log; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.View; -import android.view.View.OnKeyListener; import de.mud.terminal.VDUBuffer; import de.mud.terminal.VDUDisplay; import de.mud.terminal.vt320; @@ -63,10 +57,11 @@ import de.mud.terminal.vt320; * This class also provides SSH hostkey verification prompting, and password * prompting. */ -public class TerminalBridge implements VDUDisplay, OnKeyListener { +public class TerminalBridge implements VDUDisplay { public final static String TAG = "ConnectBot.TerminalBridge"; public final static int DEFAULT_FONT_SIZE = 10; + private final static int FONT_SIZE_STEP = 2; public Integer[] color; @@ -77,7 +72,7 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { public HostBean host; - private AbsTransport transport; + /* package */ AbsTransport transport; final Paint defaultPaint; @@ -92,26 +87,6 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { private TerminalView parent = null; private final Canvas canvas = new Canvas(); - private int metaState = 0; - - 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; - - // All the transient key codes - public final static int META_TRANSIENT = META_CTRL_ON | META_ALT_ON - | META_SHIFT_ON; - private boolean disconnected = false; private boolean awaitingClose = false; @@ -119,16 +94,12 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { private int columns; private int rows; - private String keymode = null; - - private boolean hardKeyboard = false; + /* package */ final TerminalKeyListener keyListener; private boolean selectingForCopy = false; private final SelectionArea selectionArea; private ClipboardManager clipboard; - protected KeyCharacterMap keymap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); - public int charWidth = -1; public int charHeight = -1; private int charTop = -1; @@ -179,6 +150,8 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { fontSizeChangedListeners = new LinkedList(); transport = null; + + keyListener = new TerminalKeyListener(manager, this, buffer, host); } /** @@ -187,7 +160,6 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { * and password authentication. */ public TerminalBridge(final TerminalManager manager, final HostBean host) throws IOException { - this.manager = manager; this.host = host; @@ -270,8 +242,7 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { selectionArea = new SelectionArea(); - hardKeyboard = (manager.res.getConfiguration().keyboard - == Configuration.KEYBOARD_QWERTY); + keyListener = new TerminalKeyListener(manager, this, buffer, host); } public PromptHelper getPromptHelper() { @@ -482,394 +453,6 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { } } - public void refreshKeymode() { - keymode = manager.getKeyMode(); - } - - /** - * Handle onKey() events coming down from a {@link TerminalView} above us. - * Modify the keys to make more sense to a host then pass it to the transport. - */ - public boolean onKey(View v, int keyCode, KeyEvent event) { - try { - - boolean hardKeyboardHidden = - manager.res.getConfiguration().hardKeyboardHidden == - Configuration.HARDKEYBOARDHIDDEN_YES; - - // 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; - - // skip keys if we aren't connected yet or have been disconnected - if (disconnected || transport == null) - return false; - - if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) { - if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT - && (metaState & META_SLASH) != 0) { - metaState &= ~(META_SLASH | META_TRANSIENT); - transport.write('/'); - return true; - } else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT - && (metaState & META_TAB) != 0) { - metaState &= ~(META_TAB | META_TRANSIENT); - transport.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); - transport.write('/'); - return true; - } else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT - && (metaState & META_TAB) != 0) { - metaState &= ~(META_TAB | META_TRANSIENT); - transport.write(0x09); - return true; - } - } - - return false; - } - - // check for terminal resizing keys - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { - forcedSize = false; - setFontSize(fontSize + 2); - return true; - } else if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - forcedSize = false; - setFontSize(fontSize - 2); - return true; - } - - // skip keys if we aren't connected yet or have been disconnected - if (disconnected || transport == null) - return false; - - // if we're in scrollback, scroll to bottom of window on input - if (buffer.windowBase != buffer.screenBase) - buffer.setWindowBase(buffer.screenBase); - - boolean printing = (keymap.isPrintingKey(keyCode) || keyCode == KeyEvent.KEYCODE_SPACE); - - // otherwise pass through to existing session - // print normal keys - if (printing) { - int curMetaState = event.getMetaState(); - - metaState &= ~(META_SLASH | META_TAB); - - if ((metaState & META_SHIFT_MASK) != 0) { - curMetaState |= KeyEvent.META_SHIFT_ON; - metaState &= ~META_SHIFT_ON; - redraw(); - } - - if ((metaState & META_ALT_MASK) != 0) { - curMetaState |= KeyEvent.META_ALT_ON; - metaState &= ~META_ALT_ON; - redraw(); - } - - int key = keymap.get(keyCode, curMetaState); - - if ((metaState & META_CTRL_MASK) != 0) { - metaState &= ~META_CTRL_ON; - redraw(); - - if ((!hardKeyboard || (hardKeyboard && hardKeyboardHidden)) - && sendFunctionKey(keyCode)) - return true; - - // Support CTRL-a through CTRL-z - if (key >= 0x61 && key <= 0x7A) - key -= 0x60; - // Support CTRL-A through CTRL-_ - else if (key >= 0x41 && key <= 0x5F) - key -= 0x40; - else if (key == 0x20) - key = 0x00; - else if (key == 0x3F) - key = 0x7F; - } - - // handle pressing f-keys - if ((hardKeyboard && !hardKeyboardHidden) - && (curMetaState & KeyEvent.META_SHIFT_ON) != 0 - && sendFunctionKey(keyCode)) - return true; - - if (key < 0x80) - transport.write(key); - else - // TODO write encoding routine that doesn't allocate each time - transport.write(new String(Character.toChars(key)) - .getBytes(host.getEncoding())); - - return true; - } - - if (keyCode == KeyEvent.KEYCODE_UNKNOWN && - event.getAction() == KeyEvent.ACTION_MULTIPLE) { - byte[] input = event.getCharacters().getBytes(host.getEncoding()); - transport.write(input); - 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_LEFT: - case KeyEvent.KEYCODE_ALT_RIGHT: - metaPress(META_ALT_ON); - return true; - case KeyEvent.KEYCODE_SHIFT_LEFT: - case KeyEvent.KEYCODE_SHIFT_RIGHT: - metaPress(META_SHIFT_ON); - return true; - } - } - } - - // look for special chars - switch(keyCode) { - case KeyEvent.KEYCODE_CAMERA: - - // check to see which shortcut the camera button triggers - String camera = manager.prefs.getString( - PreferenceConstants.CAMERA, - PreferenceConstants.CAMERA_CTRLA_SPACE); - if(PreferenceConstants.CAMERA_CTRLA_SPACE.equals(camera)) { - transport.write(0x01); - transport.write(' '); - } else if(PreferenceConstants.CAMERA_CTRLA.equals(camera)) { - transport.write(0x01); - } else if(PreferenceConstants.CAMERA_ESC.equals(camera)) { - ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); - } - - break; - - case KeyEvent.KEYCODE_DEL: - ((vt320) buffer).keyPressed(vt320.KEY_BACK_SPACE, ' ', - getStateForBuffer()); - metaState &= ~META_TRANSIENT; - return true; - case KeyEvent.KEYCODE_ENTER: - ((vt320)buffer).keyTyped(vt320.KEY_ENTER, ' ', 0); - metaState &= ~META_TRANSIENT; - return true; - - case KeyEvent.KEYCODE_DPAD_LEFT: - if (selectingForCopy) { - selectionArea.decrementColumn(); - redraw(); - } else { - ((vt320) buffer).keyPressed(vt320.KEY_LEFT, ' ', - getStateForBuffer()); - metaState &= ~META_TRANSIENT; - tryKeyVibrate(); - } - return true; - - case KeyEvent.KEYCODE_DPAD_UP: - if (selectingForCopy) { - selectionArea.decrementRow(); - redraw(); - } else { - ((vt320) buffer).keyPressed(vt320.KEY_UP, ' ', - getStateForBuffer()); - metaState &= ~META_TRANSIENT; - tryKeyVibrate(); - } - return true; - - case KeyEvent.KEYCODE_DPAD_DOWN: - if (selectingForCopy) { - selectionArea.incrementRow(); - redraw(); - } else { - ((vt320) buffer).keyPressed(vt320.KEY_DOWN, ' ', - getStateForBuffer()); - metaState &= ~META_TRANSIENT; - tryKeyVibrate(); - } - return true; - - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (selectingForCopy) { - selectionArea.incrementColumn(); - redraw(); - } else { - ((vt320) buffer).keyPressed(vt320.KEY_RIGHT, ' ', - getStateForBuffer()); - metaState &= ~META_TRANSIENT; - tryKeyVibrate(); - } - return true; - - case KeyEvent.KEYCODE_DPAD_CENTER: - if (selectingForCopy) { - if (selectionArea.isSelectingOrigin()) - selectionArea.finishSelectingOrigin(); - else { - if (parent != null && clipboard != null) { - // copy selected area to clipboard - String copiedText = selectionArea.copyFrom(buffer); - - clipboard.setText(copiedText); - parent.notifyUser(parent.getContext().getString( - R.string.console_copy_done, - copiedText.length())); - - selectingForCopy = false; - selectionArea.reset(); - } - } - } else { - if ((metaState & META_CTRL_ON) != 0) { - ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); - metaState &= ~META_CTRL_ON; - } else - metaState |= META_CTRL_ON; - } - - redraw(); - - return true; - } - - } catch (IOException e) { - Log.e(TAG, "Problem while trying to handle an onKey() event", e); - try { - transport.flush(); - } catch (IOException ioe) { - Log.d(TAG, "Our transport was closed, dispatching disconnect event"); - dispatchDisconnect(false); - } - } catch (NullPointerException npe) { - Log.d(TAG, "Input before connection established ignored."); - return true; - } - - return false; - } - - /** - * @param key - * @return successful - */ - private boolean sendFunctionKey(int keyCode) { - switch (keyCode) { - case KeyEvent.KEYCODE_1: - ((vt320) buffer).keyPressed(vt320.KEY_F1, ' ', 0); - return true; - case KeyEvent.KEYCODE_2: - ((vt320) buffer).keyPressed(vt320.KEY_F2, ' ', 0); - return true; - case KeyEvent.KEYCODE_3: - ((vt320) buffer).keyPressed(vt320.KEY_F3, ' ', 0); - return true; - case KeyEvent.KEYCODE_4: - ((vt320) buffer).keyPressed(vt320.KEY_F4, ' ', 0); - return true; - case KeyEvent.KEYCODE_5: - ((vt320) buffer).keyPressed(vt320.KEY_F5, ' ', 0); - return true; - case KeyEvent.KEYCODE_6: - ((vt320) buffer).keyPressed(vt320.KEY_F6, ' ', 0); - return true; - case KeyEvent.KEYCODE_7: - ((vt320) buffer).keyPressed(vt320.KEY_F7, ' ', 0); - return true; - case KeyEvent.KEYCODE_8: - ((vt320) buffer).keyPressed(vt320.KEY_F8, ' ', 0); - return true; - case KeyEvent.KEYCODE_9: - ((vt320) buffer).keyPressed(vt320.KEY_F9, ' ', 0); - return true; - case KeyEvent.KEYCODE_0: - ((vt320) buffer).keyPressed(vt320.KEY_F10, ' ', 0); - return true; - default: - return false; - } - } - - /** - * Handle meta key presses where the key can be locked on. - *

- * 1st press: next key to have meta state
- * 2nd press: meta state is locked on
- * 3rd press: disable meta state - * - * @param code - */ - private void metaPress(int code) { - if ((metaState & (code << 1)) != 0) { - metaState &= ~(code << 1); - } else if ((metaState & code) != 0) { - metaState &= ~code; - metaState |= code << 1; - } else - metaState |= code; - redraw(); - } - - public int getMetaState() { - return metaState; - } - - 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 void setSelectingForCopy(boolean selectingForCopy) { this.selectingForCopy = selectingForCopy; } @@ -890,7 +473,7 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { * Request a different font size. Will make call to parentChanged() to make * sure we resize PTY if needed. */ - private final void setFontSize(float size) { + /* package */ final void setFontSize(float size) { if (size <= 0.0) return; @@ -915,6 +498,8 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { host.setFontSize((int) fontSize); manager.hostdb.updateFontSize(host); + + forcedSize = false; } /** @@ -950,14 +535,15 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { } this.parent = parent; - int width = parent.getWidth(); - int height = parent.getHeight(); + final int width = parent.getWidth(); + final int height = parent.getHeight(); // Something has gone wrong with our layout; we're 0 width or height! if (width <= 0 || height <= 0) return; clipboard = (ClipboardManager) parent.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + keyListener.setClipboardManager(clipboard); if (!forcedSize) { // recalculate buffer size @@ -1207,10 +793,10 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { if (direction > 0) size -= step; - forcedSize = true; this.columns = cols; this.rows = rows; setFontSize(size); + forcedSize = true; } private int fontSizeCompare(float size, int cols, int rows, int width, int height) { @@ -1359,4 +945,34 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { public boolean isUsingNetwork() { return transport.usesNetwork(); } + + /** + * @return + */ + public TerminalKeyListener getKeyHandler() { + return keyListener; + } + + /** + * + */ + public void resetScrollPosition() { + // if we're in scrollback, scroll to bottom of window on input + if (buffer.windowBase != buffer.screenBase) + buffer.setWindowBase(buffer.screenBase); + } + + /** + * + */ + public void increaseFontSize() { + setFontSize(fontSize + FONT_SIZE_STEP); + } + + /** + * + */ + public void decreaseFontSize() { + setFontSize(fontSize - FONT_SIZE_STEP); + } } diff --git a/src/org/connectbot/service/TerminalKeyListener.java b/src/org/connectbot/service/TerminalKeyListener.java new file mode 100644 index 0000000..b63dab7 --- /dev/null +++ b/src/org/connectbot/service/TerminalKeyListener.java @@ -0,0 +1,486 @@ +/** + * + */ +package org.connectbot.service; + +import java.io.IOException; + +import org.connectbot.TerminalView; +import org.connectbot.bean.HostBean; +import org.connectbot.bean.SelectionArea; +import org.connectbot.util.PreferenceConstants; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.res.Configuration; +import android.preference.PreferenceManager; +import android.text.ClipboardManager; +import android.util.Log; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnKeyListener; +import de.mud.terminal.VDUBuffer; +import de.mud.terminal.vt320; + +/** + * @author kenny + * + */ +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; + + // 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 VDUBuffer buffer; + private final HostBean host; + + protected KeyCharacterMap keymap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); + + private String keymode = null; + private boolean hardKeyboard = false; + + private int metaState = 0; + + private ClipboardManager clipboard = null; + private boolean selectingForCopy = false; + private final SelectionArea selectionArea; + + private final SharedPreferences prefs; + + public TerminalKeyListener(TerminalManager manager, + TerminalBridge bridge, + VDUBuffer buffer, + HostBean host) { + this.manager = manager; + this.bridge = bridge; + this.buffer = buffer; + this.host = host; + + selectionArea = new SelectionArea(); + + prefs = PreferenceManager.getDefaultSharedPreferences(manager); + prefs.registerOnSharedPreferenceChangeListener(this); + + hardKeyboard = (manager.res.getConfiguration().keyboard + == Configuration.KEYBOARD_QWERTY); + + updateKeymode(); + } + + /** + * Handle onKey() events coming down from a {@link TerminalView} above us. + * Modify the keys to make more sense to a host then pass it to the transport. + */ + public boolean onKey(View v, int keyCode, KeyEvent event) { + try { + 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; + + // skip keys if we aren't connected yet or have been disconnected + if (bridge.isDisconnected() || bridge.transport == null) + return false; + + if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) { + if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT + && (metaState & META_SLASH) != 0) { + metaState &= ~(META_SLASH | META_TRANSIENT); + bridge.transport.write('/'); + return true; + } else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT + && (metaState & META_TAB) != 0) { + metaState &= ~(META_TAB | META_TRANSIENT); + bridge.transport.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); + bridge.transport.write('/'); + return true; + } else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + && (metaState & META_TAB) != 0) { + metaState &= ~(META_TAB | META_TRANSIENT); + bridge.transport.write(0x09); + return true; + } + } + + return false; + } + + // check for terminal resizing keys + if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + bridge.increaseFontSize(); + return true; + } else if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + bridge.decreaseFontSize(); + return true; + } + + // skip keys if we aren't connected yet or have been disconnected + if (bridge.isDisconnected() || bridge.transport == null) + return false; + + bridge.resetScrollPosition(); + + boolean printing = (keymap.isPrintingKey(keyCode) || keyCode == KeyEvent.KEYCODE_SPACE); + + // otherwise pass through to existing session + // print normal keys + if (printing) { + int curMetaState = event.getMetaState(); + + metaState &= ~(META_SLASH | META_TAB); + + if ((metaState & META_SHIFT_MASK) != 0) { + curMetaState |= KeyEvent.META_SHIFT_ON; + metaState &= ~META_SHIFT_ON; + bridge.redraw(); + } + + if ((metaState & META_ALT_MASK) != 0) { + curMetaState |= KeyEvent.META_ALT_ON; + metaState &= ~META_ALT_ON; + bridge.redraw(); + } + + int key = keymap.get(keyCode, curMetaState); + + if ((metaState & META_CTRL_MASK) != 0) { + metaState &= ~META_CTRL_ON; + bridge.redraw(); + + if ((!hardKeyboard || (hardKeyboard && hardKeyboardHidden)) + && sendFunctionKey(keyCode)) + return true; + + // Support CTRL-a through CTRL-z + if (key >= 0x61 && key <= 0x7A) + key -= 0x60; + // Support CTRL-A through CTRL-_ + else if (key >= 0x41 && key <= 0x5F) + key -= 0x40; + else if (key == 0x20) + key = 0x00; + else if (key == 0x3F) + key = 0x7F; + } + + // handle pressing f-keys + if ((hardKeyboard && !hardKeyboardHidden) + && (curMetaState & KeyEvent.META_SHIFT_ON) != 0 + && sendFunctionKey(keyCode)) + return true; + + if (key < 0x80) + bridge.transport.write(key); + else + // TODO write encoding routine that doesn't allocate each time + bridge.transport.write(new String(Character.toChars(key)) + .getBytes(host.getEncoding())); + + return true; + } + + if (keyCode == KeyEvent.KEYCODE_UNKNOWN && + event.getAction() == KeyEvent.ACTION_MULTIPLE) { + byte[] input = event.getCharacters().getBytes(host.getEncoding()); + bridge.transport.write(input); + 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_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + metaPress(META_ALT_ON); + return true; + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + metaPress(META_SHIFT_ON); + return true; + } + } + } + + // look for special chars + switch(keyCode) { + case KeyEvent.KEYCODE_CAMERA: + + // check to see which shortcut the camera button triggers + String camera = manager.prefs.getString( + PreferenceConstants.CAMERA, + PreferenceConstants.CAMERA_CTRLA_SPACE); + if(PreferenceConstants.CAMERA_CTRLA_SPACE.equals(camera)) { + bridge.transport.write(0x01); + bridge.transport.write(' '); + } else if(PreferenceConstants.CAMERA_CTRLA.equals(camera)) { + bridge.transport.write(0x01); + } else if(PreferenceConstants.CAMERA_ESC.equals(camera)) { + ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); + } + + break; + + case KeyEvent.KEYCODE_DEL: + ((vt320) buffer).keyPressed(vt320.KEY_BACK_SPACE, ' ', + getStateForBuffer()); + metaState &= ~META_TRANSIENT; + return true; + case KeyEvent.KEYCODE_ENTER: + ((vt320)buffer).keyTyped(vt320.KEY_ENTER, ' ', 0); + metaState &= ~META_TRANSIENT; + return true; + + case KeyEvent.KEYCODE_DPAD_LEFT: + if (selectingForCopy) { + selectionArea.decrementColumn(); + bridge.redraw(); + } else { + ((vt320) 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 { + ((vt320) 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 { + ((vt320) 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 { + ((vt320) buffer).keyPressed(vt320.KEY_RIGHT, ' ', + getStateForBuffer()); + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + } + return true; + + case KeyEvent.KEYCODE_DPAD_CENTER: + 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) { + ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); + metaState &= ~META_CTRL_ON; + } else + metaState |= META_CTRL_ON; + } + + bridge.redraw(); + + 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; + } + + + /** + * @param key + * @return successful + */ + private boolean sendFunctionKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_1: + ((vt320) buffer).keyPressed(vt320.KEY_F1, ' ', 0); + return true; + case KeyEvent.KEYCODE_2: + ((vt320) buffer).keyPressed(vt320.KEY_F2, ' ', 0); + return true; + case KeyEvent.KEYCODE_3: + ((vt320) buffer).keyPressed(vt320.KEY_F3, ' ', 0); + return true; + case KeyEvent.KEYCODE_4: + ((vt320) buffer).keyPressed(vt320.KEY_F4, ' ', 0); + return true; + case KeyEvent.KEYCODE_5: + ((vt320) buffer).keyPressed(vt320.KEY_F5, ' ', 0); + return true; + case KeyEvent.KEYCODE_6: + ((vt320) buffer).keyPressed(vt320.KEY_F6, ' ', 0); + return true; + case KeyEvent.KEYCODE_7: + ((vt320) buffer).keyPressed(vt320.KEY_F7, ' ', 0); + return true; + case KeyEvent.KEYCODE_8: + ((vt320) buffer).keyPressed(vt320.KEY_F8, ' ', 0); + return true; + case KeyEvent.KEYCODE_9: + ((vt320) buffer).keyPressed(vt320.KEY_F9, ' ', 0); + return true; + case KeyEvent.KEYCODE_0: + ((vt320) buffer).keyPressed(vt320.KEY_F10, ' ', 0); + return true; + default: + return false; + } + } + + /** + * Handle meta key presses where the key can be locked on. + *

+ * 1st press: next key to have meta state
+ * 2nd press: meta state is locked on
+ * 3rd press: disable meta state + * + * @param code + */ + private void metaPress(int code) { + if ((metaState & (code << 1)) != 0) { + metaState &= ~(code << 1); + } else if ((metaState & code) != 0) { + metaState &= ~code; + 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 void setClipboardManager(ClipboardManager clipboard) { + this.clipboard = clipboard; + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (PreferenceConstants.KEYMODE.equals(key)) { + updateKeymode(); + } + } + + private void updateKeymode() { + keymode = prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT); + } +} diff --git a/src/org/connectbot/service/TerminalManager.java b/src/org/connectbot/service/TerminalManager.java index 0f302f7..c378b14 100644 --- a/src/org/connectbot/service/TerminalManager.java +++ b/src/org/connectbot/service/TerminalManager.java @@ -45,6 +45,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.AssetFileDescriptor; +import android.content.res.Configuration; import android.content.res.Resources; import android.media.AudioManager; import android.media.MediaPlayer; @@ -115,6 +116,8 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen protected List> mPendingReconnect = new LinkedList>(); + public boolean hardKeyboardHidden; + @Override public void onCreate() { Log.i(TAG, "Starting background service"); @@ -152,6 +155,9 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen wantBellVibration = prefs.getBoolean(PreferenceConstants.BELL_VIBRATE, true); enableMediaPlayer(); + hardKeyboardHidden = (res.getConfiguration().hardKeyboardHidden == + Configuration.HARDKEYBOARDHIDDEN_YES); + final boolean lockingWifi = prefs.getBoolean(PreferenceConstants.WIFI_LOCK, true); connectivityManager = new ConnectivityReceiver(this, lockingWifi); @@ -259,10 +265,6 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen return scrollback; } - public String getKeyMode() { - return prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT); // "Use right-side keys" - } - /** * Open a new connection by reading parameters from the given URI. Follows * format specified by an individual transport. -- cgit v1.2.3