diff options
Diffstat (limited to 'src/org/theb/ssh')
-rw-r--r-- | src/org/theb/ssh/ConnectionThread.java | 30 | ||||
-rw-r--r-- | src/org/theb/ssh/FeedbackUI.java | 25 | ||||
-rw-r--r-- | src/org/theb/ssh/HostDbProvider.java | 18 | ||||
-rw-r--r-- | src/org/theb/ssh/HostEditor.java | 18 | ||||
-rw-r--r-- | src/org/theb/ssh/HostsList.java | 18 | ||||
-rw-r--r-- | src/org/theb/ssh/InteractiveHostKeyVerifier.java | 18 | ||||
-rw-r--r-- | src/org/theb/ssh/JCTerminalView.java | 329 | ||||
-rw-r--r-- | src/org/theb/ssh/JTATerminalView.java | 323 | ||||
-rw-r--r-- | src/org/theb/ssh/PasswordDialog.java | 18 | ||||
-rw-r--r-- | src/org/theb/ssh/PreferencesDialog.java | 42 | ||||
-rw-r--r-- | src/org/theb/ssh/SecureShell.java | 370 | ||||
-rw-r--r-- | src/org/theb/ssh/ShellView.java | 77 | ||||
-rw-r--r-- | src/org/theb/ssh/Terminal.java | 31 | ||||
-rw-r--r-- | src/org/theb/ssh/TrileadConnectionThread.java | 164 |
14 files changed, 1126 insertions, 355 deletions
diff --git a/src/org/theb/ssh/ConnectionThread.java b/src/org/theb/ssh/ConnectionThread.java new file mode 100644 index 0000000..a201e62 --- /dev/null +++ b/src/org/theb/ssh/ConnectionThread.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ +package org.theb.ssh; + +import java.io.InputStream; +import java.io.OutputStream; + +public abstract class ConnectionThread extends Thread { + public ConnectionThread(FeedbackUI ui, String hostname, String username, int port) {} + public abstract void finish(); + public abstract InputStream getReader(); + public abstract OutputStream getWriter(); + public abstract void setPassword(String password); +} diff --git a/src/org/theb/ssh/FeedbackUI.java b/src/org/theb/ssh/FeedbackUI.java new file mode 100644 index 0000000..ba4ae03 --- /dev/null +++ b/src/org/theb/ssh/FeedbackUI.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ +package org.theb.ssh; + +public interface FeedbackUI { + public void connectionLost(Throwable reason); + public void setWaiting(boolean isWaiting, String title, String message); + public void askPassword(); +} diff --git a/src/org/theb/ssh/HostDbProvider.java b/src/org/theb/ssh/HostDbProvider.java index 20ec224..f0eac73 100644 --- a/src/org/theb/ssh/HostDbProvider.java +++ b/src/org/theb/ssh/HostDbProvider.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ package org.theb.ssh; import java.util.HashMap; diff --git a/src/org/theb/ssh/HostEditor.java b/src/org/theb/ssh/HostEditor.java index d642fd4..967cf5b 100644 --- a/src/org/theb/ssh/HostEditor.java +++ b/src/org/theb/ssh/HostEditor.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ package org.theb.ssh; import org.theb.provider.HostDb; diff --git a/src/org/theb/ssh/HostsList.java b/src/org/theb/ssh/HostsList.java index 4d9e398..7114497 100644 --- a/src/org/theb/ssh/HostsList.java +++ b/src/org/theb/ssh/HostsList.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ package org.theb.ssh; import org.theb.provider.HostDb; diff --git a/src/org/theb/ssh/InteractiveHostKeyVerifier.java b/src/org/theb/ssh/InteractiveHostKeyVerifier.java index a461724..7c21f80 100644 --- a/src/org/theb/ssh/InteractiveHostKeyVerifier.java +++ b/src/org/theb/ssh/InteractiveHostKeyVerifier.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ package org.theb.ssh; import com.trilead.ssh2.ServerHostKeyVerifier; diff --git a/src/org/theb/ssh/JCTerminalView.java b/src/org/theb/ssh/JCTerminalView.java new file mode 100644 index 0000000..47d12aa --- /dev/null +++ b/src/org/theb/ssh/JCTerminalView.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ +package org.theb.ssh; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PixelXorXfermode; +import android.graphics.Typeface; +import android.graphics.Paint.FontMetricsInt; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; + +import com.jcraft.jcterm.Emulator; +import com.jcraft.jcterm.EmulatorVT100; +import com.jcraft.jcterm.Term; + +public class JCTerminalView extends View implements Term, Terminal { + private final Paint mPaint; + private Bitmap mBitmap; + private Canvas mCanvas; + + private final Paint mCursorPaint; + + private Emulator emulator = null; + + private boolean mBold = false; + private boolean mUnderline = false; + private boolean mReverse = false; + + private int mDefaultForeground = Color.WHITE; + private int mDefaultBackground = Color.BLACK; + private int mForeground = Color.WHITE; + private int mBackground = Color.BLACK; + + private boolean mAntialias = true; + + private int mTermWidth = 80; + private int mTermHeight = 24; + + private int mCharHeight; + private int mCharWidth; + private int mDescent; + + + // Cursor location + private int x = 0; + private int y = 0; + + private final Object[] mColors = {Color.BLACK, Color.RED, Color.GREEN, Color.YELLOW, + Color.BLUE, Color.MAGENTA, Color.CYAN, Color.WHITE}; + + public JCTerminalView(Context c) { + super(c); + mPaint = new Paint(); + mPaint.setAntiAlias(mAntialias); + mPaint.setColor(mDefaultForeground); + + mCursorPaint = new Paint(); + mCursorPaint.setAntiAlias(mAntialias); + mCursorPaint.setColor(mDefaultForeground); + mCursorPaint.setXfermode(new PixelXorXfermode(mDefaultBackground)); + + setFont(Typeface.MONOSPACE); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mBitmap != null) { + canvas.drawBitmap(mBitmap, 0, 0, null); + + if (mCharHeight > 0 && y > mCharHeight) { + // Invert pixels for cursor position. + canvas.drawRect(x, y - mCharHeight, x + mCharWidth, y, mCursorPaint); + } + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + Log.d("SSH/TerminalView", "onSizeChanged called"); + Bitmap newBitmap = Bitmap.createBitmap(w, h, false); + Canvas newCanvas = new Canvas(); + + newCanvas.setDevice(newBitmap); + + if (mBitmap != null) + newCanvas.drawBitmap(mBitmap, 0, 0, mPaint); + + mBitmap = newBitmap; + mCanvas = newCanvas; + + setSize(w, h); + } + + private void setSize(int w, int h) { + int column = w / getCharWidth(); + int row = h / getCharHeight(); + + mTermWidth = column; + mTermHeight = row; + + if (emulator != null) + emulator.reset(); + + clear_area(0, 0, w, h); + + // TODO: finish this method + } + + private void setFont(Typeface typeface) { + mPaint.setTypeface(typeface); + mPaint.setTextSize(8); + FontMetricsInt fm = mPaint.getFontMetricsInt(); + mDescent = fm.descent; + + float[] widths = new float[1]; + mPaint.getTextWidths("X", widths); + mCharWidth = (int)widths[0]; + + // Is this right? + mCharHeight = Math.abs(fm.top) + Math.abs(fm.descent); + Log.d("SSH", "character height is " + mCharHeight); + // mCharHeight += mLineSpace * 2; + // mDescent += mLineSpace; + } + + public void beep() { + // TODO Auto-generated method stub + + } + + public void clear() { + mPaint.setColor(getBackgroundColor()); + mCanvas.drawRect(0, 0, mCanvas.getBitmapWidth(), + mCanvas.getBitmapHeight(), mPaint); + mPaint.setColor(getForegroundColor()); + } + + private int getBackgroundColor() { + if (mReverse) + return mForeground; + return mBackground; + } + + private int getForegroundColor() { + if (mReverse) + return mBackground; + return mForeground; + } + + public void clear_area(int x1, int y1, int x2, int y2) { + mPaint.setColor(getBackgroundColor()); + if (mCanvas != null) + mCanvas.drawRect(x1, y1, x2, y2, mPaint); + mPaint.setColor(getForegroundColor()); + } + + public void drawBytes(byte[] buf, int s, int len, int x, int y) { + String chars = null; + try { + chars = new String(buf, "ASCII"); + drawString(chars.substring(s, s+len), x, y); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + Log.e("SSH", "Can't convert bytes to ASCII"); + } + } + + public void drawString(String str, int x, int y) { + mPaint.setFakeBoldText(mBold); + mPaint.setUnderlineText(mUnderline); + mCanvas.drawText(str, x, y - mDescent, mPaint); + } + + public void draw_cursor() { + postInvalidate(); + } + + public int getCharHeight() { + return mCharHeight; + } + + public int getCharWidth() { + return mCharWidth; + } + + public Object getColor(int index) { + if (mColors == null || index < 0 || mColors.length <= index) + return null; + return mColors[index]; + } + + public int getColumnCount() { + return mTermWidth; + } + + public int getRowCount() { + return mTermHeight; + } + + public int getTermHeight() { + return mTermHeight * mCharHeight; + } + + public int getTermWidth() { + return mTermWidth * mCharWidth; + } + + public void redraw(int x, int y, int width, int height) { + //invalidate(x, y, x+width, y+height); + postInvalidate(); + } + + public void resetAllAttributes() { + mBold = false; + mUnderline = false; + mReverse = false; + + mBackground = mDefaultBackground; + mForeground = mDefaultForeground; + + if (mPaint != null) + mPaint.setColor(mForeground); + } + + public void scroll_area(int x, int y, int w, int h, int dx, int dy) { + // TODO: make scrolling more efficient (memory-wise) + mCanvas.drawBitmap(Bitmap.createBitmap(mBitmap, x, y, w, h), x+dx, y+dy, null); + } + + private int toColor(Object o) { + if (o instanceof Integer) { + return ((Integer)o).intValue(); + } + + if (o instanceof String) { + return Color.parseColor((String)o); + } + + return Color.WHITE; + } + + public void setBackGround(Object background) { + mBackground = toColor(background); + } + + public void setBold() { + mBold = true; + } + + public void setCursor(int x, int y) { + // Make sure we don't go outside the bounds of the window. + this.x = Math.max( + Math.min(x, getWidth() - mCharWidth), + 0); + this.y = Math.max( + Math.min(y, getHeight()), + mCharHeight); + } + + public void setDefaultBackGround(Object background) { + mDefaultBackground = toColor(background); + } + + public void setDefaultForeGround(Object foreground) { + mDefaultForeground = toColor(foreground); + } + + public void setForeGround(Object foreground) { + mForeground = toColor(foreground); + } + + public void setReverse() { + mReverse = true; + if (mPaint != null) + mPaint.setColor(getForegroundColor()); + } + + public void setUnderline() { + mUnderline = true; + } + + public void start(InputStream in, OutputStream out) { + emulator = new EmulatorVT100(this, in); + emulator.reset(); + emulator.start(); + + clear(); + } + + public byte[] getKeyCode(int keyCode, int meta) { + if (keyCode == KeyEvent.KEYCODE_NEWLINE) + return emulator.getCodeENTER(); + else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) + return emulator.getCodeLEFT(); + else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) + return emulator.getCodeUP(); + else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) + return emulator.getCodeDOWN(); + else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) + return emulator.getCodeRIGHT(); + else + return null; + } +} diff --git a/src/org/theb/ssh/JTATerminalView.java b/src/org/theb/ssh/JTATerminalView.java new file mode 100644 index 0000000..2238f02 --- /dev/null +++ b/src/org/theb/ssh/JTATerminalView.java @@ -0,0 +1,323 @@ +package org.theb.ssh; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import de.mud.terminal.SoftFont; +import de.mud.terminal.VDUBuffer; +import de.mud.terminal.VDUDisplay; +import de.mud.terminal.vt320; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.Paint.FontMetricsInt; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; + +public class JTATerminalView extends View implements VDUDisplay, Terminal, Runnable { + private Paint paint; + private Canvas canvas; + private Bitmap bitmap; + + protected vt320 emulation; + private VDUBuffer buffer; + + private InputStream in; + private OutputStream out; + + private String encoding = "ASCII"; + private SoftFont sf = new SoftFont(); + + private Thread reader = null; + + private int charWidth; + private int charHeight; + private int charDescent; + + private int termWidth; + private int termHeight; + + private int color[] = { + Color.BLACK, + Color.RED, + Color.GREEN, + Color.YELLOW, + Color.BLUE, + Color.MAGENTA, + Color.CYAN, + Color.WHITE, + }; + + private final static int COLOR_FG_STD = 7; + private final static int COLOR_BG_STD = 0; + + public JTATerminalView(Context context) { + super(context); + + paint = new Paint(); + paint.setAntiAlias(true); + setFont(Typeface.MONOSPACE, 8); + + emulation = new vt320() { + public void write(byte[] b) { + try { + JTATerminalView.this.write(b); + } catch (IOException e) { + Log.e("SSH", "couldn't write" + b.toString()); + reader = null; + } + } + + public void sendTelnetCommand(byte cmd) { + // TODO: implement telnet command sending + } + + public void setWindowSize(int c, int r) { + // TODO: implement window sizing + } + }; + + setVDUBuffer(emulation); + emulation.setDisplay(this); + } + + @Override + protected void onDraw(Canvas canvas) { + if (bitmap != null) { + canvas.drawBitmap(bitmap, 0, 0, null); + /* + if (charHeight > 0 && y > charHeight) { + // Invert pixels for cursor position. + Bitmap cursor = Bitmap.createBitmap(mBitmap, x, y - mCharHeight, mCharWidth, mCharHeight); + for (int cy = 0; cy < mCharHeight; cy++) + for (int cx = 0; cx < mCharWidth; cx++) + cursor.setPixel(cx, cy, (~cursor.getPixel(cx, cy) & 0xFFFFFFFF) | 0xFF000000); + canvas.drawBitmap(cursor, x, y - mCharHeight, null); + cursor = null; + } + */ + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + Log.d("SSH/TerminalView", "onSizeChanged called"); + Bitmap newBitmap = Bitmap.createBitmap(w, h, false); + Canvas newCanvas = new Canvas(); + + newCanvas.setDevice(newBitmap); + + if (bitmap != null) + newCanvas.drawBitmap(bitmap, 0, 0, paint); + + bitmap = newBitmap; + canvas = newCanvas; + + setSize(w, h); + } + + private void setSize(int w, int h) { + termWidth = w / charWidth; + termHeight = h / charHeight; + + buffer.setScreenSize(termWidth, buffer.height = termHeight, true); + } + + private void setFont(Typeface typeface, int size) { + paint.setTypeface(typeface); + paint.setTextSize(size); + + FontMetricsInt fm = paint.getFontMetricsInt(); + + charDescent = fm.descent; + + float[] widths = new float[1]; + paint.getTextWidths("X", widths); + charWidth = (int)widths[0]; + + charHeight = Math.abs(fm.top) + Math.abs(fm.descent); + } + + public void write(byte[] b) throws IOException { + Log.e("SSH/JTATerm/write", "Trying to write" + b.toString()); + out.write(b); + } + + public int getColumnCount() { + return termWidth; + } + + public InputStream getInput() { + return in; + } + + public byte[] getKeyCode(int keyCode, int meta) { + switch (keyCode) { + case KeyEvent.KEYCODE_NEWLINE: + emulation.keyTyped(vt320.KEY_ENTER, ' ', meta); + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + emulation.keyPressed(vt320.KEY_LEFT, ' ', meta); + break; + case KeyEvent.KEYCODE_DPAD_UP: + emulation.keyPressed(vt320.KEY_UP, ' ', meta); + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + emulation.keyPressed(vt320.KEY_DOWN, ' ', meta) ; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + emulation.keyPressed(vt320.KEY_RIGHT, ' ', meta); + break; + } + return null; + } + + public OutputStream getOutput() { + return out; + } + + public int getRowCount() { + return termHeight; + } + + private int darken(int color) { + return Color.argb(0xFF, + (int)(Color.red(color) * 0.8), + (int)(Color.green(color) * 0.8), + (int)(Color.blue(color) * 0.8) + ); + } + + /* + private int brighten(int color) { + return Color.argb(0xFF, + (int)(Color.red(color) * 1.2), + (int)(Color.green(color) * 1.2), + (int)(Color.blue(color) * 1.2) + ); + } + */ + + public void redraw() { + // Make sure the buffer is in the center of the screen. + int xoffset = (getWidth() - buffer.width * charWidth) / 2; + int yoffset = (getHeight() - buffer.height * charHeight) / 2; + + // Draw the mouse-selection + //int selectStartLine = selectBegin.y - buffer.windowBase; + //int selectEndLine = selectEnd.y - buffer.windowBase; + + int fg, bg; + + for (int l = 0; l < buffer.height; l++) { + if (!buffer.update[0] && !buffer.update[l + 1]) continue; + + for (int c = 0; c < buffer.width; c++) { + int addr = 0; + int currAttr = buffer.charAttributes[buffer.windowBase + l][c]; + + fg = darken(color[COLOR_FG_STD]); + bg = darken(color[COLOR_BG_STD]); + + if ((currAttr & VDUBuffer.COLOR_FG) != 0) + fg = darken(color[((currAttr & VDUBuffer.COLOR_FG) >> VDUBuffer.COLOR_FG_SHIFT) - 1]); + if ((currAttr & VDUBuffer.COLOR_BG) != 0) + bg = darken(darken(color[((currAttr & VDUBuffer.COLOR_BG) >> VDUBuffer.COLOR_BG_SHIFT) - 1])); + paint.setFakeBoldText((currAttr & VDUBuffer.BOLD) != 0); + + if ((currAttr & VDUBuffer.LOW) != 0) + fg = darken(fg); + + if ((currAttr & VDUBuffer.INVERT) != 0) { + int swapc = bg; + bg = fg; + fg = swapc; + } + + // If this character is in the special font, print it and continue to the next character. + if (sf.inSoftFont(buffer.charArray[buffer.windowBase + l][c])) { + paint.setColor(bg); + canvas.drawRect(c * charWidth + xoffset, l * charHeight + yoffset, + c * (charWidth + 1) + xoffset, (l+1) * charHeight + yoffset, paint); + paint.setColor(fg); + paint.setUnderlineText((currAttr & VDUBuffer.UNDERLINE) != 0); + if ((currAttr & VDUBuffer.INVISIBLE) == 0) + sf.drawChar(canvas, paint, buffer.charArray[buffer.windowBase + l][c], xoffset + c * charWidth, l * charHeight + yoffset, charWidth, charHeight); + continue; + } + + // Determine the amount of continuous characters with the same settings and print them all at once. + while ((c + addr < buffer.width) && + ((buffer.charArray[buffer.windowBase + l][c + addr] < ' ') || + (buffer.charAttributes[buffer.windowBase + l][c + addr] == currAttr)) && + !sf.inSoftFont(buffer.charArray[buffer.windowBase + l][c + addr])) { + if (buffer.charArray[buffer.windowBase + l][c + addr] < ' ') { + buffer.charArray[buffer.windowBase + l][c + addr] = ' '; + buffer.charAttributes[buffer.windowBase + l][c + addr] = 0; + continue; + } + addr++; + } + + paint.setColor(bg); + canvas.drawRect(c * charWidth + xoffset, l * charHeight + yoffset, + addr * (charWidth + 1) + xoffset, (l+1) * charHeight + yoffset, paint); + paint.setColor(fg); + + paint.setUnderlineText((currAttr & VDUBuffer.UNDERLINE) != 0); + if ((currAttr & VDUBuffer.INVISIBLE) == 0) + canvas.drawText(buffer.charArray[buffer.windowBase + l], + c, addr, + c * charWidth + xoffset, + (l + 1) * charHeight - charDescent + yoffset, + paint); + + c += addr - 1; + } + } + + buffer.update[0] = false; + + postInvalidate(); + } + + public void updateScrollBar() { + // TODO Auto-generated method stub + } + + public void start(InputStream in, OutputStream out) { + this.in = in; + this.out = out; + + reader = new Thread(this); + reader.start(); + } + + public VDUBuffer getVDUBuffer() { + return buffer; + } + + public void setVDUBuffer(VDUBuffer buffer) { + this.buffer = buffer; + } + + public void run() { + byte[] b = new byte[256]; + int n = 0; + while (n >= 0) + try { + n = in.read(b); + if (n > 0) emulation.putString(new String(b, 0, n, encoding)); + redraw(); + } catch (IOException e) { + reader = null; + break; + } + } +} diff --git a/src/org/theb/ssh/PasswordDialog.java b/src/org/theb/ssh/PasswordDialog.java index 87839b7..faf636a 100644 --- a/src/org/theb/ssh/PasswordDialog.java +++ b/src/org/theb/ssh/PasswordDialog.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ package org.theb.ssh; import android.app.Activity; diff --git a/src/org/theb/ssh/PreferencesDialog.java b/src/org/theb/ssh/PreferencesDialog.java new file mode 100644 index 0000000..06bc229 --- /dev/null +++ b/src/org/theb/ssh/PreferencesDialog.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ +package org.theb.ssh; + +import android.app.Dialog; +import android.content.Context; + +public class PreferencesDialog extends Dialog { + + public PreferencesDialog(Context context) { + super(context); + // TODO Auto-generated constructor stub + } + + public PreferencesDialog(Context context, int theme) { + super(context, theme); + // TODO Auto-generated constructor stub + } + + public PreferencesDialog(Context context, boolean cancelable, + OnCancelListener cancelListener) { + super(context, cancelable, cancelListener); + // TODO Auto-generated constructor stub + } + +} diff --git a/src/org/theb/ssh/SecureShell.java b/src/org/theb/ssh/SecureShell.java index 31625f9..8d4dda6 100644 --- a/src/org/theb/ssh/SecureShell.java +++ b/src/org/theb/ssh/SecureShell.java @@ -1,12 +1,30 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ package org.theb.ssh; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; -import java.util.concurrent.Semaphore; import org.theb.provider.HostDb; +import com.trilead.ssh2.ConnectionMonitor; + import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; @@ -17,22 +35,16 @@ import android.os.Handler; import android.text.method.KeyCharacterMap; import android.util.Log; import android.view.KeyEvent; +import android.view.View; import android.view.Window; -import android.widget.TextView; - -import com.trilead.ssh2.ChannelCondition; -import com.trilead.ssh2.Connection; -import com.trilead.ssh2.ConnectionMonitor; -import com.trilead.ssh2.Session; -public class SecureShell extends Activity { - private TextView mOutput; +public class SecureShell extends Activity implements FeedbackUI, ConnectionMonitor { private ConnectionThread mConn; - private String mBuffer; - private KeyCharacterMap mKMap; + // Activities we support. static final int PASSWORD_REQUEST = 0; + // Database projection indices. private static final int HOSTNAME_INDEX = 1; private static final int USERNAME_INDEX = 2; private static final int PORT_INDEX = 3; @@ -46,6 +58,15 @@ public class SecureShell extends Activity { private Cursor mCursor; + // Map to convert from keyboard presses to actual characters. + private KeyCharacterMap mKMap; + + // Terminal window + private Terminal mTerminal; + + // We change the meta state when the user presses the center button. + private boolean mMetaState = false; + // Store the username, hostname, and port from the database. private String mHostname; private String mUsername; @@ -56,241 +77,21 @@ public class SecureShell extends Activity { private boolean mIsWaiting; private String mWaitingTitle; private String mWaitingMessage; - - // Connection lost reason. - private String mDisconnectReason; - - // This is for the password dialog. - Semaphore sPass; - String mPassword = null; - - Connection conn; - Session sess; - InputStream stdin; - InputStream stderr; - OutputStream stdout; - int x; - int y; - + final Handler mHandler = new Handler(); - final Runnable mUpdateView = new Runnable() { - public void run() { - updateViewInUI(); - } - }; - - class ConnectionThread extends Thread { - String hostname; - String username; - int port; - - char[][] lines; - int posy = 0; - int posx = 0; - - public ConnectionThread(String hostname, String username, int port) { - this.hostname = hostname; - this.username = username; - this.port = port; - } - - public void run() { - conn = new Connection(hostname, port); - - conn.addConnectionMonitor(mConnectionMonitor); - - setWaiting(true, "Connection", - "Connecting to " + hostname + "..."); - - Log.d("SSH", "Starting connection attempt..."); - - try { - conn.connect(new InteractiveHostKeyVerifier()); - - setWaiting(true, "Authenticating", - "Trying to authenticate..."); - - Log.d("SSH", "Starting authentication..."); - -// boolean enableKeyboardInteractive = true; -// boolean enableDSA = true; -// boolean enableRSA = true; - - while (true) { - /* - if ((enableDSA || enableRSA ) && - mConn.isAuthMethodAvailable(username, "publickey"); - */ - - if (conn.isAuthMethodAvailable(username, "password")) { - Log.d("SSH", "Trying password authentication..."); - setWaiting(true, "Authenticating", - "Trying to authenticate using password..."); - - // Set a semaphore that is unset by the returning dialog. - sPass = new Semaphore(0); - askPassword(); - - // Wait for the user to answer. - sPass.acquire(); - sPass = null; - if (mPassword == null) - continue; - - boolean res = conn.authenticateWithPassword(username, mPassword); - if (res == true) - break; - - continue; - } - - throw new IOException("No supported authentication methods available."); - } - - Log.d("SSH", "Opening session..."); - setWaiting(true, "Session", "Requesting shell..."); - - sess = conn.openSession(); - - y = (int)(mOutput.getHeight() / mOutput.getLineHeight()); - // TODO: figure out how to get the width of monospace font characters. - x = y * 3; - Log.d("SSH", "Requesting PTY of size " + x + "x" + y); - - sess.requestPTY("dumb", x, y, 0, 0, null); - - Log.d("SSH", "Requesting shell..."); - sess.startShell(); - - stdout = sess.getStdin(); - stderr = sess.getStderr(); - stdin = sess.getStdout(); - - setWaiting(false, null, null); - } catch (IOException e) { - Log.e("SSH", e.getMessage()); - setWaiting(false, null, null); - return; - } catch (InterruptedException e) { - // This thread is coming to an end. Let us exit. - Log.e("SSH", "Connection thread interrupted."); - return; - } - - byte[] buff = new byte[8192]; - lines = new char[y][]; - - try { - while (true) { - if ((stdin.available() == 0) && (stderr.available() == 0)) { - int conditions = sess.waitForCondition( - ChannelCondition.STDOUT_DATA - | ChannelCondition.STDERR_DATA - | ChannelCondition.EOF, 2000); - if ((conditions & ChannelCondition.TIMEOUT) != 0) - continue; - if ((conditions & ChannelCondition.EOF) != 0) - if ((conditions & - (ChannelCondition.STDERR_DATA - | ChannelCondition.STDOUT_DATA)) == 0) - break; - } - - if (stdin.available() > 0) { - int len = stdin.read(buff); - addText(buff, len); - } - if (stderr.available() > 0) { - int len = stderr.read(buff); - addText(buff, len); - } - } - } catch (Exception e) { - Log.e("SSH", "Got exception reading: " + e.getMessage()); - } - } - - public void addText(byte[] data, int len) { - for (int i = 0; i < len; i++) { - char c = (char) (data[i] & 0xff); - - if (c == 8) { // Backspace, VERASE - if (posx < 0) - continue; - posx--; - continue; - } - if (c == '\r') { - posx = 0; - continue; - } - - if (c == '\n') { - posy++; - if (posy >= y) { - for (int k = 1; k < y; k++) - lines[k - 1] = lines[k]; - - posy--; - lines[y - 1] = new char[x]; - - for (int k = 0; k < x; k++) - lines[y - 1][k] = ' '; - } - continue; - } - - if (c < 32) { - continue; - } - - if (posx >= x) { - posx = 0; - posy++; - if (posy >= y) { - posy--; - - for (int k = 1; k < y; k++) - lines[k - 1] = lines[k]; - lines[y - 1] = new char[x]; - for (int k = 0; k < x; k++) - lines[y - 1][k] = ' '; - } - } - - if (lines[posy] == null) { - lines[posy] = new char[x]; - for (int k = 0; k < x; k++) - lines[posy][k] = ' '; - } + // Tell the user why we disconnected. + private String mDisconnectReason; - lines[posy][posx] = c; - posx++; - } - - StringBuffer sb = new StringBuffer(x * y); - - for (int i = 0; i < lines.length; i++) { - if (i != 0) - sb.append('\n'); - - if (lines[i] != null) - sb.append(lines[i]); - } - - mBuffer = sb.toString(); - mHandler.post(mUpdateView); - } - } - @Override public void onCreate(Bundle savedValues) { super.onCreate(savedValues); requestWindowFeature(Window.FEATURE_PROGRESS); - setContentView(R.layout.secure_shell); - mOutput = (TextView) findViewById(R.id.output); + mTerminal = new JTATerminalView(this); + + // TODO: implement scroll bar on right. + setContentView((View)mTerminal); Log.d("SSH", "using URI " + getIntent().getData().toString()); @@ -307,7 +108,7 @@ public class SecureShell extends Activity { this.setTitle(title); - mConn = new ConnectionThread(mHostname, mUsername, mPort); + mConn = new TrileadConnectionThread(this, mTerminal, mHostname, mUsername, mPort); mKMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); @@ -340,10 +141,9 @@ public class SecureShell extends Activity { } }; - public String askPassword() { + public void askPassword() { Intent intent = new Intent(this, PasswordDialog.class); this.startSubActivity(intent, PASSWORD_REQUEST); - return null; } @Override @@ -351,55 +151,71 @@ public class SecureShell extends Activity { String data, Bundle extras) { if (requestCode == PASSWORD_REQUEST) { - - // If the request was cancelled, then we didn't get anything. - if (resultCode == RESULT_CANCELED) - mPassword = ""; - else - mPassword = data; - - sPass.release(); + mConn.setPassword(data); } } @Override public void onDestroy() { super.onDestroy(); - - if (sess != null) { - sess.close(); - sess = null; - } - - if (conn != null) { - conn.close(); - conn = null; - } + + mConn.finish(); + mConn = null; finish(); } @Override public boolean onKeyDown(int keyCode, KeyEvent msg) { - if (stdout != null) { - int c = mKMap.get(keyCode, msg.getMetaState()); + final OutputStream out = mConn.getWriter(); + if (out != null) { try { - stdout.write(c); + if (mKMap.isPrintingKey(keyCode) + || keyCode == KeyEvent.KEYCODE_SPACE) { + int c = mKMap.get(keyCode, msg.getMetaState()); + if (mMetaState) { + // Support CTRL-A through CTRL-Z + if (c >= 0x61 && c <= 0x79) + c -= 0x60; + else if (c >= 0x40 && c <= 0x59) + c -= 0x39; + mMetaState = false; + } + out.write(c); + } else if (keyCode == KeyEvent.KEYCODE_DEL) { + out.write(0x08); // CTRL-H + } else if (keyCode == KeyEvent.KEYCODE_NEWLINE + || keyCode == KeyEvent.KEYCODE_DPAD_LEFT + || keyCode == KeyEvent.KEYCODE_DPAD_UP + || keyCode == KeyEvent.KEYCODE_DPAD_DOWN + || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + byte[] output = mTerminal.getKeyCode(keyCode, msg.getMetaState()); + if (output != null) + out.write(output); + } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + if (mMetaState) { + out.write(0x1B); // ESCAPE + mMetaState = false; + } else { + mMetaState = true; + } + } else { + // This is not something we handle. + return super.onKeyDown(keyCode, msg); + } + return true; } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Log.e("SSH", "Can't write to stdout: "+ e.getMessage()); } } - - return super.onKeyDown(keyCode, msg); - } - - public void updateViewInUI() { - mOutput.setText(mBuffer); + return super.onKeyDown(keyCode, msg); } final Runnable mDisconnectAlert = new Runnable() { public void run() { + if (SecureShell.this.isFinishing()) + return; + AlertDialog d = AlertDialog.show(SecureShell.this, "Connection Lost", mDisconnectReason, "Ok", false); d.show(); @@ -407,11 +223,9 @@ public class SecureShell extends Activity { } }; - final ConnectionMonitor mConnectionMonitor = new ConnectionMonitor() { - public void connectionLost(Throwable reason) { - Log.d("SSH", "Connection ended."); - mDisconnectReason = reason.getMessage(); - mHandler.post(mDisconnectAlert); - } - }; + public void connectionLost(Throwable reason) { + Log.d("SSH", "Connection ended."); + mDisconnectReason = reason.getMessage(); + mHandler.post(mDisconnectAlert); + } } diff --git a/src/org/theb/ssh/ShellView.java b/src/org/theb/ssh/ShellView.java deleted file mode 100644 index 15d783b..0000000 --- a/src/org/theb/ssh/ShellView.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.theb.ssh; - -import java.io.IOException; - -import com.trilead.ssh2.Connection; -import com.trilead.ssh2.Session; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.Log; -import android.widget.EditText; -import android.widget.TextView; - -public class ShellView extends EditText { - - private Connection mConn; - private Session mSess; - - private String mHostname; - private String mUsername; - private String mPassword; - - public ShellView(Context context) { - super(context); - // TODO Auto-generated constructor stub - - mPassword = "OEfmP07-"; - } - - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - - append("Connecting... "); - mConn = new Connection(mHostname); - - try { - mConn.connect(new InteractiveHostKeyVerifier()); - append("OK\n"); - } catch (IOException e) { - append("Failed.\n"); - Log.e("SSH", e.getMessage()); - append("\nWhoops: " + e.getCause().getMessage() + "\n"); - } - - boolean enableKeyboardInteractive = true; - boolean enableDSA = true; - boolean enableRSA = true; - - try { - while (true) { - /* - if ((enableDSA || enableRSA ) && - mConn.isAuthMethodAvailable(username, "publickey"); - */ - - if (mConn.isAuthMethodAvailable(mUsername, "password")) { - boolean res = mConn.authenticateWithPassword(mUsername, mPassword); - if (res == true) - break; - - append("Login failed.\n"); - continue; - } - - throw new IOException("No supported authentication methods available."); - } - - mSess = mConn.openSession(); - append("Logged in as " + mUsername + ".\n"); - } catch (IOException e) { - Log.e("SSH", e.getMessage()); - } - append("Exiting\n"); - } - -} diff --git a/src/org/theb/ssh/Terminal.java b/src/org/theb/ssh/Terminal.java new file mode 100644 index 0000000..1212aa2 --- /dev/null +++ b/src/org/theb/ssh/Terminal.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ +package org.theb.ssh; + +import java.io.InputStream; +import java.io.OutputStream; + +public interface Terminal { + public int getWidth(); + public int getHeight(); + public int getRowCount(); + public int getColumnCount(); + public void start(InputStream in, OutputStream out); + public byte[] getKeyCode(int keyCode, int meta); +} diff --git a/src/org/theb/ssh/TrileadConnectionThread.java b/src/org/theb/ssh/TrileadConnectionThread.java new file mode 100644 index 0000000..63180b0 --- /dev/null +++ b/src/org/theb/ssh/TrileadConnectionThread.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ +package org.theb.ssh; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.Semaphore; + +import com.trilead.ssh2.Connection; +import com.trilead.ssh2.ConnectionMonitor; +import com.trilead.ssh2.Session; + +public class TrileadConnectionThread extends ConnectionThread { + private String hostname; + private String username; + private String password; + private int port; + + private Connection connection; + private Session session; + + private InputStream stdOut; + private OutputStream stdIn; + + private Semaphore sPass; + + protected FeedbackUI ui; + protected Terminal term; + + public TrileadConnectionThread(FeedbackUI ui, Terminal term, String hostname, String username, int port) { + super(ui, hostname, username, port); + this.ui = ui; + this.term = term; + this.hostname = hostname; + this.username = username; + this.port = port; + } + + @Override + public void finish() { + if (session != null) { + session.close(); + session = null; + } + + if (connection != null) { + connection.close(); + connection = null; + } + } + + @Override + public InputStream getReader() { + return stdOut; + } + + @Override + public OutputStream getWriter() { + return stdIn; + } + + @Override + public void run() { + connection = new Connection(hostname, port); + + connection.addConnectionMonitor((ConnectionMonitor) ui); + + ui.setWaiting(true, "Connection", "Connecting to " + hostname + "..."); + + try { + connection.connect(new InteractiveHostKeyVerifier()); + + ui.setWaiting(true, "Authenticating", "Trying to authenticate..."); + + // boolean enableKeyboardInteractive = true; + // boolean enableDSA = true; + // boolean enableRSA = true; + + while (true) { + /* + * if ((enableDSA || enableRSA ) && + * mConn.isAuthMethodAvailable(username, "publickey"); + */ + + if (connection.isAuthMethodAvailable(username, "password")) { + ui.setWaiting(true, "Authenticating", + "Trying to authenticate using password..."); + + // Set a semaphore that is unset by the returning dialog. + sPass = new Semaphore(0); + ui.askPassword(); + + // Wait for the user to answer. + sPass.acquire(); + sPass = null; + if (password == null) + continue; + + boolean res = connection.authenticateWithPassword(username, + password); + password = null; + if (res == true) + break; + + continue; + } + + throw new IOException( + "No supported authentication methods available."); + } + + ui.setWaiting(true, "Session", "Requesting shell..."); + + session = connection.openSession(); + + session.requestPTY("vt100", + term.getColumnCount(), term.getRowCount(), + term.getWidth(), term.getHeight(), + null); + + session.startShell(); + + stdIn = session.getStdin(); + // stderr = session.getStderr(); + stdOut = session.getStdout(); + + ui.setWaiting(false, null, null); + } catch (IOException e) { + ui.setWaiting(false, null, null); + return; + } catch (InterruptedException e) { + // This thread is coming to an end. Let us exit. + return; + } + + term.start(stdOut, stdIn); + } + + @Override + public void setPassword(String password) { + if (password == null) + this.password = ""; + else + this.password = password; + sPass.release(); + } +} |