0
|
1 /*
|
|
2 * ConnectBot: simple, powerful, open-source SSH client for Android
|
|
3 * Copyright 2007 Kenny Root, Jeffrey Sharkey
|
|
4 *
|
|
5 * Licensed under the Apache License, Version 2.0 (the "License");
|
|
6 * you may not use this file except in compliance with the License.
|
|
7 * You may obtain a copy of the License at
|
|
8 *
|
|
9 * http://www.apache.org/licenses/LICENSE-2.0
|
|
10 *
|
|
11 * Unless required by applicable law or agreed to in writing, software
|
|
12 * distributed under the License is distributed on an "AS IS" BASIS,
|
|
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14 * See the License for the specific language governing permissions and
|
|
15 * limitations under the License.
|
|
16 */
|
|
17
|
|
18 package com.five_ten_sg.connectbot.service;
|
|
19
|
|
20 import java.io.File;
|
|
21 import java.io.FileOutputStream;
|
|
22 import java.io.IOException;
|
|
23 import java.nio.charset.Charset;
|
|
24 import java.text.SimpleDateFormat;
|
|
25 import java.util.Date;
|
|
26 import java.util.LinkedHashSet;
|
|
27 import java.util.LinkedList;
|
|
28 import java.util.List;
|
|
29 import java.util.Set;
|
|
30 import java.util.regex.Matcher;
|
|
31 import java.util.regex.Pattern;
|
|
32
|
|
33 import com.five_ten_sg.connectbot.R;
|
|
34 import com.five_ten_sg.connectbot.TerminalView;
|
|
35 import com.five_ten_sg.connectbot.bean.HostBean;
|
|
36 import com.five_ten_sg.connectbot.bean.PortForwardBean;
|
|
37 import com.five_ten_sg.connectbot.bean.SelectionArea;
|
|
38 import com.five_ten_sg.connectbot.transport.AbsTransport;
|
|
39 import com.five_ten_sg.connectbot.transport.TransportFactory;
|
|
40 import com.five_ten_sg.connectbot.util.HostDatabase;
|
|
41 import com.five_ten_sg.connectbot.util.PreferenceConstants;
|
|
42 import android.app.AlertDialog;
|
|
43 import android.content.Context;
|
|
44 import android.graphics.Bitmap;
|
|
45 import android.graphics.Bitmap.Config;
|
|
46 import android.graphics.Canvas;
|
|
47 import android.graphics.Color;
|
|
48 import android.graphics.Paint;
|
|
49 import android.graphics.Paint.FontMetrics;
|
|
50 import android.graphics.Typeface;
|
|
51 import android.os.Binder;
|
|
52 import android.os.Environment;
|
|
53 import android.text.ClipboardManager;
|
|
54 import android.text.Editable;
|
|
55 import android.text.method.CharacterPickerDialog;
|
|
56 import android.util.FloatMath;
|
|
57 import android.util.Log;
|
|
58 import android.view.KeyEvent;
|
|
59 import android.view.View;
|
|
60 import android.widget.AdapterView;
|
|
61 import android.widget.Button;
|
|
62 import de.mud.terminal.VDUBuffer;
|
|
63 import de.mud.terminal.VDUDisplay;
|
|
64 import de.mud.terminal.vt320;
|
|
65
|
|
66
|
|
67 /**
|
|
68 * Provides a bridge between a MUD terminal buffer and a possible TerminalView.
|
|
69 * This separation allows us to keep the TerminalBridge running in a background
|
|
70 * service. A TerminalView shares down a bitmap that we can use for rendering
|
|
71 * when available.
|
|
72 *
|
|
73 * This class also provides SSH hostkey verification prompting, and password
|
|
74 * prompting.
|
|
75 */
|
|
76 @SuppressWarnings("deprecation") // for ClipboardManager
|
|
77 public class TerminalBridge implements VDUDisplay {
|
|
78 public final static String TAG = "ConnectBot.TerminalBridge";
|
|
79
|
|
80 public final static int DEFAULT_FONT_SIZE = 10;
|
|
81 private final static int FONT_SIZE_STEP = 2;
|
|
82
|
|
83 public Integer[] color;
|
|
84
|
|
85 public int defaultFg = HostDatabase.DEFAULT_FG_COLOR;
|
|
86 public int defaultBg = HostDatabase.DEFAULT_BG_COLOR;
|
|
87
|
|
88 protected final TerminalManager manager;
|
95
|
89 public final HostBean host;
|
|
90 public final String homeDirectory;
|
0
|
91
|
|
92 AbsTransport transport;
|
|
93
|
|
94 final Paint defaultPaint;
|
|
95
|
|
96 private Relay relay;
|
|
97
|
67
|
98 private String emulation; // aka answerback string, aka terminal type
|
0
|
99
|
|
100 public Bitmap bitmap = null;
|
|
101 public vt320 buffer = null;
|
|
102
|
47
|
103 public TerminalView parent = null;
|
0
|
104 private final Canvas canvas = new Canvas();
|
|
105
|
|
106 private boolean disconnected = false;
|
|
107 private boolean awaitingClose = false;
|
|
108
|
|
109 private boolean forcedSize = false;
|
|
110 private int columns;
|
|
111 private int rows;
|
|
112
|
|
113 public TerminalMonitor monitor = null;
|
|
114 private TerminalKeyListener keyListener = null;
|
|
115
|
|
116 private boolean selectingForCopy = false;
|
|
117 private final SelectionArea selectionArea;
|
|
118
|
|
119 // TODO add support for the new clipboard API
|
|
120 private ClipboardManager clipboard;
|
|
121
|
42
|
122 public int charWidth = -1;
|
|
123 public int charHeight = -1;
|
|
124 private int charTop = -1;
|
|
125 private float fontSize = -1;
|
0
|
126
|
|
127 private final List<FontSizeChangedListener> fontSizeChangedListeners;
|
|
128
|
|
129 private final List<String> localOutput;
|
|
130
|
|
131 /**
|
|
132 * Flag indicating if we should perform a full-screen redraw during our next
|
|
133 * rendering pass.
|
|
134 */
|
|
135 private boolean fullRedraw = false;
|
|
136
|
|
137 public PromptHelper promptHelper;
|
|
138
|
|
139 protected BridgeDisconnectedListener disconnectListener = null;
|
|
140
|
|
141 /**
|
|
142 * Create a new terminal bridge suitable for unit testing.
|
|
143 */
|
|
144 public TerminalBridge() {
|
|
145 buffer = new vt320() {
|
|
146 @Override
|
|
147 public void write(byte[] b) {}
|
|
148 @Override
|
|
149 public void write(int b) {}
|
|
150 @Override
|
|
151 public void sendTelnetCommand(byte cmd) {}
|
|
152 @Override
|
|
153 public void setWindowSize(int c, int r) {}
|
|
154 @Override
|
|
155 public void debug(String s) {}
|
|
156 };
|
95
|
157 emulation = null;
|
|
158 manager = null;
|
|
159 host = null;
|
|
160 homeDirectory = null;
|
|
161 defaultPaint = new Paint();
|
0
|
162 selectionArea = new SelectionArea();
|
95
|
163 localOutput = new LinkedList<String>();
|
0
|
164 fontSizeChangedListeners = new LinkedList<FontSizeChangedListener>();
|
|
165 transport = null;
|
|
166 keyListener = new TerminalKeyListener(manager, this, buffer, null);
|
|
167 monitor = null;
|
|
168 }
|
|
169
|
|
170 /**
|
42
|
171 * Create new terminal bridge with following parameters.
|
0
|
172 */
|
94
|
173 public TerminalBridge(final TerminalManager manager, final HostBean host, final String homeDirectory) throws IOException {
|
0
|
174 float hostFontSize;
|
94
|
175 this.manager = manager;
|
|
176 this.host = host;
|
|
177 this.homeDirectory = homeDirectory;
|
66
|
178 emulation = host.getHostEmulation();
|
112
|
179
|
65
|
180 if ((emulation == null) || (emulation.length() == 0)) emulation = manager.getEmulation();
|
112
|
181
|
0
|
182 // create prompt helper to relay password and hostkey requests up to gui
|
|
183 promptHelper = new PromptHelper(this);
|
|
184 // create our default paint
|
|
185 defaultPaint = new Paint();
|
|
186 defaultPaint.setAntiAlias(true);
|
|
187 defaultPaint.setTypeface(Typeface.MONOSPACE);
|
|
188 defaultPaint.setFakeBoldText(true); // more readable?
|
|
189 localOutput = new LinkedList<String>();
|
|
190 fontSizeChangedListeners = new LinkedList<FontSizeChangedListener>();
|
|
191 Integer defaultFontSize = Integer.parseInt(manager.prefs.getString(PreferenceConstants.DEFAULT_FONT_SIZE, "-1"));
|
|
192 Log.i(TAG, "fontSize: " + this.fontSize + ", defaultFontSize: " + defaultFontSize);
|
|
193
|
42
|
194 if (fontSize == -1) {
|
0
|
195 if (defaultFontSize > 0 && host.getFontSize() == -1)
|
|
196 hostFontSize = defaultFontSize;
|
|
197 else
|
|
198 hostFontSize = host.getFontSize();
|
|
199 }
|
|
200 else
|
42
|
201 hostFontSize = fontSize;
|
0
|
202
|
42
|
203 if (hostFontSize <= 0) hostFontSize = DEFAULT_FONT_SIZE;
|
0
|
204
|
|
205 setFontSize(hostFontSize);
|
|
206 resetColors();
|
|
207 selectionArea = new SelectionArea();
|
|
208 }
|
|
209
|
|
210 public PromptHelper getPromptHelper() {
|
|
211 return promptHelper;
|
|
212 }
|
|
213
|
|
214 /**
|
|
215 * Spawn thread to open connection and start login process.
|
|
216 */
|
|
217 protected void startConnection() {
|
29
|
218 transport = TransportFactory.getTransport(host.getProtocol());
|
94
|
219 transport.setLinks(manager, this, homeDirectory, host, emulation);
|
42
|
220 buffer = transport.getTransportBuffer();
|
31
|
221 keyListener = transport.getTerminalKeyListener();
|
112
|
222 String monitor_init = host.getMonitor();
|
29
|
223
|
|
224 if ((monitor_init != null) && (monitor_init.length() > 0)) {
|
113
cb3b9b660b3d
depress() keys from the terminal monitor go straight thru buffer.keyPressed() rather than detour though the key listener
Carl Byington <carl@five-ten-sg.com>
diff
changeset
|
225 monitor = new TerminalMonitor(manager, buffer, parent, monitor_init);
|
29
|
226 }
|
|
227
|
0
|
228 transport.setCompression(host.getCompression());
|
|
229 transport.setHttpproxy(host.getHttpproxy());
|
|
230 transport.setUseAuthAgent(host.getUseAuthAgent());
|
|
231
|
|
232 if (transport.canForwardPorts()) {
|
|
233 for (PortForwardBean portForward : manager.hostdb.getPortForwardsForHost(host))
|
|
234 transport.addPortForward(portForward);
|
|
235 }
|
|
236
|
|
237 outputLine(manager.res.getString(R.string.terminal_connecting, host.getHostname(), host.getPort(), host.getProtocol()));
|
|
238 Thread connectionThread = new Thread(new Runnable() {
|
|
239 public void run() {
|
|
240 transport.connect();
|
|
241 }
|
|
242 });
|
|
243 connectionThread.setName("Connection");
|
|
244 connectionThread.setDaemon(true);
|
|
245 connectionThread.start();
|
|
246 }
|
|
247
|
|
248 /**
|
|
249 * Handle challenges from keyboard-interactive authentication mode.
|
|
250 */
|
|
251 public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) {
|
|
252 String[] responses = new String[numPrompts];
|
|
253
|
|
254 for (int i = 0; i < numPrompts; i++) {
|
|
255 // request response from user for each prompt
|
|
256 responses[i] = promptHelper.requestPasswordPrompt(instruction, prompt[i]);
|
|
257 }
|
|
258
|
|
259 return responses;
|
|
260 }
|
|
261
|
|
262 /**
|
|
263 * @return charset in use by bridge
|
|
264 */
|
|
265 public Charset getCharset() {
|
42
|
266 if (relay != null) return relay.getCharset();
|
112
|
267
|
45
|
268 return keyListener.getCharset();
|
0
|
269 }
|
|
270
|
|
271 /**
|
|
272 * Sets the encoding used by the terminal. If the connection is live,
|
|
273 * then the character set is changed for the next read.
|
|
274 * @param encoding the canonical name of the character encoding
|
|
275 */
|
|
276 public void setCharset(String encoding) {
|
42
|
277 if (relay != null) relay.setCharset(encoding);
|
112
|
278
|
0
|
279 keyListener.setCharset(encoding);
|
|
280 }
|
|
281
|
|
282 /**
|
|
283 * Convenience method for writing a line into the underlying MUD buffer.
|
|
284 * Should never be called once the session is established.
|
|
285 */
|
|
286 public final void outputLine(String line) {
|
|
287 if (transport != null && transport.isSessionOpen())
|
|
288 Log.e(TAG, "Session established, cannot use outputLine!", new IOException("outputLine call traceback"));
|
|
289
|
|
290 synchronized (localOutput) {
|
|
291 final String s = line + "\r\n";
|
|
292 localOutput.add(s);
|
|
293 buffer.putString(s);
|
|
294 // For accessibility
|
|
295 final char[] charArray = s.toCharArray();
|
|
296 propagateConsoleText(charArray, charArray.length);
|
|
297 }
|
|
298 }
|
|
299
|
|
300 /**
|
|
301 * Inject a specific string into this terminal. Used for post-login strings
|
|
302 * and pasting clipboard.
|
|
303 */
|
|
304 public void injectString(final String string) {
|
|
305 if (string == null || string.length() == 0)
|
|
306 return;
|
|
307
|
|
308 Thread injectStringThread = new Thread(new Runnable() {
|
|
309 public void run() {
|
|
310 try {
|
|
311 transport.write(string.getBytes(host.getEncoding()));
|
|
312 }
|
|
313 catch (Exception e) {
|
|
314 Log.e(TAG, "Couldn't inject string to remote host: ", e);
|
|
315 }
|
|
316 }
|
|
317 });
|
|
318 injectStringThread.setName("InjectString");
|
|
319 injectStringThread.start();
|
|
320 }
|
|
321
|
|
322 /**
|
|
323 * Internal method to request actual PTY terminal once we've finished
|
|
324 * authentication. If called before authenticated, it will just fail.
|
|
325 */
|
|
326 public void onConnected() {
|
|
327 disconnected = false;
|
|
328 buffer.reset();
|
|
329 buffer.setAnswerBack(emulation);
|
42
|
330 localOutput.clear(); // We no longer need our local output.
|
0
|
331
|
|
332 if (HostDatabase.DELKEY_BACKSPACE.equals(host.getDelKey()))
|
|
333 buffer.setBackspace(vt320.DELETE_IS_BACKSPACE);
|
|
334 else
|
|
335 buffer.setBackspace(vt320.DELETE_IS_DEL);
|
|
336
|
|
337 // create thread to relay incoming connection data to buffer
|
29
|
338 // only if needed by the transport
|
|
339 if (transport.needsRelay()) {
|
|
340 relay = new Relay(this, transport, buffer, host.getEncoding());
|
|
341 Thread relayThread = new Thread(relay);
|
|
342 relayThread.setDaemon(true);
|
|
343 relayThread.setName("Relay");
|
|
344 relayThread.start();
|
|
345 }
|
|
346
|
0
|
347 // force font-size to make sure we resizePTY as needed
|
|
348 setFontSize(fontSize);
|
|
349 // finally send any post-login string, if requested
|
|
350 injectString(host.getPostLogin());
|
|
351 }
|
|
352
|
|
353 /**
|
|
354 * @return whether a session is open or not
|
|
355 */
|
|
356 public boolean isSessionOpen() {
|
|
357 if (transport != null) return transport.isSessionOpen();
|
|
358
|
|
359 return false;
|
|
360 }
|
|
361
|
|
362 public void setOnDisconnectedListener(BridgeDisconnectedListener disconnectListener) {
|
|
363 this.disconnectListener = disconnectListener;
|
|
364 }
|
|
365
|
|
366 /**
|
|
367 * Force disconnection of this terminal bridge.
|
|
368 */
|
|
369 public void dispatchDisconnect(boolean immediate) {
|
|
370 // We don't need to do this multiple times.
|
|
371 synchronized (this) {
|
|
372 if (disconnected && !immediate) return;
|
|
373
|
|
374 disconnected = true;
|
|
375 }
|
|
376
|
|
377 // Cancel any pending prompts.
|
|
378 promptHelper.cancelPrompt();
|
|
379 // disconnection request hangs if we havent really connected to a host yet
|
|
380 // temporary fix is to just spawn disconnection into a thread
|
|
381 Thread disconnectThread = new Thread(new Runnable() {
|
|
382 public void run() {
|
|
383 if (transport != null && transport.isConnected())
|
|
384 transport.close();
|
|
385 }
|
|
386 });
|
|
387 disconnectThread.setName("Disconnect");
|
|
388 disconnectThread.start();
|
|
389
|
|
390 if (immediate) {
|
|
391 awaitingClose = true;
|
|
392
|
|
393 if (disconnectListener != null)
|
|
394 disconnectListener.onDisconnected(TerminalBridge.this);
|
|
395 }
|
|
396 else {
|
|
397 final String line = manager.res.getString(R.string.alert_disconnect_msg);
|
|
398 buffer.putString("\r\n" + line + "\r\n");
|
|
399
|
|
400 if (host.getStayConnected()) {
|
|
401 manager.requestReconnect(this);
|
|
402 return;
|
|
403 }
|
|
404
|
|
405 Thread disconnectPromptThread = new Thread(new Runnable() {
|
|
406 public void run() {
|
|
407 Boolean result = promptHelper.requestBooleanPrompt(null,
|
|
408 manager.res.getString(R.string.prompt_host_disconnected));
|
|
409
|
|
410 if (result == null || result.booleanValue()) {
|
|
411 awaitingClose = true;
|
|
412
|
|
413 // Tell the TerminalManager that we can be destroyed now.
|
|
414 if (disconnectListener != null)
|
|
415 disconnectListener.onDisconnected(TerminalBridge.this);
|
|
416 }
|
|
417 }
|
|
418 });
|
|
419 disconnectPromptThread.setName("DisconnectPrompt");
|
|
420 disconnectPromptThread.setDaemon(true);
|
|
421 disconnectPromptThread.start();
|
|
422 }
|
|
423
|
|
424 // close the monitor
|
|
425 if (monitor != null) monitor.Disconnect();
|
112
|
426
|
0
|
427 monitor = null;
|
|
428 }
|
|
429
|
|
430 public void setSelectingForCopy(boolean selectingForCopy) {
|
|
431 this.selectingForCopy = selectingForCopy;
|
|
432 }
|
|
433
|
|
434 public boolean isSelectingForCopy() {
|
|
435 return selectingForCopy;
|
|
436 }
|
|
437
|
|
438 public SelectionArea getSelectionArea() {
|
|
439 return selectionArea;
|
|
440 }
|
|
441
|
|
442 public synchronized void tryKeyVibrate() {
|
|
443 manager.tryKeyVibrate();
|
|
444 }
|
|
445
|
|
446 /**
|
|
447 * Request a different font size. Will make call to parentChanged() to make
|
|
448 * sure we resize PTY if needed.
|
|
449 */
|
|
450 /* package */ final void setFontSize(float size) {
|
|
451 if (size <= 0.0)
|
|
452 return;
|
|
453
|
|
454 defaultPaint.setTextSize(size);
|
|
455 fontSize = size;
|
|
456 // read new metrics to get exact pixel dimensions
|
|
457 FontMetrics fm = defaultPaint.getFontMetrics();
|
|
458 charTop = (int)FloatMath.ceil(fm.top);
|
|
459 float[] widths = new float[1];
|
|
460 defaultPaint.getTextWidths("X", widths);
|
|
461 charWidth = (int)FloatMath.ceil(widths[0]);
|
|
462 charHeight = (int)FloatMath.ceil(fm.descent - fm.top);
|
|
463
|
|
464 // refresh any bitmap with new font size
|
47
|
465 if (parent != null) parentChanged(parent);
|
0
|
466
|
|
467 for (FontSizeChangedListener ofscl : fontSizeChangedListeners)
|
|
468 ofscl.onFontSizeChanged(size);
|
|
469
|
|
470 host.setFontSize((int) fontSize);
|
|
471 manager.hostdb.updateFontSize(host);
|
|
472 }
|
|
473
|
|
474 /**
|
|
475 * Add an {@link FontSizeChangedListener} to the list of listeners for this
|
|
476 * bridge.
|
|
477 *
|
|
478 * @param listener
|
|
479 * listener to add
|
|
480 */
|
|
481 public void addFontSizeChangedListener(FontSizeChangedListener listener) {
|
|
482 fontSizeChangedListeners.add(listener);
|
|
483 }
|
|
484
|
|
485 /**
|
|
486 * Remove an {@link FontSizeChangedListener} from the list of listeners for
|
|
487 * this bridge.
|
|
488 *
|
|
489 * @param listener
|
|
490 */
|
|
491 public void removeFontSizeChangedListener(FontSizeChangedListener listener) {
|
|
492 fontSizeChangedListeners.remove(listener);
|
|
493 }
|
|
494
|
|
495 /**
|
|
496 * Something changed in our parent {@link TerminalView}, maybe it's a new
|
|
497 * parent, or maybe it's an updated font size. We should recalculate
|
|
498 * terminal size information and request a PTY resize.
|
|
499 */
|
|
500
|
|
501 public final synchronized void parentChanged(TerminalView parent) {
|
|
502 if (manager != null && !manager.isResizeAllowed()) {
|
|
503 Log.d(TAG, "Resize is not allowed now");
|
|
504 return;
|
|
505 }
|
|
506
|
|
507 this.parent = parent;
|
|
508 final int width = parent.getWidth();
|
|
509 final int height = parent.getHeight();
|
|
510
|
|
511 // Something has gone wrong with our layout; we're 0 width or height!
|
|
512 if (width <= 0 || height <= 0)
|
|
513 return;
|
|
514
|
|
515 clipboard = (ClipboardManager) parent.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
516 keyListener.setClipboardManager(clipboard);
|
|
517
|
|
518 if (!forcedSize) {
|
|
519 // recalculate buffer size
|
|
520 int newColumns, newRows;
|
|
521 newColumns = width / charWidth;
|
|
522 newRows = height / charHeight;
|
|
523
|
|
524 // If nothing has changed in the terminal dimensions and not an intial
|
|
525 // draw then don't blow away scroll regions and such.
|
|
526 if (newColumns == columns && newRows == rows)
|
|
527 return;
|
|
528
|
|
529 columns = newColumns;
|
|
530 rows = newRows;
|
|
531 }
|
|
532
|
|
533 // reallocate new bitmap if needed
|
|
534 boolean newBitmap = (bitmap == null);
|
|
535
|
|
536 if (bitmap != null)
|
|
537 newBitmap = (bitmap.getWidth() != width || bitmap.getHeight() != height);
|
|
538
|
|
539 if (newBitmap) {
|
|
540 discardBitmap();
|
|
541 bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
|
|
542 canvas.setBitmap(bitmap);
|
|
543 }
|
|
544
|
|
545 // clear out any old buffer information
|
|
546 defaultPaint.setColor(Color.BLACK);
|
|
547 canvas.drawPaint(defaultPaint);
|
|
548
|
|
549 // Stroke the border of the terminal if the size is being forced;
|
|
550 if (forcedSize) {
|
|
551 int borderX = (columns * charWidth) + 1;
|
|
552 int borderY = (rows * charHeight) + 1;
|
|
553 defaultPaint.setColor(Color.GRAY);
|
|
554 defaultPaint.setStrokeWidth(0.0f);
|
|
555
|
|
556 if (width >= borderX)
|
|
557 canvas.drawLine(borderX, 0, borderX, borderY + 1, defaultPaint);
|
|
558
|
|
559 if (height >= borderY)
|
|
560 canvas.drawLine(0, borderY, borderX + 1, borderY, defaultPaint);
|
|
561 }
|
|
562
|
|
563 try {
|
|
564 // request a terminal pty resize
|
42
|
565 if (buffer != null) {
|
|
566 synchronized (buffer) {
|
|
567 buffer.setScreenSize(columns, rows, true);
|
|
568 }
|
0
|
569 }
|
|
570
|
|
571 if (transport != null)
|
|
572 transport.setDimensions(columns, rows, width, height);
|
|
573 }
|
|
574 catch (Exception e) {
|
|
575 Log.e(TAG, "Problem while trying to resize screen or PTY", e);
|
|
576 }
|
|
577
|
29
|
578 // redraw local output if we don't have a session to receive our resize request
|
0
|
579 if (transport == null) {
|
|
580 synchronized (localOutput) {
|
|
581 buffer.reset();
|
|
582
|
|
583 for (String line : localOutput)
|
|
584 buffer.putString(line);
|
|
585 }
|
|
586 }
|
|
587
|
|
588 // force full redraw with new buffer size
|
|
589 fullRedraw = true;
|
|
590 redraw();
|
|
591 parent.notifyUser(String.format("%d x %d", columns, rows));
|
|
592 Log.i(TAG, String.format("parentChanged() now width=%d, height=%d", columns, rows));
|
|
593 }
|
|
594
|
|
595 /**
|
|
596 * Somehow our parent {@link TerminalView} was destroyed. Now we don't need
|
|
597 * to redraw anywhere, and we can recycle our internal bitmap.
|
|
598 */
|
|
599
|
|
600 public synchronized void parentDestroyed() {
|
|
601 parent = null;
|
|
602 discardBitmap();
|
|
603 }
|
|
604
|
|
605 private void discardBitmap() {
|
|
606 if (bitmap != null)
|
|
607 bitmap.recycle();
|
|
608
|
|
609 bitmap = null;
|
|
610 }
|
|
611
|
|
612 public void propagateConsoleText(char[] rawText, int length) {
|
|
613 if (parent != null) {
|
|
614 parent.propagateConsoleText(rawText, length);
|
|
615 }
|
|
616 }
|
|
617
|
|
618 public void onDraw() {
|
|
619 int fg, bg;
|
|
620
|
|
621 synchronized (buffer) {
|
|
622 boolean entireDirty = buffer.update[0] || fullRedraw;
|
|
623 boolean isWideCharacter = false;
|
|
624
|
|
625 // walk through all lines in the buffer
|
|
626 for (int l = 0; l < buffer.height; l++) {
|
|
627 // check if this line is dirty and needs to be repainted
|
|
628 // also check for entire-buffer dirty flags
|
|
629 if (!entireDirty && !buffer.update[l + 1]) continue;
|
|
630
|
|
631 // reset dirty flag for this line
|
|
632 buffer.update[l + 1] = false;
|
|
633
|
|
634 // walk through all characters in this line
|
|
635 for (int c = 0; c < buffer.width; c++) {
|
|
636 int addr = 0;
|
|
637 int currAttr = buffer.charAttributes[buffer.windowBase + l][c];
|
|
638 {
|
|
639 int fgcolor = defaultFg;
|
|
640
|
|
641 // check if foreground color attribute is set
|
|
642 if ((currAttr & VDUBuffer.COLOR_FG) != 0)
|
|
643 fgcolor = ((currAttr & VDUBuffer.COLOR_FG) >> VDUBuffer.COLOR_FG_SHIFT) - 1;
|
|
644
|
|
645 if (fgcolor < 8 && (currAttr & VDUBuffer.BOLD) != 0)
|
|
646 fg = color[fgcolor + 8];
|
|
647 else
|
|
648 fg = color[fgcolor];
|
|
649 }
|
|
650
|
|
651 // check if background color attribute is set
|
|
652 if ((currAttr & VDUBuffer.COLOR_BG) != 0)
|
|
653 bg = color[((currAttr & VDUBuffer.COLOR_BG) >> VDUBuffer.COLOR_BG_SHIFT) - 1];
|
|
654 else
|
|
655 bg = color[defaultBg];
|
|
656
|
|
657 // support character inversion by swapping background and foreground color
|
|
658 if ((currAttr & VDUBuffer.INVERT) != 0) {
|
|
659 int swapc = bg;
|
|
660 bg = fg;
|
|
661 fg = swapc;
|
|
662 }
|
|
663
|
|
664 // set underlined attributes if requested
|
|
665 defaultPaint.setUnderlineText((currAttr & VDUBuffer.UNDERLINE) != 0);
|
|
666 isWideCharacter = (currAttr & VDUBuffer.FULLWIDTH) != 0;
|
|
667
|
|
668 if (isWideCharacter)
|
|
669 addr++;
|
|
670 else {
|
|
671 // determine the amount of continuous characters with the same settings and print them all at once
|
|
672 while (c + addr < buffer.width
|
|
673 && buffer.charAttributes[buffer.windowBase + l][c + addr] == currAttr) {
|
|
674 addr++;
|
|
675 }
|
|
676 }
|
|
677
|
|
678 // Save the current clip region
|
|
679 canvas.save(Canvas.CLIP_SAVE_FLAG);
|
|
680 // clear this dirty area with background color
|
|
681 defaultPaint.setColor(bg);
|
|
682
|
|
683 if (isWideCharacter) {
|
|
684 canvas.clipRect(c * charWidth,
|
|
685 l * charHeight,
|
|
686 (c + 2) * charWidth,
|
|
687 (l + 1) * charHeight);
|
|
688 }
|
|
689 else {
|
|
690 canvas.clipRect(c * charWidth,
|
|
691 l * charHeight,
|
|
692 (c + addr) * charWidth,
|
|
693 (l + 1) * charHeight);
|
|
694 }
|
|
695
|
|
696 canvas.drawPaint(defaultPaint);
|
|
697 // write the text string starting at 'c' for 'addr' number of characters
|
|
698 defaultPaint.setColor(fg);
|
|
699
|
|
700 if ((currAttr & VDUBuffer.INVISIBLE) == 0)
|
|
701 canvas.drawText(buffer.charArray[buffer.windowBase + l], c,
|
|
702 addr, c * charWidth, (l * charHeight) - charTop,
|
|
703 defaultPaint);
|
|
704
|
|
705 // Restore the previous clip region
|
|
706 canvas.restore();
|
|
707 // advance to the next text block with different characteristics
|
|
708 c += addr - 1;
|
|
709
|
|
710 if (isWideCharacter)
|
|
711 c++;
|
|
712 }
|
|
713 }
|
|
714
|
|
715 // reset entire-buffer flags
|
|
716 buffer.update[0] = false;
|
|
717 }
|
|
718
|
|
719 fullRedraw = false;
|
|
720 }
|
|
721
|
|
722 public void redraw() {
|
|
723 if (parent != null)
|
|
724 parent.postInvalidate();
|
|
725 }
|
|
726
|
|
727 // We don't have a scroll bar.
|
|
728 public void updateScrollBar() {
|
|
729 }
|
|
730
|
|
731 /**
|
|
732 * Resize terminal to fit [rows]x[cols] in screen of size [width]x[height]
|
|
733 * @param rows
|
|
734 * @param cols
|
|
735 * @param width
|
|
736 * @param height
|
|
737 */
|
|
738
|
|
739 public synchronized void resizeComputed(int cols, int rows, int width, int height) {
|
|
740 float size = 8.0f;
|
|
741 float step = 8.0f;
|
|
742 float limit = 0.125f;
|
|
743 int direction;
|
|
744
|
|
745 while ((direction = fontSizeCompare(size, cols, rows, width, height)) < 0)
|
|
746 size += step;
|
|
747
|
|
748 if (direction == 0) {
|
|
749 Log.d("fontsize", String.format("Found match at %f", size));
|
|
750 return;
|
|
751 }
|
|
752
|
|
753 step /= 2.0f;
|
|
754 size -= step;
|
|
755
|
|
756 while ((direction = fontSizeCompare(size, cols, rows, width, height)) != 0
|
|
757 && step >= limit) {
|
|
758 step /= 2.0f;
|
|
759
|
|
760 if (direction > 0) {
|
|
761 size -= step;
|
|
762 }
|
|
763 else {
|
|
764 size += step;
|
|
765 }
|
|
766 }
|
|
767
|
|
768 if (direction > 0)
|
|
769 size -= step;
|
|
770
|
|
771 this.columns = cols;
|
|
772 this.rows = rows;
|
|
773 forcedSize = true;
|
|
774 setFontSize(size);
|
|
775 }
|
|
776
|
|
777 private int fontSizeCompare(float size, int cols, int rows, int width, int height) {
|
|
778 // read new metrics to get exact pixel dimensions
|
|
779 defaultPaint.setTextSize(size);
|
|
780 FontMetrics fm = defaultPaint.getFontMetrics();
|
|
781 float[] widths = new float[1];
|
|
782 defaultPaint.getTextWidths("X", widths);
|
|
783 int termWidth = (int)widths[0] * cols;
|
|
784 int termHeight = (int)FloatMath.ceil(fm.descent - fm.top) * rows;
|
|
785 Log.d("fontsize", String.format("font size %f resulted in %d x %d", size, termWidth, termHeight));
|
|
786
|
|
787 // Check to see if it fits in resolution specified.
|
|
788 if (termWidth > width || termHeight > height)
|
|
789 return 1;
|
|
790
|
|
791 if (termWidth == width || termHeight == height)
|
|
792 return 0;
|
|
793
|
|
794 return -1;
|
|
795 }
|
|
796
|
|
797 /**
|
|
798 * @return whether underlying transport can forward ports
|
|
799 */
|
|
800 public boolean canFowardPorts() {
|
|
801 return transport.canForwardPorts();
|
|
802 }
|
|
803
|
|
804 /**
|
|
805 * Adds the {@link PortForwardBean} to the list.
|
|
806 * @param portForward the port forward bean to add
|
|
807 * @return true on successful addition
|
|
808 */
|
|
809 public boolean addPortForward(PortForwardBean portForward) {
|
|
810 return transport.addPortForward(portForward);
|
|
811 }
|
|
812
|
|
813 /**
|
|
814 * Removes the {@link PortForwardBean} from the list.
|
|
815 * @param portForward the port forward bean to remove
|
|
816 * @return true on successful removal
|
|
817 */
|
|
818 public boolean removePortForward(PortForwardBean portForward) {
|
|
819 return transport.removePortForward(portForward);
|
|
820 }
|
|
821
|
|
822 /**
|
|
823 * @return the list of port forwards
|
|
824 */
|
|
825 public List<PortForwardBean> getPortForwards() {
|
|
826 return transport.getPortForwards();
|
|
827 }
|
|
828
|
|
829 /**
|
|
830 * Enables a port forward member. After calling this method, the port forward should
|
|
831 * be operational.
|
|
832 * @param portForward member of our current port forwards list to enable
|
|
833 * @return true on successful port forward setup
|
|
834 */
|
|
835 public boolean enablePortForward(PortForwardBean portForward) {
|
|
836 if (!transport.isConnected()) {
|
|
837 Log.i(TAG, "Attempt to enable port forward while not connected");
|
|
838 return false;
|
|
839 }
|
|
840
|
|
841 return transport.enablePortForward(portForward);
|
|
842 }
|
|
843
|
|
844 /**
|
|
845 * Disables a port forward member. After calling this method, the port forward should
|
|
846 * be non-functioning.
|
|
847 * @param portForward member of our current port forwards list to enable
|
|
848 * @return true on successful port forward tear-down
|
|
849 */
|
|
850 public boolean disablePortForward(PortForwardBean portForward) {
|
|
851 if (!transport.isConnected()) {
|
|
852 Log.i(TAG, "Attempt to disable port forward while not connected");
|
|
853 return false;
|
|
854 }
|
|
855
|
|
856 return transport.disablePortForward(portForward);
|
|
857 }
|
|
858
|
|
859 /**
|
|
860 * @return whether underlying transport can transfer files
|
|
861 */
|
|
862 public boolean canTransferFiles() {
|
|
863 return transport.canTransferFiles();
|
|
864 }
|
|
865
|
|
866 /**
|
|
867 * Downloads the specified remote file to the local connectbot folder.
|
|
868 * @return true on success, false on failure
|
|
869 */
|
|
870 public boolean downloadFile(String remoteFile, String localFolder) {
|
|
871 return transport.downloadFile(remoteFile, localFolder);
|
|
872 }
|
|
873
|
|
874 /**
|
|
875 * Uploads the specified local file to the remote host's default directory.
|
|
876 * @return true on success, false on failure
|
|
877 */
|
|
878 public boolean uploadFile(String localFile, String remoteFolder, String remoteFile, String mode) {
|
|
879 if (mode == null)
|
|
880 mode = "0600";
|
|
881
|
|
882 return transport.uploadFile(localFile, remoteFolder, remoteFile, mode);
|
|
883 }
|
|
884
|
|
885 /**
|
|
886 * @return whether the TerminalBridge should close
|
|
887 */
|
|
888 public boolean isAwaitingClose() {
|
|
889 return awaitingClose;
|
|
890 }
|
|
891
|
|
892 /**
|
|
893 * @return whether this connection had started and subsequently disconnected
|
|
894 */
|
|
895 public boolean isDisconnected() {
|
|
896 return disconnected;
|
|
897 }
|
|
898
|
|
899 /* (non-Javadoc)
|
|
900 * @see de.mud.terminal.VDUDisplay#setColor(byte, byte, byte, byte)
|
|
901 */
|
|
902 public void setColor(int index, int red, int green, int blue) {
|
|
903 // Don't allow the system colors to be overwritten for now. May violate specs.
|
|
904 if (index < color.length && index >= 16)
|
|
905 color[index] = 0xff000000 | red << 16 | green << 8 | blue;
|
|
906 }
|
|
907
|
|
908 public final void resetColors() {
|
|
909 int[] defaults = manager.hostdb.getDefaultColorsForScheme(HostDatabase.DEFAULT_COLOR_SCHEME);
|
|
910 defaultFg = defaults[0];
|
|
911 defaultBg = defaults[1];
|
|
912 color = manager.hostdb.getColorsForScheme(HostDatabase.DEFAULT_COLOR_SCHEME);
|
|
913 }
|
|
914
|
|
915 private static Pattern urlPattern = null;
|
|
916
|
|
917 /**
|
|
918 * @return
|
|
919 */
|
|
920 public List<String> scanForURLs() {
|
|
921 Set<String> urls = new LinkedHashSet<String>();
|
|
922
|
|
923 if (urlPattern == null) {
|
|
924 // based on http://www.ietf.org/rfc/rfc2396.txt
|
|
925 String scheme = "[A-Za-z][-+.0-9A-Za-z]*";
|
|
926 String unreserved = "[-._~0-9A-Za-z]";
|
|
927 String pctEncoded = "%[0-9A-Fa-f]{2}";
|
|
928 String subDelims = "[!$&'()*+,;:=]";
|
|
929 String userinfo = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + "|:)*";
|
|
930 String h16 = "[0-9A-Fa-f]{1,4}";
|
|
931 String decOctet = "(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])";
|
|
932 String ipv4address = decOctet + "\\." + decOctet + "\\." + decOctet + "\\." + decOctet;
|
|
933 String ls32 = "(?:" + h16 + ":" + h16 + "|" + ipv4address + ")";
|
|
934 String ipv6address = "(?:(?:" + h16 + "){6}" + ls32 + ")";
|
|
935 String ipvfuture = "v[0-9A-Fa-f]+.(?:" + unreserved + "|" + subDelims + "|:)+";
|
|
936 String ipLiteral = "\\[(?:" + ipv6address + "|" + ipvfuture + ")\\]";
|
|
937 String regName = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + ")*";
|
|
938 String host = "(?:" + ipLiteral + "|" + ipv4address + "|" + regName + ")";
|
|
939 String port = "[0-9]*";
|
|
940 String authority = "(?:" + userinfo + "@)?" + host + "(?::" + port + ")?";
|
|
941 String pchar = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + "|@)";
|
|
942 String segment = pchar + "*";
|
|
943 String pathAbempty = "(?:/" + segment + ")*";
|
|
944 String segmentNz = pchar + "+";
|
|
945 String pathAbsolute = "/(?:" + segmentNz + "(?:/" + segment + ")*)?";
|
|
946 String pathRootless = segmentNz + "(?:/" + segment + ")*";
|
|
947 String hierPart = "(?://" + authority + pathAbempty + "|" + pathAbsolute + "|" + pathRootless + ")";
|
|
948 String query = "(?:" + pchar + "|/|\\?)*";
|
|
949 String fragment = "(?:" + pchar + "|/|\\?)*";
|
|
950 String uriRegex = scheme + ":" + hierPart + "(?:" + query + ")?(?:#" + fragment + ")?";
|
|
951 urlPattern = Pattern.compile(uriRegex);
|
|
952 }
|
|
953
|
|
954 char[] visibleBuffer = new char[buffer.height * buffer.width];
|
|
955
|
|
956 for (int l = 0; l < buffer.height; l++)
|
|
957 System.arraycopy(buffer.charArray[buffer.windowBase + l], 0,
|
|
958 visibleBuffer, l * buffer.width, buffer.width);
|
|
959
|
|
960 Matcher urlMatcher = urlPattern.matcher(new String(visibleBuffer));
|
|
961
|
|
962 while (urlMatcher.find())
|
|
963 urls.add(urlMatcher.group());
|
|
964
|
|
965 return (new LinkedList<String> (urls));
|
|
966 }
|
|
967
|
|
968 /**
|
|
969 * @return
|
|
970 */
|
|
971 public boolean isUsingNetwork() {
|
|
972 return transport.usesNetwork();
|
|
973 }
|
|
974
|
|
975 /**
|
|
976 * @return
|
|
977 */
|
|
978 public TerminalKeyListener getKeyHandler() {
|
|
979 return keyListener;
|
|
980 }
|
|
981
|
|
982 /**
|
|
983 *
|
|
984 */
|
|
985 public void resetScrollPosition() {
|
|
986 // if we're in scrollback, scroll to bottom of window on input
|
|
987 if (buffer.windowBase != buffer.screenBase)
|
|
988 buffer.setWindowBase(buffer.screenBase);
|
|
989 }
|
|
990
|
|
991 /**
|
|
992 *
|
|
993 */
|
|
994 public void increaseFontSize() {
|
|
995 setFontSize(fontSize + FONT_SIZE_STEP);
|
|
996 }
|
|
997
|
|
998 /**
|
|
999 *
|
|
1000 */
|
|
1001 public void decreaseFontSize() {
|
|
1002 setFontSize(fontSize - FONT_SIZE_STEP);
|
|
1003 }
|
|
1004
|
|
1005 /**
|
|
1006 * Auto-size window back to default
|
|
1007 */
|
|
1008 public void resetSize(TerminalView parent) {
|
|
1009 this.forcedSize = false;
|
|
1010 Integer defaultFontSize = Integer.parseInt(manager.prefs.getString(PreferenceConstants.DEFAULT_FONT_SIZE, "-1"));
|
|
1011
|
|
1012 if (this.fontSize != -1 && defaultFontSize > 0)
|
|
1013 setFontSize(defaultFontSize);
|
|
1014 else
|
|
1015 setFontSize(DEFAULT_FONT_SIZE);
|
|
1016 }
|
|
1017
|
|
1018 /**
|
|
1019 * Create a screenshot of the current view
|
|
1020 */
|
|
1021 public void captureScreen() {
|
|
1022 String msg;
|
|
1023 File dir, path;
|
|
1024 boolean success = true;
|
|
1025 Bitmap screenshot = this.bitmap;
|
|
1026
|
|
1027 if (manager == null || parent == null || screenshot == null)
|
|
1028 return;
|
|
1029
|
|
1030 SimpleDateFormat s = new SimpleDateFormat("yyyyMMdd_HHmmss");
|
|
1031 String date = s.format(new Date());
|
|
1032 String pref_path = manager.prefs.getString(PreferenceConstants.SCREEN_CAPTURE_FOLDER, "");
|
|
1033 File default_path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
|
1034
|
|
1035 if (pref_path.equals(""))
|
|
1036 dir = default_path;
|
|
1037 else
|
|
1038 dir = new File(pref_path);
|
|
1039
|
|
1040 path = new File(dir, "vx-" + date + ".png");
|
|
1041
|
|
1042 try {
|
|
1043 dir.mkdirs();
|
|
1044 FileOutputStream out = new FileOutputStream(path);
|
|
1045 screenshot.compress(Bitmap.CompressFormat.PNG, 90, out);
|
|
1046 out.close();
|
|
1047 }
|
|
1048 catch (Exception e) {
|
|
1049 e.printStackTrace();
|
|
1050 success = false;
|
|
1051 }
|
|
1052
|
|
1053 if (success) {
|
|
1054 msg = manager.getResources().getString(R.string.screenshot_saved_as) + " " + path;
|
|
1055
|
|
1056 if (manager.prefs.getBoolean(PreferenceConstants.SCREEN_CAPTURE_POPUP, true)) {
|
|
1057 new AlertDialog.Builder(parent.getContext())
|
|
1058 .setTitle(R.string.screenshot_success_title)
|
|
1059 .setMessage(msg)
|
|
1060 .setPositiveButton(R.string.button_close, null)
|
|
1061 .show();
|
|
1062 }
|
|
1063 }
|
|
1064 else {
|
|
1065 msg = manager.getResources().getString(R.string.screenshot_not_saved_as) + " " + path;
|
|
1066 new AlertDialog.Builder(parent.getContext())
|
|
1067 .setTitle(R.string.screenshot_error_title)
|
|
1068 .setMessage(msg)
|
|
1069 .setNegativeButton(R.string.button_close, null)
|
|
1070 .show();
|
|
1071 }
|
|
1072
|
|
1073 return;
|
|
1074 }
|
|
1075
|
|
1076 /**
|
|
1077 * Show change font size dialog
|
|
1078 */
|
|
1079 public boolean showFontSizeDialog() {
|
|
1080 final String pickerString = "+-";
|
|
1081 CharSequence str = "";
|
|
1082 Editable content = Editable.Factory.getInstance().newEditable(str);
|
|
1083
|
|
1084 if (parent == null)
|
|
1085 return false;
|
|
1086
|
|
1087 CharacterPickerDialog cpd = new CharacterPickerDialog(parent.getContext(),
|
|
1088 parent, content, pickerString, true) {
|
|
1089 private void changeFontSize(CharSequence result) {
|
|
1090 if (result.equals("+"))
|
|
1091 increaseFontSize();
|
|
1092 else if (result.equals("-"))
|
|
1093 decreaseFontSize();
|
|
1094 }
|
|
1095 @Override
|
|
1096 public void onItemClick(AdapterView p, View v, int pos, long id) {
|
|
1097 final String result = String.valueOf(pickerString.charAt(pos));
|
|
1098 changeFontSize(result);
|
|
1099 }
|
|
1100 @Override
|
|
1101 public void onClick(View v) {
|
|
1102 if (v instanceof Button) {
|
|
1103 final CharSequence result = ((Button) v).getText();
|
|
1104
|
|
1105 if (result.equals(""))
|
|
1106 dismiss();
|
|
1107 else
|
|
1108 changeFontSize(result);
|
|
1109 }
|
|
1110 }
|
|
1111 @Override
|
|
1112 public boolean dispatchKeyEvent(KeyEvent event) {
|
|
1113 if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
1114 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK)
|
|
1115 dismiss();
|
|
1116
|
|
1117 return keyListener.onKey(parent, event.getKeyCode(), event);
|
|
1118 }
|
|
1119
|
|
1120 return true;
|
|
1121 }
|
|
1122 };
|
|
1123 cpd.show();
|
|
1124 return true;
|
|
1125 }
|
|
1126
|
|
1127 /**
|
|
1128 * Show arrows dialog
|
|
1129 */
|
|
1130 public boolean showArrowsDialog() {
|
|
1131 final String pickerString = "â†â†’↑↓TIDE";
|
|
1132 CharSequence str = "";
|
|
1133 Editable content = Editable.Factory.getInstance().newEditable(str);
|
|
1134
|
|
1135 if (parent == null)
|
|
1136 return false;
|
|
1137
|
|
1138 CharacterPickerDialog cpd = new CharacterPickerDialog(parent.getContext(),
|
|
1139 parent, content, pickerString, true) {
|
|
1140 private void buttonPressed(CharSequence result) {
|
|
1141 if (result.equals("â†"))
|
|
1142 buffer.keyPressed(vt320.KEY_LEFT, ' ', 0);
|
|
1143 else if (result.equals("→"))
|
|
1144 buffer.keyPressed(vt320.KEY_RIGHT, ' ', 0);
|
|
1145 else if (result.equals("↑"))
|
|
1146 buffer.keyPressed(vt320.KEY_UP, ' ', 0);
|
|
1147 else if (result.equals("↓"))
|
|
1148 buffer.keyPressed(vt320.KEY_DOWN, ' ', 0);
|
58
|
1149 else if (result.equals("T"))
|
|
1150 buffer.keyPressed(vt320.KEY_TAB, ' ', 0);
|
0
|
1151 else if (result.equals("I"))
|
|
1152 buffer.keyPressed(vt320.KEY_INSERT, ' ', 0);
|
|
1153 else if (result.equals("D"))
|
|
1154 buffer.keyPressed(vt320.KEY_DELETE, ' ', 0);
|
|
1155 else if (result.equals("E"))
|
58
|
1156 buffer.keyPressed(vt320.KEY_ENTER, ' ', 0);
|
0
|
1157 }
|
|
1158 @Override
|
|
1159 public void onItemClick(AdapterView p, View v, int pos, long id) {
|
|
1160 final String result = String.valueOf(pickerString.charAt(pos));
|
|
1161 buttonPressed(result);
|
|
1162 }
|
|
1163 @Override
|
|
1164 public void onClick(View v) {
|
|
1165 if (v instanceof Button) {
|
|
1166 final CharSequence result = ((Button) v).getText();
|
|
1167
|
|
1168 if (result.equals(""))
|
|
1169 dismiss();
|
|
1170 else
|
|
1171 buttonPressed(result);
|
|
1172 }
|
|
1173 }
|
|
1174 @Override
|
|
1175 public boolean dispatchKeyEvent(KeyEvent event) {
|
|
1176 if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
1177 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK)
|
|
1178 dismiss();
|
|
1179
|
|
1180 return keyListener.onKey(parent, event.getKeyCode(), event);
|
|
1181 }
|
|
1182
|
|
1183 return true;
|
|
1184 }
|
|
1185 };
|
|
1186 cpd.show();
|
|
1187 return true;
|
|
1188 }
|
|
1189
|
|
1190
|
|
1191 /**
|
|
1192 * CTRL dialog
|
|
1193 */
|
|
1194 private String getCtrlString() {
|
|
1195 final String defaultSet = "ABCDEKLOQRWSTUXZ";
|
|
1196 String set = manager.prefs.getString(PreferenceConstants.CTRL_STRING, defaultSet);
|
|
1197
|
|
1198 if (set == null || set.equals("")) {
|
|
1199 set = defaultSet;
|
|
1200 }
|
|
1201
|
|
1202 return set;
|
|
1203 }
|
|
1204
|
|
1205 public boolean showCtrlDialog() {
|
|
1206 CharSequence str = "";
|
|
1207 Editable content = Editable.Factory.getInstance().newEditable(str);
|
|
1208
|
|
1209 if (parent == null)
|
|
1210 return false;
|
|
1211
|
|
1212 CharacterPickerDialog cpd = new CharacterPickerDialog(parent.getContext(),
|
|
1213 parent, content, getCtrlString(), true) {
|
|
1214 private void buttonPressed(CharSequence result) {
|
|
1215 int code = result.toString().toUpperCase().charAt(0) - 64;
|
|
1216
|
|
1217 if (code > 0 && code < 80) {
|
|
1218 try {
|
|
1219 transport.write(code);
|
|
1220 }
|
|
1221 catch (IOException e) {
|
|
1222 Log.d(TAG, "Error writing CTRL+" + result.toString().toUpperCase().charAt(0));
|
|
1223 }
|
|
1224 }
|
|
1225
|
|
1226 dismiss();
|
|
1227 }
|
|
1228 @Override
|
|
1229 public void onItemClick(AdapterView p, View v, int pos, long id) {
|
|
1230 final String result = String.valueOf(getCtrlString().charAt(pos));
|
|
1231 buttonPressed(result);
|
|
1232 }
|
|
1233 @Override
|
|
1234 public void onClick(View v) {
|
|
1235 if (v instanceof Button) {
|
|
1236 final CharSequence result = ((Button) v).getText();
|
|
1237
|
|
1238 if (result.equals(""))
|
|
1239 dismiss();
|
|
1240 else
|
|
1241 buttonPressed(result);
|
|
1242 }
|
|
1243 }
|
|
1244 @Override
|
|
1245 public boolean dispatchKeyEvent(KeyEvent event) {
|
|
1246 if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
1247 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK)
|
|
1248 dismiss();
|
|
1249
|
|
1250 return keyListener.onKey(parent, event.getKeyCode(), event);
|
|
1251 }
|
|
1252
|
|
1253 return true;
|
|
1254 }
|
|
1255 };
|
|
1256 cpd.show();
|
|
1257 return true;
|
|
1258 }
|
|
1259
|
|
1260 /**
|
|
1261 * Function keys dialog
|
|
1262 */
|
|
1263 public boolean showFKeysDialog() {
|
|
1264 final String pickerString = "1234567890";
|
|
1265 CharSequence str = "";
|
|
1266 Editable content = Editable.Factory.getInstance().newEditable(str);
|
|
1267
|
|
1268 if (parent == null)
|
|
1269 return false;
|
|
1270
|
|
1271 CharacterPickerDialog cpd = new CharacterPickerDialog(parent.getContext(),
|
|
1272 parent, content, pickerString, true) {
|
|
1273 private void buttonPressed(CharSequence result) {
|
|
1274 int key = 0;
|
|
1275
|
|
1276 if (result.equals("1"))
|
|
1277 key = vt320.KEY_F1;
|
|
1278 else if (result.equals("2"))
|
|
1279 key = vt320.KEY_F2;
|
|
1280 else if (result.equals("3"))
|
|
1281 key = vt320.KEY_F3;
|
|
1282 else if (result.equals("4"))
|
|
1283 key = vt320.KEY_F4;
|
|
1284 else if (result.equals("5"))
|
|
1285 key = vt320.KEY_F5;
|
|
1286 else if (result.equals("6"))
|
|
1287 key = vt320.KEY_F6;
|
|
1288 else if (result.equals("7"))
|
|
1289 key = vt320.KEY_F7;
|
|
1290 else if (result.equals("8"))
|
|
1291 key = vt320.KEY_F8;
|
|
1292 else if (result.equals("9"))
|
|
1293 key = vt320.KEY_F9;
|
|
1294 else if (result.equals("0"))
|
|
1295 key = vt320.KEY_F10;
|
|
1296
|
58
|
1297 if (key != 0) buffer.keyPressed(key, ' ', 0);
|
0
|
1298
|
|
1299 dismiss();
|
|
1300 }
|
|
1301 @Override
|
|
1302 public void onItemClick(AdapterView p, View v, int pos, long id) {
|
|
1303 final String result = String.valueOf(pickerString.charAt(pos));
|
|
1304 buttonPressed(result);
|
|
1305 }
|
|
1306 @Override
|
|
1307 public void onClick(View v) {
|
|
1308 if (v instanceof Button) {
|
|
1309 final CharSequence result = ((Button) v).getText();
|
|
1310
|
|
1311 if (result.equals(""))
|
|
1312 dismiss();
|
|
1313 else
|
|
1314 buttonPressed(result);
|
|
1315 }
|
|
1316 }
|
|
1317 @Override
|
|
1318 public boolean dispatchKeyEvent(KeyEvent event) {
|
|
1319 if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
1320 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK)
|
|
1321 dismiss();
|
|
1322
|
|
1323 return keyListener.onKey(parent, event.getKeyCode(), event);
|
|
1324 }
|
|
1325
|
|
1326 return true;
|
|
1327 }
|
|
1328 };
|
|
1329 cpd.show();
|
|
1330 return true;
|
|
1331 }
|
|
1332
|
|
1333 private String getPickerString() {
|
|
1334 final String defaultSet = "~\\^()[]{}<>|/:_;,.!@#$%&*?\"'-+=";
|
|
1335 String set = manager.prefs.getString(PreferenceConstants.PICKER_STRING, defaultSet);
|
|
1336
|
|
1337 if (set == null || set.equals("")) {
|
|
1338 set = defaultSet;
|
|
1339 }
|
|
1340
|
|
1341 return set;
|
|
1342 }
|
|
1343
|
|
1344 public boolean showCharPickerDialog() {
|
|
1345 CharSequence str = "";
|
|
1346 Editable content = Editable.Factory.getInstance().newEditable(str);
|
|
1347
|
|
1348 if (parent == null || !transport.isAuthenticated())
|
|
1349 return false;
|
|
1350
|
|
1351 CharacterPickerDialog cpd = new CharacterPickerDialog(parent.getContext(),
|
|
1352 parent, content, getPickerString(), true) {
|
|
1353 private void writeChar(CharSequence result) {
|
|
1354 try {
|
|
1355 if (transport.isAuthenticated())
|
|
1356 transport.write(result.toString().getBytes(getCharset().name()));
|
|
1357 }
|
|
1358 catch (IOException e) {
|
|
1359 Log.e(TAG, "Problem with the CharacterPickerDialog", e);
|
|
1360 }
|
|
1361
|
|
1362 if (!manager.prefs.getBoolean(PreferenceConstants.PICKER_KEEP_OPEN, false))
|
|
1363 dismiss();
|
|
1364 }
|
|
1365 @Override
|
|
1366 public void onItemClick(AdapterView p, View v, int pos, long id) {
|
|
1367 String result = String.valueOf(getPickerString().charAt(pos));
|
|
1368 writeChar(result);
|
|
1369 }
|
|
1370 @Override
|
|
1371 public void onClick(View v) {
|
|
1372 if (v instanceof Button) {
|
|
1373 CharSequence result = ((Button) v).getText();
|
|
1374
|
|
1375 if (result.equals(""))
|
|
1376 dismiss();
|
|
1377 else
|
|
1378 writeChar(result);
|
|
1379 }
|
|
1380 }
|
|
1381 @Override
|
|
1382 public boolean dispatchKeyEvent(KeyEvent event) {
|
|
1383 int keyCode = event.getKeyCode();
|
|
1384
|
|
1385 if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
1386 // close window if SYM or BACK keys are pressed
|
|
1387 if (keyListener.isSymKey(keyCode) ||
|
|
1388 keyCode == KeyEvent.KEYCODE_BACK) {
|
|
1389 dismiss();
|
|
1390 return true;
|
|
1391 }
|
|
1392 }
|
112
|
1393
|
0
|
1394 return super.dispatchKeyEvent(event);
|
|
1395 }
|
|
1396 };
|
|
1397 cpd.show();
|
|
1398 return true;
|
|
1399 }
|
|
1400 }
|