Mercurial > 510Connectbot
view src/com/five_ten_sg/connectbot/transport/TN5250.java @ 52:0e3fc85d0586 tn5250
start tn5250 integration
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Wed, 11 Jun 2014 12:18:18 -0700 |
parents | 8c6de858bb73 |
children | e872762ec105 |
line wrap: on
line source
/* * 510ConnectBot * Copyright 2014 Carl Byington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.five_ten_sg.connectbot.transport; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.tn5250j.framework.tn5250.Screen5250; import org.tn5250j.framework.tn5250.tnvt; import com.five_ten_sg.connectbot.R; import com.five_ten_sg.connectbot.bean.HostBean; import com.five_ten_sg.connectbot.bean.PortForwardBean; import com.five_ten_sg.connectbot.service.TerminalBridge; import com.five_ten_sg.connectbot.service.TerminalKeyListener; import com.five_ten_sg.connectbot.service.TerminalManager; import com.five_ten_sg.connectbot.util.HostDatabase; import com.five_ten_sg.connectbot.util.PreferenceConstants; import android.content.Context; import android.net.Uri; import android.util.Log; import android.view.KeyEvent; import android.view.View; import de.mud.terminal.vt320; /** * @author Carl Byington * */ public class TN5250 extends AbsTransport { private static final String PROTOCOL = "tn5250"; private static final String TAG = "ConnectBot.tn5250"; private static final int DEFAULT_PORT = 23; private Screen5250 screen52; private tnvt handler = null; private Socket socket; private boolean connected = false; static final Pattern hostmask; static { hostmask = Pattern.compile("^([0-9a-z.-]+)(:(\\d+))?$", Pattern.CASE_INSENSITIVE); } class vt320x5250 extends vt320 { @Override public void debug(String s) { Log.d(TAG, s); } @Override public void write(byte[] b) { screen52.sendKeys(new String(b)); } @Override public void write(int b) { screen52.sendKeys(new String(new byte[] {(byte)b})); } // bridge.monitor placement of new characters @Override public void putChar(int c, int l, char ch, int attributes) { if (bridge.monitor != null) bridge.monitor.screenChanged(l, c); super.putChar(c, l, ch, attributes); } @Override public void setCursorPosition(int c, int l) { if (bridge.monitor != null) bridge.monitor.cursorMove(l, c); super.setCursorPosition(c, l); } }; class Terminal5250KeyListener extends TerminalKeyListener { public Terminal5250KeyListener(TerminalManager manager, TerminalBridge bridge, vt320 buffer, String encoding) { super(manager, bridge, buffer, encoding); } /** * Handle onKey() events coming down from a {@link com.five_ten_sg.connectbot.TerminalView} above us. * Modify the keys to make more sense to a host then pass it to the 5250. */ public boolean onKey(View v, int keyCode, KeyEvent event) { try { // skip keys if we aren't connected yet or have been disconnected if (bridge.isDisconnected()) return false; // 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 || hardKeyboardHidden) return false; // if keycode debugging enabled, log and print the pressed key if (prefs.getBoolean(PreferenceConstants.DEBUG_KEYCODES, false)) { String keyCodeString = String.format(": %d", keyCode); String toastText = v.getContext().getString(R.string.keycode_pressed) + keyCodeString; Log.d(TAG, toastText); } if (fullKeyboard()) { switch (keyCode) { case KeyEvent.KEYCODE_CTRL_LEFT: case KeyEvent.KEYCODE_CTRL_RIGHT: metaKeyUp(META_CTRL_ON); return true; case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: metaKeyUp(META_ALT_ON); return true; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: metaKeyUp(META_SHIFT_ON); return true; default: } } else if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) { if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT && (metaState & META_SLASH) != 0) { metaState &= ~(META_SLASH | META_TRANSIENT); buffer.write('/'); return true; } else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT && (metaState & META_TAB) != 0) { metaState &= ~(META_TAB | META_TRANSIENT); sendEncoded("[tab]"); return true; } } else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) { if (keyCode == KeyEvent.KEYCODE_ALT_LEFT && (metaState & META_SLASH) != 0) { metaState &= ~(META_SLASH | META_TRANSIENT); buffer.write('/'); return true; } else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT && (metaState & META_TAB) != 0) { metaState &= ~(META_TAB | META_TRANSIENT); sendEncoded("[tab]"); return true; } } return false; } bridge.resetScrollPosition(); if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) { byte[] input = event.getCharacters().getBytes(encoding); buffer.write(input); return true; } int curMetaState = event.getMetaState(); final int orgMetaState = curMetaState; if ((metaState & META_SHIFT_MASK) != 0) { curMetaState |= KeyEvent.META_SHIFT_ON; } if ((metaState & META_ALT_MASK) != 0) { curMetaState |= KeyEvent.META_ALT_ON; } int uchar = event.getUnicodeChar(curMetaState); // no hard keyboard? ALT-k should pass through to below if ((orgMetaState & KeyEvent.META_ALT_ON) != 0 && (!hardKeyboard || hardKeyboardHidden)) { uchar = 0; } if ((uchar & KeyCharacterMap.COMBINING_ACCENT) != 0) { mDeadKey = uchar & KeyCharacterMap.COMBINING_ACCENT_MASK; return true; } if (mDeadKey != 0 && uchar != 0) { uchar = KeyCharacterMap.getDeadChar(mDeadKey, uchar); mDeadKey = 0; } // handle customized keymaps if (customKeymapAction(v, keyCode, event)) return true; if (v != null) { //Show up the CharacterPickerDialog when the SYM key is pressed if ((isSymKey(keyCode) || uchar == KeyCharacterMap.PICKER_DIALOG_INPUT)) { bridge.showCharPickerDialog(); if (metaState == 4) { // reset fn-key state metaState = 0; bridge.redraw(); } return true; } else if (keyCode == KeyEvent.KEYCODE_SEARCH) { //Show up the URL scan dialog when the search key is pressed urlScan(v); return true; } } // otherwise pass through to existing session // print normal keys if (uchar > 0x00 && keyCode != KeyEvent.KEYCODE_ENTER) { metaState &= ~(META_SLASH | META_TAB); // Remove shift and alt modifiers final int lastMetaState = metaState; metaState &= ~(META_SHIFT_ON | META_ALT_ON); if (metaState != lastMetaState) { bridge.redraw(); } if ((metaState & META_CTRL_MASK) != 0) { metaState &= ~META_CTRL_ON; bridge.redraw(); // If there is no hard keyboard or there is a hard keyboard currently hidden, // CTRL-1 through CTRL-9 will send F1 through F9 if ((!hardKeyboard || hardKeyboardHidden) && sendFunctionKey(keyCode)) return true; uchar = keyAsControl(uchar); } // handle pressing f-keys if ((hardKeyboard && !hardKeyboardHidden) && (curMetaState & KeyEvent.META_ALT_ON) != 0 && (curMetaState & KeyEvent.META_SHIFT_ON) != 0 && sendFunctionKey(keyCode)) return true; if (uchar < 0x80) buffer.write(uchar); else sendEncoded(new String(Character.toChars(uchar))); return true; } // send ctrl and meta-keys as appropriate if (!hardKeyboard || hardKeyboardHidden) { int k = event.getUnicodeChar(0); int k0 = k; boolean sendCtrl = false; boolean sendMeta = false; if (k != 0) { if ((orgMetaState & HC_META_CTRL_ON) != 0) { k = keyAsControl(k); if (k != k0) sendCtrl = true; // send F1-F10 via CTRL-1 through CTRL-0 if (!sendCtrl && sendFunctionKey(keyCode)) return true; } else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) { sendMeta = true; buffer.write(0x1b); } if (sendMeta || sendCtrl) { buffer.write(k); return true; } } } // handle meta and f-keys for full hardware keyboard if (hardKeyboard && !hardKeyboardHidden && fullKeyboard()) { int k = event.getUnicodeChar(orgMetaState & KeyEvent.META_SHIFT_ON); int k0 = k; if (k != 0) { if ((orgMetaState & HC_META_CTRL_ON) != 0) { k = keyAsControl(k); if (k != k0) buffer.write(k); return true; } else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) { buffer.write(0x1b); buffer.write(k); return true; } } if (sendFullSpecialKey(keyCode)) return true; } // try handling keymode shortcuts if (hardKeyboard && !hardKeyboardHidden && event.getRepeatCount() == 0) { if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) { switch (keyCode) { case KeyEvent.KEYCODE_ALT_RIGHT: metaState |= META_SLASH; return true; case KeyEvent.KEYCODE_SHIFT_RIGHT: metaState |= META_TAB; return true; case KeyEvent.KEYCODE_SHIFT_LEFT: metaPress(META_SHIFT_ON); return true; case KeyEvent.KEYCODE_ALT_LEFT: metaPress(META_ALT_ON); return true; } } else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) { switch (keyCode) { case KeyEvent.KEYCODE_ALT_LEFT: metaState |= META_SLASH; return true; case KeyEvent.KEYCODE_SHIFT_LEFT: metaState |= META_TAB; return true; case KeyEvent.KEYCODE_SHIFT_RIGHT: metaPress(META_SHIFT_ON); return true; case KeyEvent.KEYCODE_ALT_RIGHT: metaPress(META_ALT_ON); return true; } } else { switch (keyCode) { case KeyEvent.KEYCODE_ALT_RIGHT: case KeyEvent.KEYCODE_ALT_LEFT: metaPress(META_ALT_ON); return true; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: metaPress(META_SHIFT_ON); return true; } } // Handle hardware CTRL keys if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT || keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) { ctrlKeySpecial(); return true; } } // look for special chars switch (keyCode) { case KEYCODE_ESCAPE: buffer.write(0x1b); return true; case KeyEvent.KEYCODE_TAB: sendEncoded("[tab]"); return true; case KEYCODE_PAGE_DOWN: sendEncoded("[pgdown]"); metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); return true; case KEYCODE_PAGE_UP: sendEncoded("[pgup]"); metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); return true; case KeyEvent.KEYCODE_MOVE_HOME: sendEncoded("[home]"); metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); return true; case KeyEvent.KEYCODE_MOVE_END: sendEncoded("[end]"); // does not exist!! metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); return true; case KeyEvent.KEYCODE_CAMERA: // check to see which shortcut the camera button triggers String hwbuttonShortcut = manager.prefs.getString( PreferenceConstants.CAMERA, PreferenceConstants.HWBUTTON_SCREEN_CAPTURE); return (handleShortcut(v, hwbuttonShortcut)); case KeyEvent.KEYCODE_VOLUME_UP: // check to see which shortcut the volume button triggers hwbuttonShortcut = manager.prefs.getString( PreferenceConstants.VOLUP, PreferenceConstants.HWBUTTON_CTRL); return (handleShortcut(v, hwbuttonShortcut)); case KeyEvent.KEYCODE_VOLUME_DOWN: // check to see which shortcut the camera button triggers hwbuttonShortcut = manager.prefs.getString( PreferenceConstants.VOLDN, PreferenceConstants.HWBUTTON_TAB); return (handleShortcut(v, hwbuttonShortcut)); case KeyEvent.KEYCODE_SEARCH: // check to see which shortcut the search button triggers hwbuttonShortcut = manager.prefs.getString( PreferenceConstants.SEARCH, PreferenceConstants.HWBUTTON_ESC); return (handleShortcut(v, hwbuttonShortcut)); case KeyEvent.KEYCODE_DEL: if ((metaState & META_ALT_MASK) != 0) { sendEncoded("[insert]"); } else { sendEncoded("[backspace]"); } metaState &= ~META_TRANSIENT; return true; case KeyEvent.KEYCODE_ENTER: sendEncoded("[enter]"); metaState &= ~META_TRANSIENT; return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (selectingForCopy) { selectionArea.decrementColumn(); bridge.redraw(); } else { if ((metaState & META_ALT_MASK) != 0) { sendEncoded("[home]"); } else { sendEncoded("[left]"); } metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); } return true; case KeyEvent.KEYCODE_DPAD_UP: if (selectingForCopy) { selectionArea.decrementRow(); bridge.redraw(); } else { if ((metaState & META_ALT_MASK) != 0) { sendEncoded("[pgup]"); } else { sendEncoded("[up]"); } metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); } return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (selectingForCopy) { selectionArea.incrementRow(); bridge.redraw(); } else { if ((metaState & META_ALT_MASK) != 0) { sendEncoded("[pgdown]"); } else { sendEncoded("[down]"); } metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); } return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (selectingForCopy) { selectionArea.incrementColumn(); bridge.redraw(); } else { if ((metaState & META_ALT_MASK) != 0) { sendEncoded("[end]"); // does not exist!! } else { sendEncoded("[right]"); } metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); } return true; case KeyEvent.KEYCODE_DPAD_CENTER: ctrlKeySpecial(); return true; } } catch (IOException e) { Log.e(TAG, "Problem while trying to handle an onKey() event", e); try { 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; } } }; public TN5250() { super(); } /** * @return protocol part of the URI */ public static String getProtocolName() { return PROTOCOL; } /** * Encode the current transport into a URI that can be passed via intent calls. * @return URI to host */ public Uri getUri(String input) { Matcher matcher = hostmask.matcher(input); if (!matcher.matches()) return null; StringBuilder sb = new StringBuilder(); sb.append(PROTOCOL) .append("://") .append(matcher.group(1)); String portString = matcher.group(3); int port = DEFAULT_PORT; if (portString != null) { try { port = Integer.parseInt(portString); if (port < 1 || port > 65535) { port = DEFAULT_PORT; } } catch (NumberFormatException nfe) { // Keep the default port } } if (port != DEFAULT_PORT) { sb.append(':'); sb.append(port); } sb.append("/#") .append(Uri.encode(input)); Uri uri = Uri.parse(sb.toString()); return uri; } /** * Causes transport to connect to the target host. After connecting but before a * session is started, must call back to {@link TerminalBridge#onConnected()}. * After that call a session may be opened. */ @Override public void connect() { screen52 = new Screen5250(); handler = new tnvt(screen52, true, false, bridge, manager); handler.setSSLType("TLS"); screen52.setVT(handler); screen52.setBuffer(buffer); connected = handler.connect(host.getHostname(), host.getPort()); if (connected) bridge.onConnected(); } /** * Checks if read() will block. If there are no bytes remaining in * the underlying transport, return true. */ @Override public boolean willBlock() { // we don't use a relay thread between the transport and the vt320 buffer return true; } /** * Reads from the transport. Transport must support reading into a byte array * <code>buffer</code> at the start of <code>offset</code> and a maximum of * <code>length</code> bytes. If the remote host disconnects, throw an * {@link IOException}. * @param buffer byte buffer to store read bytes into * @param offset where to start writing in the buffer * @param length maximum number of bytes to read * @return number of bytes read * @throws IOException when remote host disconnects */ public int read(byte[] buffer, int offset, int length) throws IOException { // we don't use a relay thread between the transport and the vt320 buffer return 0; } /** * Writes to the transport. If the host is not yet connected, simply return without * doing anything. An {@link IOException} should be thrown if there is an error after * connection. * @param buffer bytes to write to transport * @throws IOException when there is a problem writing after connection */ public void write(byte[] buffer) throws IOException { } /** * Writes to the transport. See {@link #write(byte[])} for behavior details. * @param c character to write to the transport * @throws IOException when there is a problem writing after connection */ public void write(int c) throws IOException { } /** * Flushes the write commands to the transport. * @throws IOException when there is a problem writing after connection */ public void flush() throws IOException { } /** * Closes the connection to the terminal. */ public void close() { handler.disconnect(); connected = false; bridge.dispatchDisconnect(false); } /** * Tells the transport what dimensions the display is currently * @param columns columns of text * @param rows rows of text * @param width width in pixels * @param height height in pixels */ @Override public void setDimensions(int columns, int rows, int width, int height) { // do nothing } @Override public vt320 getTransportBuffer() { buffer = new vt320x5250(); return setupTransportBuffer(); } @Override public int getDefaultPort() { return DEFAULT_PORT; } @Override public boolean isConnected() { return connected; } @Override public boolean isSessionOpen() { return connected; } @Override public boolean isAuthenticated() { return connected; } @Override public String getDefaultNickname(String username, String hostname, int port) { if (port == DEFAULT_PORT) { return String.format("%s", hostname); } else { return String.format("%s:%d", hostname, port); } } @Override public void getSelectionArgs(Uri uri, Map<String, String> selection) { selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL); selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment()); selection.put(HostDatabase.FIELD_HOST_HOSTNAME, uri.getHost()); int port = uri.getPort(); if (port < 0) port = DEFAULT_PORT; selection.put(HostDatabase.FIELD_HOST_PORT, Integer.toString(port)); } @Override public HostBean createHost(Uri uri) { HostBean host = new HostBean(); host.setProtocol(PROTOCOL); host.setHostname(uri.getHost()); int port = uri.getPort(); if (port < 0) port = DEFAULT_PORT; host.setPort(port); String nickname = uri.getFragment(); if (nickname == null || nickname.length() == 0) { host.setNickname(getDefaultNickname(host.getUsername(), host.getHostname(), host.getPort())); } else { host.setNickname(uri.getFragment()); } return host; } public String getFormatHint(Context context) { return String.format("%s:%s", context.getString(R.string.format_hostname), context.getString(R.string.format_port)); } @Override public boolean usesNetwork() { return true; } @Override public boolean needsRelay() { // we don't use a relay thread between the transport and the vt320 buffer return false; } public TerminalKeyListener getTerminalKeyListener() { return new Terminal5250KeyListener(manager, bridge, buffer, host.getEncoding()); } }