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