From 62f7ef4e11d2d7d814c89f72ec48b9acd83ab671 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Tue, 16 Mar 2010 09:52:15 +0800 Subject: [PATCH] Update to r493 Fix inverted logic for in-memory key use --- AndroidManifest.xml | 2 +- src/org/connectbot/ConsoleActivity.java | 327 ++++++++------ src/org/connectbot/HostEditorActivity.java | 8 +- src/org/connectbot/HostListActivity.java | 4 +- src/org/connectbot/PortForwardListActivity.java | 12 +- src/org/connectbot/PubkeyListActivity.java | 6 +- src/org/connectbot/TerminalView.java | 27 +- .../connectbot/service/ConnectivityReceiver.java | 155 +++++++ src/org/connectbot/service/PromptHelper.java | 6 +- src/org/connectbot/service/TerminalBridge.java | 486 +++----------------- .../connectbot/service/TerminalKeyListener.java | 490 +++++++++++++++++++++ src/org/connectbot/service/TerminalManager.java | 236 +++++++--- src/org/connectbot/transport/AbsTransport.java | 5 + src/org/connectbot/transport/Local.java | 8 + src/org/connectbot/transport/SSH.java | 16 +- src/org/connectbot/transport/Telnet.java | 8 + 16 files changed, 1136 insertions(+), 660 deletions(-) create mode 100644 src/org/connectbot/service/ConnectivityReceiver.java create mode 100644 src/org/connectbot/service/TerminalKeyListener.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 803e2a4..752bc42 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionCode="263"> = 0) { + if (flip.getDisplayedChild() == flipIndex) { + shiftCurrentTerminal(SHIFT_LEFT); + } + flip.removeViewAt(flipIndex); /* TODO Remove this workaround when ViewFlipper is fixed to listen * to view removals. Android Issue 1784 */ final int numChildren = flip.getChildCount(); if (flip.getDisplayedChild() >= numChildren && - numChildren > 0) + numChildren > 0) { flip.setDisplayedChild(numChildren - 1); + } updateEmptyVisible(); - break; } - } - // If we just closed the last bridge, go back to the previous activity. - if (flip.getChildCount() == 0) { - finish(); + // If we just closed the last bridge, go back to the previous activity. + if (flip.getChildCount() == 0) { + finish(); + } } } @@ -429,20 +394,20 @@ public class ConsoleActivity extends Activity { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - float distx = e2.getRawX() - e1.getRawX(); - float disty = e2.getRawY() - e1.getRawY(); - int goalwidth = flip.getWidth() / 2; + final float distx = e2.getRawX() - e1.getRawX(); + final float disty = e2.getRawY() - e1.getRawY(); + final int goalwidth = flip.getWidth() / 2; // need to slide across half of display to trigger console change // make sure user kept a steady hand horizontally - if(Math.abs(disty) < 100) { - if(distx > goalwidth) { - shiftRight(); + if (Math.abs(disty) < (flip.getHeight() / 4)) { + if (distx > goalwidth) { + shiftCurrentTerminal(SHIFT_RIGHT); return true; } - if(distx < -goalwidth) { - shiftLeft(); + if (distx < -goalwidth) { + shiftCurrentTerminal(SHIFT_LEFT); return true; } @@ -463,12 +428,12 @@ public class ConsoleActivity extends Activity { return false; // if releasing then reset total scroll - if(e2.getAction() == MotionEvent.ACTION_UP) { + if (e2.getAction() == MotionEvent.ACTION_UP) { totalY = 0; } // activate consider if within x tolerance - if(Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) { + if (Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) { View flip = findCurrentView(R.id.console_flip); if(flip == null) return false; @@ -477,11 +442,11 @@ public class ConsoleActivity extends Activity { // estimate how many rows we have scrolled through // accumulate distance that doesn't trigger immediate scroll totalY += distanceY; - int moved = (int)(totalY / terminal.bridge.charHeight); + final int moved = (int)(totalY / terminal.bridge.charHeight); // consume as scrollback only if towards right half of screen - if (e2.getX() > flip.getWidth() / 2.0) { - if(moved != 0) { + if (e2.getX() > flip.getWidth() / 2) { + if (moved != 0) { int base = terminal.bridge.buffer.getWindowBase(); terminal.bridge.buffer.setWindowBase(base + moved); totalY = 0; @@ -489,12 +454,12 @@ public class ConsoleActivity extends Activity { } } else { // otherwise consume as pgup/pgdown for every 5 lines - if(moved > 5) { + if (moved > 5) { ((vt320)terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0); terminal.bridge.tryKeyVibrate(); totalY = 0; return true; - } else if(moved < -5) { + } else if (moved < -5) { ((vt320)terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0); terminal.bridge.tryKeyVibrate(); totalY = 0; @@ -868,6 +833,54 @@ public class ConsoleActivity extends Activity { bound.setResizeAllowed(true); } + /* (non-Javadoc) + * @see android.app.Activity#onNewIntent(android.content.Intent) + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + Log.d(TAG, "onNewIntent called"); + + requested = intent.getData(); + + if (requested == null) { + Log.e(TAG, "Got null intent data in onNewIntent()"); + return; + } + + if (bound == null) { + Log.e(TAG, "We're not bound in onNewIntent()"); + return; + } + + TerminalBridge requestedBridge = bound.getConnectedBridge(requested.getFragment()); + int requestedIndex = 0; + + synchronized (flip) { + if (requestedBridge == null) { + // If we didn't find the requested connection, try opening it + + try { + Log.d(TAG, String.format("We couldnt find an existing bridge with URI=%s (nickname=%s),"+ + "so creating one now", requested.toString(), requested.getFragment())); + requestedBridge = bound.openConnection(requested); + } catch(Exception e) { + Log.e(TAG, "Problem while trying to create new requested bridge from URI", e); + } + + requestedIndex = addNewTerminalView(requestedBridge); + } else { + final int flipIndex = getFlipIndex(requestedBridge); + if (flipIndex > requestedIndex) { + requestedIndex = flipIndex; + } + } + + setDisplayedTerminal(requestedIndex); + } + } + @Override public void onStop() { super.onStop(); @@ -879,60 +892,40 @@ public class ConsoleActivity extends Activity { wakelock.release(); } - protected void shiftLeft() { + protected void shiftCurrentTerminal(final int direction) { View overlay; - boolean shouldAnimate = flip.getChildCount() > 1; - - // Only show animation if there is something else to go to. - if (shouldAnimate) { - // keep current overlay from popping up again - overlay = findCurrentView(R.id.terminal_overlay); - if (overlay != null) - overlay.startAnimation(fade_stay_hidden); - - flip.setInAnimation(slide_left_in); - flip.setOutAnimation(slide_left_out); - flip.showNext(); - } - - ConsoleActivity.this.updateDefault(); - - if (shouldAnimate) { - // show overlay on new slide and start fade - overlay = findCurrentView(R.id.terminal_overlay); - if (overlay != null) - overlay.startAnimation(fade_out_delayed); - } - - updatePromptVisible(); - } + synchronized (flip) { + boolean shouldAnimate = flip.getChildCount() > 1; + + // Only show animation if there is something else to go to. + if (shouldAnimate) { + // keep current overlay from popping up again + overlay = findCurrentView(R.id.terminal_overlay); + if (overlay != null) + overlay.startAnimation(fade_stay_hidden); + + if (direction == SHIFT_LEFT) { + flip.setInAnimation(slide_left_in); + flip.setOutAnimation(slide_left_out); + flip.showNext(); + } else if (direction == SHIFT_RIGHT) { + flip.setInAnimation(slide_right_in); + flip.setOutAnimation(slide_right_out); + flip.showPrevious(); + } + } - protected void shiftRight() { - View overlay; - boolean shouldAnimate = flip.getChildCount() > 1; - - // Only show animation if there is something else to go to. - if (shouldAnimate) { - // keep current overlay from popping up again - overlay = findCurrentView(R.id.terminal_overlay); - if (overlay != null) - overlay.startAnimation(fade_stay_hidden); - - flip.setInAnimation(slide_right_in); - flip.setOutAnimation(slide_right_out); - flip.showPrevious(); - } + ConsoleActivity.this.updateDefault(); - ConsoleActivity.this.updateDefault(); + if (shouldAnimate) { + // show overlay on new slide and start fade + overlay = findCurrentView(R.id.terminal_overlay); + if (overlay != null) + overlay.startAnimation(fade_out_delayed); + } - if (shouldAnimate) { - // show overlay on new slide and start fade - overlay = findCurrentView(R.id.terminal_overlay); - if (overlay != null) - overlay.startAnimation(fade_out_delayed); + updatePromptVisible(); } - - updatePromptVisible(); } /** @@ -1039,6 +1032,80 @@ public class ConsoleActivity extends Activity { bound.setResizeAllowed(false); else bound.setResizeAllowed(true); + + bound.hardKeyboardHidden = (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES); + } + } + + /** + * Adds a new TerminalBridge to the current set of views in our ViewFlipper. + * + * @param bridge TerminalBridge to add to our ViewFlipper + * @return the child index of the new view in the ViewFlipper + */ + private int addNewTerminalView(TerminalBridge bridge) { + // let them know about our prompt handler services + bridge.promptHelper.setHandler(promptHandler); + + // inflate each terminal view + RelativeLayout view = (RelativeLayout)inflater.inflate(R.layout.item_terminal, flip, false); + + // set the terminal overlay text + TextView overlay = (TextView)view.findViewById(R.id.terminal_overlay); + overlay.setText(bridge.host.getNickname()); + + // and add our terminal view control, using index to place behind overlay + TerminalView terminal = new TerminalView(ConsoleActivity.this, bridge); + terminal.setId(R.id.console_flip); + view.addView(terminal, 0); + + synchronized (flip) { + // finally attach to the flipper + flip.addView(view); + return flip.getChildCount() - 1; + } + } + + private int getFlipIndex(TerminalBridge bridge) { + synchronized (flip) { + final int children = flip.getChildCount(); + for (int i = 0; i < children; i++) { + final View view = flip.getChildAt(i).findViewById(R.id.console_flip); + + if (view == null || !(view instanceof TerminalView)) { + // How did that happen? + continue; + } + + final TerminalView tv = (TerminalView) view; + + if (tv.bridge == bridge) { + return i; + } + } + } + + return -1; + } + + /** + * Displays the child in the ViewFlipper at the requestedIndex and updates the prompts. + * + * @param requestedIndex the index of the terminal view to display + */ + private void setDisplayedTerminal(int requestedIndex) { + synchronized (flip) { + try { + // show the requested bridge if found, also fade out overlay + flip.setDisplayedChild(requestedIndex); + flip.getCurrentView().findViewById(R.id.terminal_overlay) + .startAnimation(fade_out_delayed); + } catch (NullPointerException npe) { + Log.d(TAG, "View went away when we were about to display it", npe); + } + + updatePromptVisible(); + updateEmptyVisible(); } } } diff --git a/src/org/connectbot/HostEditorActivity.java b/src/org/connectbot/HostEditorActivity.java index e8202e2..95d04cc 100644 --- a/src/org/connectbot/HostEditorActivity.java +++ b/src/org/connectbot/HostEditorActivity.java @@ -240,13 +240,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr public void onServiceConnected(ComponentName className, IBinder service) { TerminalManager bound = ((TerminalManager.TerminalBinder) service).getService(); - for (TerminalBridge bridge: bound.bridges) { - if (bridge.host.equals(host)) { - hostBridge = bridge; - Log.d(TAG, "Found host bridge; charset updates will be made live"); - break; - } - } + hostBridge = bound.getConnectedBridge(host); } public void onServiceDisconnected(ComponentName name) { diff --git a/src/org/connectbot/HostListActivity.java b/src/org/connectbot/HostListActivity.java index c477a58..94cc1ad 100644 --- a/src/org/connectbot/HostListActivity.java +++ b/src/org/connectbot/HostListActivity.java @@ -330,7 +330,7 @@ public class HostListActivity extends ListActivity { // edit, disconnect, delete MenuItem connect = menu.add(R.string.list_host_disconnect); - final TerminalBridge bridge = bound.findBridge(host); + final TerminalBridge bridge = bound.getConnectedBridge(host); connect.setEnabled((bridge != null)); connect.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { @@ -473,7 +473,7 @@ public class HostListActivity extends ListActivity { if (this.manager == null) return STATE_UNKNOWN; - if (manager.findBridge(host) != null) + if (manager.getConnectedBridge(host) != null) return STATE_CONNECTED; if (manager.disconnected.contains(host)) diff --git a/src/org/connectbot/PortForwardListActivity.java b/src/org/connectbot/PortForwardListActivity.java index 2cb5dbc..137030a 100644 --- a/src/org/connectbot/PortForwardListActivity.java +++ b/src/org/connectbot/PortForwardListActivity.java @@ -132,16 +132,8 @@ public class PortForwardListActivity extends ListActivity { public void onServiceConnected(ComponentName className, IBinder service) { TerminalManager bound = ((TerminalManager.TerminalBinder) service).getService(); - for (TerminalBridge bridge: bound.bridges) { - if (bridge.host.equals(host)) { - hostBridge = bridge; - updateHandler.sendEmptyMessage(-1); - Log.d(TAG, "Found host bridge; using that instead of database"); - break; - } - } - - + hostBridge = bound.getConnectedBridge(host); + updateHandler.sendEmptyMessage(-1); } public void onServiceDisconnected(ComponentName name) { diff --git a/src/org/connectbot/PubkeyListActivity.java b/src/org/connectbot/PubkeyListActivity.java index b445d18..cd4d255 100644 --- a/src/org/connectbot/PubkeyListActivity.java +++ b/src/org/connectbot/PubkeyListActivity.java @@ -292,10 +292,8 @@ public class PubkeyListActivity extends ListActivity implements EventListener { Log.d(TAG, String.format("Unlocked key '%s'", pubkey.getNickname())); - // save this key in-memory if option enabled - if(bound.isSavingKeys()) { - bound.addKey(pubkey, trileadKey); - } + // save this key in memory + bound.addKey(pubkey, trileadKey); updateHandler.sendEmptyMessage(-1); } 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/ConnectivityReceiver.java b/src/org/connectbot/service/ConnectivityReceiver.java new file mode 100644 index 0000000..e2d2d3b --- /dev/null +++ b/src/org/connectbot/service/ConnectivityReceiver.java @@ -0,0 +1,155 @@ +/** + * + */ +package org.connectbot.service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiManager.WifiLock; +import android.util.Log; + +/** + * @author kroot + * + */ +public class ConnectivityReceiver extends BroadcastReceiver { + private static final String TAG = "ConnectBot.ConnectivityManager"; + + private boolean mIsConnected = false; + + final private TerminalManager mTerminalManager; + + final private WifiLock mWifiLock; + + private int mNetworkRef = 0; + + private boolean mLockingWifi; + + public ConnectivityReceiver(TerminalManager manager, boolean lockingWifi) { + mTerminalManager = manager; + + final ConnectivityManager cm = + (ConnectivityManager) manager.getSystemService(Context.CONNECTIVITY_SERVICE); + + final WifiManager wm = (WifiManager) manager.getSystemService(Context.WIFI_SERVICE); + mWifiLock = wm.createWifiLock(TAG); + + final NetworkInfo info = cm.getActiveNetworkInfo(); + if (info != null) { + mIsConnected = (info.getState() == State.CONNECTED); + } + + mLockingWifi = lockingWifi; + + final IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + manager.registerReceiver(this, filter); + } + + /* (non-Javadoc) + * @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent) + */ + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + + if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + Log.w(TAG, "onReceived() called: " + intent); + return; + } + + boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); + boolean isFailover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false); + + Log.d(TAG, "onReceived() called; noConnectivity? " + noConnectivity + "; isFailover? " + isFailover); + + if (noConnectivity && !isFailover && mIsConnected) { + mIsConnected = false; + mTerminalManager.onConnectivityLost(); + } else if (!mIsConnected) { + NetworkInfo info = (NetworkInfo) intent.getExtras() + .get(ConnectivityManager.EXTRA_NETWORK_INFO); + + if (mIsConnected = (info.getState() == State.CONNECTED)) { + mTerminalManager.onConnectivityRestored(); + } + } + } + + /** + * + */ + public void cleanup() { + if (mWifiLock.isHeld()) + mWifiLock.release(); + + mTerminalManager.unregisterReceiver(this); + } + + /** + * Increase the number of things using the network. Acquire a Wifi lock if necessary. + */ + public void incRef() { + synchronized (this) { + acquireWifiLockIfNecessary(); + + mNetworkRef += 1; + } + } + + public void decRef() { + synchronized (this) { + mNetworkRef -= 1; + + releaseWifiLockIfNecessary(); + } + } + + /** + * @param mLockingWifi + */ + public void setWantWifiLock(boolean lockingWifi) { + synchronized (this) { + mLockingWifi = lockingWifi; + + if (mLockingWifi) { + acquireWifiLockIfNecessary(); + } else if (!mLockingWifi) { + releaseWifiLockIfNecessary(); + } + } + } + + /** + * + */ + private void acquireWifiLockIfNecessary() { + synchronized (this) { + if (mLockingWifi && mNetworkRef > 0 && !mWifiLock.isHeld()) { + mWifiLock.acquire(); + } + } + } + + /** + * + */ + private void releaseWifiLockIfNecessary() { + if (mNetworkRef == 0 && mWifiLock.isHeld()) { + mWifiLock.release(); + } + } + + /** + * @return whether we're connected to a network + */ + public boolean isConnected() { + return mIsConnected; + } +} diff --git a/src/org/connectbot/service/PromptHelper.java b/src/org/connectbot/service/PromptHelper.java index 41082c7..f0a37be 100644 --- a/src/org/connectbot/service/PromptHelper.java +++ b/src/org/connectbot/service/PromptHelper.java @@ -66,6 +66,9 @@ public class PromptHelper { */ public void setResponse(Object value) { response = value; + promptRequested = null; + promptInstructions = null; + promptHint = null; promptResponse.release(); } @@ -101,9 +104,6 @@ public class PromptHelper { // acquire lock until user passes back value promptResponse.acquire(); - promptInstructions = null; - promptHint = null; - promptRequested = null; response = popResponse(); } finally { diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index c748046..e9e69ca 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, null); } /** @@ -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.getEncoding()); } public PromptHelper getPromptHelper() { @@ -336,6 +307,7 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { public void setCharset(String encoding) { if (relay != null) relay.setCharset(encoding); + keyListener.setCharset(encoding); } /** @@ -455,8 +427,12 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { if (disconnectListener != null) disconnectListener.onDisconnected(TerminalBridge.this); } else { + { + final String line = manager.res.getString(R.string.alert_disconnect_msg); + ((vt320) buffer).putString("\r\n" + line + "\r\n"); + } if (host.getStayConnected()) { - startConnection(); + manager.requestReconnect(this); return; } Thread disconnectPromptThread = new Thread(new Runnable() { @@ -478,394 +454,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; } @@ -886,7 +474,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; @@ -911,6 +499,8 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { host.setFontSize((int) fontSize); manager.hostdb.updateFontSize(host); + + forcedSize = false; } /** @@ -946,14 +536,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 @@ -1203,10 +794,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) { @@ -1348,4 +939,41 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener { return urls; } + + /** + * @return + */ + 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..21bd218 --- /dev/null +++ b/src/org/connectbot/service/TerminalKeyListener.java @@ -0,0 +1,490 @@ +/** + * + */ +package org.connectbot.service; + +import java.io.IOException; + +import org.connectbot.TerminalView; +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; + + 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 String encoding; + + private final SharedPreferences prefs; + + public TerminalKeyListener(TerminalManager manager, + TerminalBridge bridge, + VDUBuffer 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(); + } + + /** + * 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(encoding)); + + return true; + } + + if (keyCode == KeyEvent.KEYCODE_UNKNOWN && + event.getAction() == KeyEvent.ACTION_MULTIPLE) { + byte[] input = event.getCharacters().getBytes(encoding); + 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); + } + + public void setCharset(String encoding) { + this.encoding = encoding; + } +} diff --git a/src/org/connectbot/service/TerminalManager.java b/src/org/connectbot/service/TerminalManager.java index 438636e..c378b14 100644 --- a/src/org/connectbot/service/TerminalManager.java +++ b/src/org/connectbot/service/TerminalManager.java @@ -18,6 +18,7 @@ package org.connectbot.service; import java.io.IOException; +import java.lang.ref.WeakReference; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Arrays; @@ -44,14 +45,12 @@ 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; import android.media.MediaPlayer.OnCompletionListener; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.Uri; -import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -73,6 +72,11 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen public final static String TAG = "ConnectBot.TerminalManager"; public List bridges = new LinkedList(); + public Map> mHostBridgeMap = + new HashMap>(); + public Map> mNicknameBridgeMap = + new HashMap>(); + public TerminalBridge defaultBridge = null; public List disconnected = new LinkedList(); @@ -88,10 +92,9 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen protected SharedPreferences prefs; - private final IBinder binder = new TerminalBinder(); + final private IBinder binder = new TerminalBinder(); - private ConnectivityManager connectivityManager; - private WifiManager.WifiLock wifilock; + private ConnectivityReceiver connectivityManager; private MediaPlayer mediaPlayer; @@ -108,6 +111,13 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen private boolean resizeAllowed = true; + private boolean savingKeys; + + protected List> mPendingReconnect + = new LinkedList>(); + + public boolean hardKeyboardHidden; + @Override public void onCreate() { Log.i(TAG, "Starting background service"); @@ -139,29 +149,31 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen } } - connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - - WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE); - wifilock = manager.createWifiLock(TAG); - vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); wantKeyVibration = prefs.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); 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); + + updateSavingKeys(); + } + + private void updateSavingKeys() { + savingKeys = prefs.getBoolean(PreferenceConstants.MEMKEYS, true); } @Override public void onDestroy() { Log.i(TAG, "Destroying background service"); - if (bridges.size() > 0) { - TerminalBridge[] tmpBridges = bridges.toArray(new TerminalBridge[bridges.size()]); - - // disconnect and dispose of any existing bridges - for (int i = 0; i < tmpBridges.length; i++) - tmpBridges[i].dispatchDisconnect(true); - } + disconnectAll(true); if(hostdb != null) { hostdb.close(); @@ -180,8 +192,7 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen pubkeyTimer.cancel(); } - if (wifilock != null && wifilock.isHeld()) - wifilock.release(); + connectivityManager.cleanup(); ConnectionNotifier.getInstance().hideRunningNotification(this); @@ -189,30 +200,56 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen } /** + * Disconnect all currently connected bridges. + */ + private void disconnectAll(final boolean immediate) { + TerminalBridge[] tmpBridges = null; + + synchronized (bridges) { + if (bridges.size() > 0) { + tmpBridges = bridges.toArray(new TerminalBridge[bridges.size()]); + } + } + + if (tmpBridges != null) { + // disconnect and dispose of any existing bridges + for (int i = 0; i < tmpBridges.length; i++) + tmpBridges[i].dispatchDisconnect(immediate); + } + } + + /** * Open a new SSH session using the given parameters. */ - private void openConnection(HostBean host) throws IllegalArgumentException, IOException { + private TerminalBridge openConnection(HostBean host) throws IllegalArgumentException, IOException { // throw exception if terminal already open - if (findBridge(host) != null) { + if (getConnectedBridge(host) != null) { throw new IllegalArgumentException("Connection already open for that nickname"); } TerminalBridge bridge = new TerminalBridge(this, host); bridge.setOnDisconnectedListener(this); bridge.startConnection(); - bridges.add(bridge); - // Add a reference to the WifiLock - NetworkInfo info = connectivityManager.getActiveNetworkInfo(); - if (isLockingWifi() && - info != null && - info.getType() == ConnectivityManager.TYPE_WIFI) { - Log.d(TAG, "Acquiring WifiLock"); - wifilock.acquire(); + synchronized (bridges) { + bridges.add(bridge); + WeakReference wr = new WeakReference(bridge); + mHostBridgeMap.put(bridge.host, wr); + mNicknameBridgeMap.put(bridge.host.getNickname(), wr); + } + + synchronized (disconnected) { + disconnected.remove(bridge.host); + } + + if (bridge.isUsingNetwork()) { + connectivityManager.incRef(); } // also update database with new connected time touchHost(host); + + return bridge; } public String getEmulation() { @@ -228,29 +265,17 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen return scrollback; } - public boolean isSavingKeys() { - return prefs.getBoolean(PreferenceConstants.MEMKEYS, true); - } - - public String getKeyMode() { - return prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT); // "Use right-side keys" - } - - public boolean isLockingWifi() { - return prefs.getBoolean(PreferenceConstants.WIFI_LOCK, true); - } - /** * Open a new connection by reading parameters from the given URI. Follows * format specified by an individual transport. */ - public void openConnection(Uri uri) throws Exception { + public TerminalBridge openConnection(Uri uri) throws Exception { HostBean host = TransportFactory.findHost(hostdb, uri); if (host == null) host = TransportFactory.getTransport(uri.getScheme()).createHost(uri); - openConnection(host); + return openConnection(host); } /** @@ -262,30 +287,57 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen } /** - * Find the {@link TerminalBridge} with the given nickname. + * Find a connected {@link TerminalBridge} with the given HostBean. + * + * @param host the HostBean to search for + * @return TerminalBridge that uses the HostBean */ - public TerminalBridge findBridge(HostBean host) { - // find the first active bridge with given nickname - for(TerminalBridge bridge : bridges) { - if (bridge.host.equals(host)) - return bridge; + public TerminalBridge getConnectedBridge(HostBean host) { + WeakReference wr = mHostBridgeMap.get(host); + if (wr != null) { + return wr.get(); + } else { + return null; + } + } + + /** + * Find a connected {@link TerminalBridge} using its nickname. + * + * @param nickname + * @return TerminalBridge that matches nickname + */ + public TerminalBridge getConnectedBridge(final String nickname) { + if (nickname == null) { + return null; + } + WeakReference wr = mNicknameBridgeMap.get(nickname); + if (wr != null) { + return wr.get(); + } else { + return null; } - return null; } /** * Called by child bridge when somehow it's been disconnected. */ public void onDisconnected(TerminalBridge bridge) { - // remove this bridge from our list - bridges.remove(bridge); + synchronized (bridges) { + // remove this bridge from our list + bridges.remove(bridge); + + mHostBridgeMap.remove(bridge.host); + mNicknameBridgeMap.remove(bridge.host.getNickname()); - if (bridges.size() == 0 && wifilock.isHeld()) { - Log.d(TAG, "WifiLock was held, releasing"); - wifilock.release(); + if (bridge.isUsingNetwork()) { + connectivityManager.decRef(); + } } - disconnected.add(bridge.host); + synchronized (disconnected) { + disconnected.add(bridge.host); + } // pass notification back up to gui if (disconnectHandler != null) @@ -298,6 +350,9 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen } public void addKey(PubkeyBean pubkey, Object trileadKey) { + if (!savingKeys) + return; + removeKey(pubkey.getNickname()); byte[] sshPubKey = PubkeyUtils.extractOpenSSHPublic(trileadKey); @@ -555,6 +610,11 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen } else if (PreferenceConstants.BUMPY_ARROWS.equals(key)) { wantKeyVibration = sharedPreferences.getBoolean( PreferenceConstants.BUMPY_ARROWS, true); + } else if (PreferenceConstants.WIFI_LOCK.equals(key)) { + final boolean lockingWifi = prefs.getBoolean(PreferenceConstants.WIFI_LOCK, true); + connectivityManager.setWantWifiLock(lockingWifi); + } else if (PreferenceConstants.MEMKEYS.equals(key)) { + updateSavingKeys(); } } @@ -570,9 +630,69 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen return resizeAllowed; } - public class KeyHolder { + public static class KeyHolder { public PubkeyBean bean; public Object trileadKey; public byte[] openSSHPubkey; } + + /** + * Called when connectivity to the network is lost and it doesn't appear + * we'll be getting a different connection any time soon. + */ + public void onConnectivityLost() { + final Thread t = new Thread() { + @Override + public void run() { + disconnectAll(false); + } + }; + t.start(); + } + + /** + * Called when connectivity to the network is restored. + */ + public void onConnectivityRestored() { + final Thread t = new Thread() { + @Override + public void run() { + reconnectPending(); + } + }; + t.start(); + } + + /** + * Insert request into reconnect queue to be executed either immediately + * or later when connectivity is restored depending on whether we're + * currently connected. + * + * @param bridge the TerminalBridge to reconnect when possible + */ + public void requestReconnect(TerminalBridge bridge) { + synchronized (mPendingReconnect) { + mPendingReconnect.add(new WeakReference(bridge)); + if (connectivityManager.isConnected()) { + reconnectPending(); + } + } + } + + /** + * Reconnect all bridges that were pending a reconnect when connectivity + * was lost. + */ + private void reconnectPending() { + synchronized (mPendingReconnect) { + for (WeakReference ref : mPendingReconnect) { + TerminalBridge bridge = ref.get(); + if (bridge == null) { + continue; + } + bridge.startConnection(); + } + mPendingReconnect.clear(); + } + } } diff --git a/src/org/connectbot/transport/AbsTransport.java b/src/org/connectbot/transport/AbsTransport.java index fbd4655..18397ea 100644 --- a/src/org/connectbot/transport/AbsTransport.java +++ b/src/org/connectbot/transport/AbsTransport.java @@ -246,4 +246,9 @@ public abstract class AbsTransport { public static String getFormatHint(Context context) { return "???"; } + + /** + * @return + */ + public abstract boolean usesNetwork(); } diff --git a/src/org/connectbot/transport/Local.java b/src/org/connectbot/transport/Local.java index 46903b2..5ace1b0 100644 --- a/src/org/connectbot/transport/Local.java +++ b/src/org/connectbot/transport/Local.java @@ -208,4 +208,12 @@ public class Local extends AbsTransport { public static String getFormatHint(Context context) { return context.getString(R.string.hostpref_nickname_title); } + + /* (non-Javadoc) + * @see org.connectbot.transport.AbsTransport#usesNetwork() + */ + @Override + public boolean usesNetwork() { + return false; + } } diff --git a/src/org/connectbot/transport/SSH.java b/src/org/connectbot/transport/SSH.java index 11e0659..8a90f72 100644 --- a/src/org/connectbot/transport/SSH.java +++ b/src/org/connectbot/transport/SSH.java @@ -299,7 +299,7 @@ public class SSH extends AbsTransport implements ConnectionMonitor, InteractiveC Log.d(TAG, String.format("Found unlocked key '%s' already in-memory", pubkey.getNickname())); if (pubkey.isConfirmUse()) { - if (promptForPubkeyUse(pubkey.getNickname())) + if (!promptForPubkeyUse(pubkey.getNickname())) return false; } @@ -342,10 +342,8 @@ public class SSH extends AbsTransport implements ConnectionMonitor, InteractiveC Log.d(TAG, String.format("Unlocked key '%s'", pubkey.getNickname())); - // save this key in-memory if option enabled - if(manager.isSavingKeys()) { - manager.addKey(pubkey, trileadKey); - } + // save this key in memory + manager.addKey(pubkey, trileadKey); } return tryPublicKey(host.getUsername(), pubkey.getNickname(), trileadKey); @@ -944,4 +942,12 @@ public class SSH extends AbsTransport implements ConnectionMonitor, InteractiveC agentLockPassphrase = lockPassphrase; return true; } + + /* (non-Javadoc) + * @see org.connectbot.transport.AbsTransport#usesNetwork() + */ + @Override + public boolean usesNetwork() { + return true; + } } diff --git a/src/org/connectbot/transport/Telnet.java b/src/org/connectbot/transport/Telnet.java index b7388ad..5fde2f6 100644 --- a/src/org/connectbot/transport/Telnet.java +++ b/src/org/connectbot/transport/Telnet.java @@ -319,4 +319,12 @@ public class Telnet extends AbsTransport { context.getString(R.string.format_hostname), context.getString(R.string.format_port)); } + + /* (non-Javadoc) + * @see org.connectbot.transport.AbsTransport#usesNetwork() + */ + @Override + public boolean usesNetwork() { + return true; + } } -- 2.11.0