comparison app/src/main/java/com/five_ten_sg/connectbot/service/TerminalKeyListener.java @ 438:d29cce60f393

migrate from Eclipse to Android Studio
author Carl Byington <carl@five-ten-sg.com>
date Thu, 03 Dec 2015 11:23:55 -0800
parents src/com/five_ten_sg/connectbot/service/TerminalKeyListener.java@208b31032318
children 7c8aebcc882a
comparison
equal deleted inserted replaced
437:208b31032318 438:d29cce60f393
1 /*
2 * ConnectBot: simple, powerful, open-source SSH client for Android
3 * Copyright 2010 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 package com.five_ten_sg.connectbot.service;
18
19 import java.io.IOException;
20 import java.io.UnsupportedEncodingException;
21 import java.nio.charset.Charset;
22 import java.lang.ref.WeakReference;
23 import java.util.List;
24
25 import com.five_ten_sg.connectbot.R;
26 import com.five_ten_sg.connectbot.TerminalView;
27 import com.five_ten_sg.connectbot.bean.SelectionArea;
28 import com.five_ten_sg.connectbot.util.PreferenceConstants;
29 import android.app.Dialog;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.SharedPreferences;
33 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
34 import android.content.res.Configuration;
35 import android.net.Uri;
36 import android.preference.PreferenceManager;
37 import android.text.ClipboardManager;
38 import android.util.Log;
39 import android.view.Gravity;
40 import android.view.KeyCharacterMap;
41 import android.view.KeyEvent;
42 import android.view.View;
43 import android.view.View.OnKeyListener;
44 import android.widget.AdapterView;
45 import android.widget.AdapterView.OnItemClickListener;
46 import android.widget.ArrayAdapter;
47 import android.widget.ListView;
48 import android.widget.TextView;
49 import de.mud.terminal.VDUBuffer;
50 import de.mud.terminal.vt320;
51
52 /**
53 * @author kenny
54 *
55 */
56 @SuppressWarnings("deprecation") // for ClipboardManager
57 public class TerminalKeyListener implements OnKeyListener, OnSharedPreferenceChangeListener {
58 private static final String TAG = "ConnectBot.OnKeyListener";
59
60 public final static int META_CTRL_ON = 0x01;
61 public final static int META_CTRL_LOCK = 0x02;
62 public final static int META_ALT_ON = 0x04;
63 public final static int META_ALT_LOCK = 0x08;
64 public final static int META_SHIFT_ON = 0x10;
65 public final static int META_SHIFT_LOCK = 0x20;
66 public final static int META_SLASH = 0x40;
67 public final static int META_TAB = 0x80;
68
69 // The bit mask of momentary and lock states for each
70 public final static int META_CTRL_MASK = META_CTRL_ON | META_CTRL_LOCK;
71 public final static int META_ALT_MASK = META_ALT_ON | META_ALT_LOCK;
72 public final static int META_SHIFT_MASK = META_SHIFT_ON | META_SHIFT_LOCK;
73
74 // backport constants from api level 11
75 public final static int KEYCODE_ESCAPE = 111;
76 public final static int HC_META_CTRL_ON = 4096;
77 public final static int KEYCODE_PAGE_UP = 92;
78 public final static int KEYCODE_PAGE_DOWN = 93;
79
80 // All the transient key codes
81 public final static int META_TRANSIENT = META_CTRL_ON | META_ALT_ON
82 | META_SHIFT_ON;
83
84 protected final TerminalManager manager;
85 protected final TerminalBridge bridge;
86 protected final vt320 buffer;
87 protected String encoding;
88
89 protected String keymode = null;
90 protected boolean hardKeyboard = false;
91 protected boolean hardKeyboardHidden;
92 protected String customKeyboard = null;
93
94 protected int metaState = 0;
95 protected int mDeadKey = 0;
96
97 // TODO add support for the new API.
98 private ClipboardManager clipboard = null;
99 private boolean selectingForCopy = false;
100 private final SelectionArea selectionArea;
101 protected final SharedPreferences prefs;
102
103
104 public TerminalKeyListener(TerminalManager manager,
105 TerminalBridge bridge,
106 vt320 buffer,
107 String encoding) {
108 this.manager = manager;
109 this.bridge = bridge;
110 this.buffer = buffer;
111 this.encoding = encoding;
112 selectionArea = new SelectionArea();
113 prefs = PreferenceManager.getDefaultSharedPreferences(manager);
114 prefs.registerOnSharedPreferenceChangeListener(this);
115 hardKeyboard = (manager.res.getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY);
116 hardKeyboardHidden = manager.hardKeyboardHidden;
117 updateKeymode();
118 updateCustomKeymap();
119 }
120
121 public void sendEscape() {
122 buffer.keyPressed(vt320.KEY_ESCAPE, ' ', getStateForBuffer());
123 }
124
125 protected void sendEncoded(String s) {
126 byte [] b = null;
127
128 try {
129 b = s.getBytes(encoding);
130 }
131 catch (UnsupportedEncodingException e) {
132 }
133
134 if (b != null) buffer.write(b);
135 }
136
137 /**
138 * Handle onKey() events coming down from a {@link com.five_ten_sg.connectbot.TerminalView} above us.
139 * Modify the keys to make more sense to a host then pass it to the vt320.
140 */
141 public boolean onKey(View v, int keyCode, KeyEvent event) {
142 try {
143 int repeat = event.getRepeatCount();
144
145 // skip keys if we aren't connected yet or have been disconnected
146 if (bridge.isDisconnected()) return false;
147
148 // short cuts can see repeat counts and key up/down
149 if (handleShortcuts(v, keyCode, event, repeat, (event.getAction() == KeyEvent.ACTION_DOWN))) return true;
150
151 // Ignore all key-up events except for the special keys
152 if (event.getAction() == KeyEvent.ACTION_UP) {
153 // There's nothing else here for virtual keyboard users.
154 if (!hardKeyboard || hardKeyboardHidden) return false;
155
156 // if keycode debugging enabled, log and print the pressed key
157 if (prefs.getBoolean(PreferenceConstants.DEBUG_KEYCODES, false)) {
158 String keyCodeString = String.format(": %d", keyCode);
159 String toastText = v.getContext().getString(R.string.keycode_pressed) + keyCodeString;
160 Log.d(TAG, toastText);
161 }
162
163 if (fullKeyboard()) {
164 switch (keyCode) {
165 case KeyEvent.KEYCODE_CTRL_LEFT:
166 case KeyEvent.KEYCODE_CTRL_RIGHT:
167 metaKeyUp(META_CTRL_ON);
168 return true;
169
170 case KeyEvent.KEYCODE_ALT_LEFT:
171 case KeyEvent.KEYCODE_ALT_RIGHT:
172 metaKeyUp(META_ALT_ON);
173 return true;
174
175 case KeyEvent.KEYCODE_SHIFT_LEFT:
176 case KeyEvent.KEYCODE_SHIFT_RIGHT:
177 metaKeyUp(META_SHIFT_ON);
178 return true;
179
180 default:
181 }
182 }
183 else if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) {
184 if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT
185 && (metaState & META_SLASH) != 0) {
186 metaState &= ~(META_SLASH | META_TRANSIENT);
187 buffer.write('/');
188 return true;
189 }
190 else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT
191 && (metaState & META_TAB) != 0) {
192 metaState &= ~(META_TAB | META_TRANSIENT);
193 buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer());
194 return true;
195 }
196 }
197 else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) {
198 if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
199 && (metaState & META_SLASH) != 0) {
200 metaState &= ~(META_SLASH | META_TRANSIENT);
201 buffer.write('/');
202 return true;
203 }
204 else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
205 && (metaState & META_TAB) != 0) {
206 metaState &= ~(META_TAB | META_TRANSIENT);
207 buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer());
208 return true;
209 }
210 }
211
212 return false;
213 }
214
215 bridge.resetScrollPosition();
216
217 if (keyCode == KeyEvent.KEYCODE_UNKNOWN &&
218 event.getAction() == KeyEvent.ACTION_MULTIPLE) {
219 sendEncoded(event.getCharacters());
220 return true;
221 }
222
223 int curMetaState = event.getMetaState();
224 final int orgMetaState = curMetaState;
225
226 if ((metaState & META_SHIFT_MASK) != 0) {
227 curMetaState |= KeyEvent.META_SHIFT_ON;
228 }
229
230 if ((metaState & META_ALT_MASK) != 0) {
231 curMetaState |= KeyEvent.META_ALT_ON;
232 }
233
234 int uchar = event.getUnicodeChar(curMetaState);
235
236 // no hard keyboard? ALT-k should pass through to below
237 if ((orgMetaState & KeyEvent.META_ALT_ON) != 0 &&
238 (!hardKeyboard || hardKeyboardHidden)) {
239 uchar = 0;
240 }
241
242 if ((uchar & KeyCharacterMap.COMBINING_ACCENT) != 0) {
243 mDeadKey = uchar & KeyCharacterMap.COMBINING_ACCENT_MASK;
244 return true;
245 }
246
247 if (mDeadKey != 0 && uchar != 0) {
248 uchar = KeyCharacterMap.getDeadChar(mDeadKey, uchar);
249 mDeadKey = 0;
250 }
251
252 // handle customized keymaps
253 if (customKeymapAction(v, keyCode, event))
254 return true;
255
256 if (v != null) {
257 //Show up the CharacterPickerDialog when the SYM key is pressed
258 if ((isSymKey(keyCode) || uchar == KeyCharacterMap.PICKER_DIALOG_INPUT)) {
259 bridge.showCharPickerDialog();
260
261 if (metaState == 4) { // reset fn-key state
262 metaState = 0;
263 bridge.redraw();
264 }
265
266 return true;
267 }
268 else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
269 //Show up the URL scan dialog when the search key is pressed
270 urlScan(v);
271 return true;
272 }
273 }
274
275 // otherwise pass through to existing session
276 // print normal keys
277 if (uchar > 0x00 && keyCode != KeyEvent.KEYCODE_ENTER) {
278 metaState &= ~(META_SLASH | META_TAB);
279 // Remove shift and alt modifiers
280 final int lastMetaState = metaState;
281 metaState &= ~(META_SHIFT_ON | META_ALT_ON);
282
283 if (metaState != lastMetaState) {
284 bridge.redraw();
285 }
286
287 if ((metaState & META_CTRL_MASK) != 0) {
288 metaState &= ~META_CTRL_ON;
289 bridge.redraw();
290
291 // If there is no hard keyboard or there is a hard keyboard currently hidden,
292 // CTRL-1 through CTRL-9 will send F1 through F9
293 if ((!hardKeyboard || hardKeyboardHidden) && sendFunctionKey(keyCode))
294 return true;
295
296 uchar = keyAsControl(uchar);
297 }
298
299 // handle pressing f-keys
300 if ((hardKeyboard && !hardKeyboardHidden)
301 && (curMetaState & KeyEvent.META_ALT_ON) != 0
302 && (curMetaState & KeyEvent.META_SHIFT_ON) != 0
303 && sendFunctionKey(keyCode))
304 return true;
305
306 if (uchar < 0x80)
307 buffer.write(uchar);
308 else
309 sendEncoded(new String(Character.toChars(uchar)));
310
311 return true;
312 }
313
314 // send ctrl and meta-keys as appropriate
315 if (!hardKeyboard || hardKeyboardHidden) {
316 int k = event.getUnicodeChar(0);
317 int k0 = k;
318 boolean sendCtrl = false;
319 boolean sendMeta = false;
320
321 if (k != 0) {
322 if ((orgMetaState & HC_META_CTRL_ON) != 0) {
323 k = keyAsControl(k);
324 if (k != k0) sendCtrl = true;
325 // send F1-F10 via CTRL-1 through CTRL-0
326 if (!sendCtrl && sendFunctionKey(keyCode))
327 return true;
328 }
329 else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) {
330 sendMeta = true;
331 sendEscape();
332 }
333
334 if (sendMeta || sendCtrl) {
335 buffer.write(k);
336 return true;
337 }
338 }
339 }
340
341 // handle meta and f-keys for full hardware keyboard
342 if (hardKeyboard && !hardKeyboardHidden && fullKeyboard()) {
343 int k = event.getUnicodeChar(orgMetaState & KeyEvent.META_SHIFT_ON);
344 int k0 = k;
345
346 if (k != 0) {
347 if ((orgMetaState & HC_META_CTRL_ON) != 0) {
348 k = keyAsControl(k);
349 if (k != k0) buffer.write(k);
350 return true;
351 }
352 else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) {
353 sendEscape();
354 buffer.write(k);
355 return true;
356 }
357 }
358
359 if (sendFullSpecialKey(keyCode))
360 return true;
361 }
362
363 // try handling keymode shortcuts
364 if (hardKeyboard && !hardKeyboardHidden && (repeat == 0)) {
365 if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) {
366 switch (keyCode) {
367 case KeyEvent.KEYCODE_ALT_RIGHT:
368 metaState |= META_SLASH;
369 return true;
370
371 case KeyEvent.KEYCODE_SHIFT_RIGHT:
372 metaState |= META_TAB;
373 return true;
374
375 case KeyEvent.KEYCODE_SHIFT_LEFT:
376 metaPress(META_SHIFT_ON);
377 return true;
378
379 case KeyEvent.KEYCODE_ALT_LEFT:
380 metaPress(META_ALT_ON);
381 return true;
382 }
383 }
384 else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) {
385 switch (keyCode) {
386 case KeyEvent.KEYCODE_ALT_LEFT:
387 metaState |= META_SLASH;
388 return true;
389
390 case KeyEvent.KEYCODE_SHIFT_LEFT:
391 metaState |= META_TAB;
392 return true;
393
394 case KeyEvent.KEYCODE_SHIFT_RIGHT:
395 metaPress(META_SHIFT_ON);
396 return true;
397
398 case KeyEvent.KEYCODE_ALT_RIGHT:
399 metaPress(META_ALT_ON);
400 return true;
401 }
402 }
403 else {
404 switch (keyCode) {
405 case KeyEvent.KEYCODE_ALT_RIGHT:
406 case KeyEvent.KEYCODE_ALT_LEFT:
407 metaPress(META_ALT_ON);
408 return true;
409
410 case KeyEvent.KEYCODE_SHIFT_LEFT:
411 case KeyEvent.KEYCODE_SHIFT_RIGHT:
412 metaPress(META_SHIFT_ON);
413 return true;
414 }
415 }
416
417 // Handle hardware CTRL keys
418 if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT ||
419 keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) {
420 ctrlKeySpecial();
421 return true;
422 }
423 }
424
425 // look for special chars
426 switch (keyCode) {
427 case KeyEvent.KEYCODE_ESCAPE:
428 sendEscape();
429 return true;
430
431 case KeyEvent.KEYCODE_TAB:
432 buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer());
433 return true;
434
435 case KeyEvent.KEYCODE_PAGE_DOWN:
436 buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ', getStateForBuffer());
437 metaState &= ~META_TRANSIENT;
438 bridge.tryKeyVibrate();
439 return true;
440
441 case KeyEvent.KEYCODE_PAGE_UP:
442 buffer.keyPressed(vt320.KEY_PAGE_UP, ' ', getStateForBuffer());
443 metaState &= ~META_TRANSIENT;
444 bridge.tryKeyVibrate();
445 return true;
446
447 case KeyEvent.KEYCODE_MOVE_HOME:
448 buffer.keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer());
449 metaState &= ~META_TRANSIENT;
450 bridge.tryKeyVibrate();
451 return true;
452
453 case KeyEvent.KEYCODE_MOVE_END:
454 buffer.keyPressed(vt320.KEY_END, ' ', getStateForBuffer());
455 metaState &= ~META_TRANSIENT;
456 bridge.tryKeyVibrate();
457 return true;
458
459 case KeyEvent.KEYCODE_DEL:
460 if ((metaState & META_ALT_MASK) != 0) {
461 buffer.keyPressed(vt320.KEY_INSERT, ' ', getStateForBuffer());
462 }
463 else {
464 buffer.keyPressed(vt320.KEY_BACK_SPACE, ' ', getStateForBuffer());
465 }
466
467 metaState &= ~META_TRANSIENT;
468 return true;
469
470 case KeyEvent.KEYCODE_ENTER:
471 buffer.keyPressed(vt320.KEY_ENTER, ' ', getStateForBuffer());
472 metaState &= ~META_TRANSIENT;
473 return true;
474
475 case KeyEvent.KEYCODE_DPAD_LEFT:
476 if (selectingForCopy) {
477 selectionArea.decrementColumn();
478 bridge.redraw();
479 }
480 else {
481 if ((metaState & META_ALT_MASK) != 0) {
482 buffer.keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer());
483 }
484 else {
485 buffer.keyPressed(vt320.KEY_LEFT, ' ', getStateForBuffer());
486 }
487
488 metaState &= ~META_TRANSIENT;
489 bridge.tryKeyVibrate();
490 }
491
492 return true;
493
494 case KeyEvent.KEYCODE_DPAD_UP:
495 if (selectingForCopy) {
496 selectionArea.decrementRow();
497 bridge.redraw();
498 }
499 else {
500 if ((metaState & META_ALT_MASK) != 0) {
501 buffer.keyPressed(vt320.KEY_PAGE_UP, ' ', getStateForBuffer());
502 }
503 else {
504 buffer.keyPressed(vt320.KEY_UP, ' ', getStateForBuffer());
505 }
506
507 metaState &= ~META_TRANSIENT;
508 bridge.tryKeyVibrate();
509 }
510
511 return true;
512
513 case KeyEvent.KEYCODE_DPAD_DOWN:
514 if (selectingForCopy) {
515 selectionArea.incrementRow();
516 bridge.redraw();
517 }
518 else {
519 if ((metaState & META_ALT_MASK) != 0) {
520 buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ', getStateForBuffer());
521 }
522 else {
523 buffer.keyPressed(vt320.KEY_DOWN, ' ', getStateForBuffer());
524 }
525
526 metaState &= ~META_TRANSIENT;
527 bridge.tryKeyVibrate();
528 }
529
530 return true;
531
532 case KeyEvent.KEYCODE_DPAD_RIGHT:
533 if (selectingForCopy) {
534 selectionArea.incrementColumn();
535 bridge.redraw();
536 }
537 else {
538 if ((metaState & META_ALT_MASK) != 0) {
539 buffer.keyPressed(vt320.KEY_END, ' ', getStateForBuffer());
540 }
541 else {
542 buffer.keyPressed(vt320.KEY_RIGHT, ' ', getStateForBuffer());
543 }
544
545 metaState &= ~META_TRANSIENT;
546 bridge.tryKeyVibrate();
547 }
548
549 return true;
550
551 case KeyEvent.KEYCODE_DPAD_CENTER:
552 ctrlKeySpecial();
553 return true;
554 }
555 }
556 catch (NullPointerException npe) {
557 Log.d(TAG, "Input before connection established ignored.");
558 return true;
559 }
560
561 return false;
562 }
563
564 private boolean handleShortcuts(View v, int keyCode, KeyEvent event, int repeat, boolean down) {
565 String hwbuttonShortcut;
566
567 switch (keyCode) {
568 case KeyEvent.KEYCODE_CAMERA:
569 // check to see which shortcut the camera button triggers
570 hwbuttonShortcut = manager.prefs.getString(
571 PreferenceConstants.CAMERA,
572 PreferenceConstants.HWBUTTON_SCREEN_CAPTURE);
573 return (handleShortcut(v, hwbuttonShortcut, repeat, down));
574
575 case KeyEvent.KEYCODE_VOLUME_UP:
576 // check to see which shortcut the volume button triggers
577 hwbuttonShortcut = manager.prefs.getString(
578 PreferenceConstants.VOLUP,
579 PreferenceConstants.HWBUTTON_FUNCTION_KEYS);
580 return (handleShortcut(v, hwbuttonShortcut, repeat, down));
581
582 case KeyEvent.KEYCODE_VOLUME_DOWN:
583 // check to see which shortcut the camera button triggers
584 hwbuttonShortcut = manager.prefs.getString(
585 PreferenceConstants.VOLDN,
586 PreferenceConstants.HWBUTTON_TAB);
587 return (handleShortcut(v, hwbuttonShortcut, repeat, down));
588
589 case KeyEvent.KEYCODE_SEARCH:
590 // check to see which shortcut the search button triggers
591 hwbuttonShortcut = manager.prefs.getString(
592 PreferenceConstants.SEARCH,
593 PreferenceConstants.HWBUTTON_ESC);
594 return (handleShortcut(v, hwbuttonShortcut, repeat, down));
595
596 case KeyEvent.KEYCODE_BUTTON_L2:
597 // check to see which shortcut the ptt button triggers
598 hwbuttonShortcut = manager.prefs.getString(
599 PreferenceConstants.PTT,
600 PreferenceConstants.HWBUTTON_MONITOR);
601 return (handleShortcut(v, hwbuttonShortcut, repeat, down));
602
603 default: return false;
604 }
605 }
606
607 private boolean handleShortcut(View v, String shortcut, int repeat, boolean down) {
608 if (PreferenceConstants.HWBUTTON_DECREASE_FONTSIZE.equals(shortcut)) {
609 if (!down) return false;
610
611 bridge.decreaseFontSize();
612 }
613 else if (PreferenceConstants.HWBUTTON_INCREASE_FONTSIZE.equals(shortcut)) {
614 if (!down) return false;
615
616 bridge.increaseFontSize();
617 }
618 else if (PreferenceConstants.HWBUTTON_FUNCTION_KEYS.equals(shortcut)) {
619 if (repeat > 0) return false;
620
621 if (!down) return false;
622
623 bridge.showFKeysDialog();
624 }
625 else if (PreferenceConstants.HWBUTTON_MONITOR.equals(shortcut)) {
626 if (repeat > 0) return false;
627
628 buffer.monitorKey(down);
629 }
630 else if (PreferenceConstants.HWBUTTON_SCREEN_CAPTURE.equals(shortcut)) {
631 if (repeat > 0) return false;
632
633 if (!down) return false;
634
635 bridge.captureScreen();
636 }
637 else if (PreferenceConstants.HWBUTTON_CTRL.equals(shortcut)) {
638 if (!down) return false;
639
640 showMetakeyToast(v, PreferenceConstants.HWBUTTON_CTRL);
641 metaPress(META_CTRL_ON);
642 }
643 else if (PreferenceConstants.HWBUTTON_TAB.equals(shortcut)) {
644 if (!down) return false;
645
646 buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer());
647 }
648 else if (PreferenceConstants.HWBUTTON_CTRLA_SPACE.equals(shortcut)) {
649 if (!down) return false;
650
651 buffer.write(0x01);
652 buffer.write(' ');
653 }
654 else if (PreferenceConstants.HWBUTTON_CTRLA.equals(shortcut)) {
655 if (!down) return false;
656
657 buffer.write(0x01);
658 }
659 else if (PreferenceConstants.HWBUTTON_ESC.equals(shortcut)) {
660 if (!down) return false;
661
662 showMetakeyToast(v, PreferenceConstants.HWBUTTON_ESC);
663 sendEscape();
664 }
665 else if (PreferenceConstants.HWBUTTON_ESC_A.equals(shortcut)) {
666 if (!down) return false;
667
668 sendEscape();
669 buffer.write('a');
670 }
671 else {
672 return (false);
673 }
674
675 return (true);
676 }
677
678 private void showMetakeyToast(View v, String keyname) {
679 Log.d(TAG, keyname);
680 }
681
682 public int keyAsControl(int key) {
683 // Support CTRL-a through CTRL-z
684 if (key >= 0x60 && key <= 0x7A)
685 key -= 0x60;
686 // Support CTRL-A through CTRL-_
687 else if (key >= 0x40 && key <= 0x5F)
688 key -= 0x40;
689 // CTRL-space sends NULL
690 else if (key == 0x20)
691 key = 0x00;
692 // CTRL-? sends DEL
693 else if (key == 0x3F)
694 key = 0x7F;
695
696 return key;
697 }
698
699 /**
700 * @param key
701 * @return successful
702 */
703 private boolean sendFunctionKey(int keyCode) {
704 switch (keyCode) {
705 case KeyEvent.KEYCODE_1:
706 buffer.keyPressed(vt320.KEY_F1, ' ', 0);
707 return true;
708
709 case KeyEvent.KEYCODE_2:
710 buffer.keyPressed(vt320.KEY_F2, ' ', 0);
711 return true;
712
713 case KeyEvent.KEYCODE_3:
714 buffer.keyPressed(vt320.KEY_F3, ' ', 0);
715 return true;
716
717 case KeyEvent.KEYCODE_4:
718 buffer.keyPressed(vt320.KEY_F4, ' ', 0);
719 return true;
720
721 case KeyEvent.KEYCODE_5:
722 buffer.keyPressed(vt320.KEY_F5, ' ', 0);
723 return true;
724
725 case KeyEvent.KEYCODE_6:
726 buffer.keyPressed(vt320.KEY_F6, ' ', 0);
727 return true;
728
729 case KeyEvent.KEYCODE_7:
730 buffer.keyPressed(vt320.KEY_F7, ' ', 0);
731 return true;
732
733 case KeyEvent.KEYCODE_8:
734 buffer.keyPressed(vt320.KEY_F8, ' ', 0);
735 return true;
736
737 case KeyEvent.KEYCODE_9:
738 buffer.keyPressed(vt320.KEY_F9, ' ', 0);
739 return true;
740
741 case KeyEvent.KEYCODE_0:
742 buffer.keyPressed(vt320.KEY_F10, ' ', 0);
743 return true;
744
745 default:
746 return false;
747 }
748 }
749
750 private boolean sendFullSpecialKey(int keyCode) {
751 switch (keyCode) {
752 case KeyEvent.KEYCODE_F1:
753 buffer.keyPressed(vt320.KEY_F1, ' ', 0);
754 return true;
755
756 case KeyEvent.KEYCODE_F2:
757 buffer.keyPressed(vt320.KEY_F2, ' ', 0);
758 return true;
759
760 case KeyEvent.KEYCODE_F3:
761 buffer.keyPressed(vt320.KEY_F3, ' ', 0);
762 return true;
763
764 case KeyEvent.KEYCODE_F4:
765 buffer.keyPressed(vt320.KEY_F4, ' ', 0);
766 return true;
767
768 case KeyEvent.KEYCODE_F5:
769 buffer.keyPressed(vt320.KEY_F5, ' ', 0);
770 return true;
771
772 case KeyEvent.KEYCODE_F6:
773 buffer.keyPressed(vt320.KEY_F6, ' ', 0);
774 return true;
775
776 case KeyEvent.KEYCODE_F7:
777 buffer.keyPressed(vt320.KEY_F7, ' ', 0);
778 return true;
779
780 case KeyEvent.KEYCODE_F8:
781 buffer.keyPressed(vt320.KEY_F8, ' ', 0);
782 return true;
783
784 case KeyEvent.KEYCODE_F9:
785 buffer.keyPressed(vt320.KEY_F9, ' ', 0);
786 return true;
787
788 case KeyEvent.KEYCODE_F10:
789 buffer.keyPressed(vt320.KEY_F10, ' ', 0);
790 return true;
791
792 case KeyEvent.KEYCODE_F11:
793 buffer.keyPressed(vt320.KEY_F11, ' ', 0);
794 return true;
795
796 case KeyEvent.KEYCODE_F12:
797 buffer.keyPressed(vt320.KEY_F12, ' ', 0);
798 return true;
799
800 case KeyEvent.KEYCODE_INSERT:
801 buffer.keyPressed(vt320.KEY_INSERT, ' ', 0);
802 return true;
803
804 case KeyEvent.KEYCODE_FORWARD_DEL:
805 buffer.keyPressed(vt320.KEY_DELETE, ' ', 0);
806 return true;
807
808 /*
809 case KeyEvent.KEYCODE_PAGE_UP:
810 buffer.keyPressed(vt320.KEY_PAGE_UP, ' ', 0);
811 return true;
812 case KeyEvent.KEYCODE_PAGE_DOWN:
813 buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0);
814 return true;
815 case KeyEvent.KEYCODE_MOVE_HOME:
816 buffer.keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer());
817 return true;
818 case KeyEvent.KEYCODE_MOVE_END:
819 buffer.keyPressed(vt320.KEY_END, ' ', getStateForBuffer());
820 return true;
821 */
822 default:
823 return false;
824 }
825 }
826
827 /**
828 * Handle meta key presses for full hardware keyboard
829 */
830 private void metaKeyDown(int code) {
831 if ((metaState & code) == 0) {
832 metaState |= code;
833 bridge.redraw();
834 }
835 }
836
837 protected void metaKeyUp(int code) {
838 if ((metaState & code) != 0) {
839 metaState &= ~code;
840 bridge.redraw();
841 }
842 }
843
844 /**
845 * Handle meta key presses where the key can be locked on.
846 * <p>
847 * 1st press: next key to have meta state<br />
848 * 2nd press: meta state is locked on<br />
849 * 3rd press: disable meta state
850 *
851 * @param code
852 */
853 public void metaPress(int code) {
854 if ((metaState & (code << 1)) != 0) {
855 metaState &= ~(code << 1);
856 }
857 else if ((metaState & code) != 0) {
858 metaState &= ~code;
859
860 if (!fullKeyboard())
861 metaState |= code << 1;
862 }
863 else
864 metaState |= code;
865
866 bridge.redraw();
867 }
868
869 public void setTerminalKeyMode(String keymode) {
870 this.keymode = keymode;
871 }
872
873 private int getStateForBuffer() {
874 int bufferState = 0;
875
876 if ((metaState & META_CTRL_MASK) != 0)
877 bufferState |= vt320.KEY_CONTROL;
878
879 if ((metaState & META_SHIFT_MASK) != 0)
880 bufferState |= vt320.KEY_SHIFT;
881
882 if ((metaState & META_ALT_MASK) != 0)
883 bufferState |= vt320.KEY_ALT;
884
885 return bufferState;
886 }
887
888 public int getMetaState() {
889 return metaState;
890 }
891
892 public int getDeadKey() {
893 return mDeadKey;
894 }
895
896 public void setClipboardManager(ClipboardManager clipboard) {
897 this.clipboard = clipboard;
898 }
899
900 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
901 String key) {
902 if (PreferenceConstants.KEYMODE.equals(key)) {
903 updateKeymode();
904 }
905 else if (PreferenceConstants.CUSTOM_KEYMAP.equals(key)) {
906 updateCustomKeymap();
907 }
908 }
909
910 private void updateKeymode() {
911 keymode = prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT);
912 }
913
914 private void updateCustomKeymap() {
915 customKeyboard = prefs.getString(PreferenceConstants.CUSTOM_KEYMAP,
916 PreferenceConstants.CUSTOM_KEYMAP_DISABLED);
917 }
918
919 public void setCharset(String encoding) {
920 this.encoding = encoding;
921 }
922
923 public Charset getCharset() {
924 return Charset.forName(encoding);
925 }
926
927 protected void ctrlKeySpecial() {
928 if (selectingForCopy) {
929 if (selectionArea.isSelectingOrigin())
930 selectionArea.finishSelectingOrigin();
931 else {
932 if (clipboard != null) {
933 // copy selected area to clipboard
934 String copiedText = selectionArea.copyFrom(buffer);
935 clipboard.setText(copiedText);
936 selectingForCopy = false;
937 selectionArea.reset();
938 }
939 }
940 }
941 else {
942 if ((metaState & META_CTRL_ON) != 0) {
943 sendEscape();
944 metaState &= ~META_CTRL_ON;
945 }
946 else
947 metaPress(META_CTRL_ON);
948 }
949
950 bridge.redraw();
951 }
952
953 protected boolean customKeymapAction(View v, int keyCode, KeyEvent event) {
954 if (bridge == null || customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_DISABLED))
955 return false;
956
957 byte c = 0x00;
958 int termKey = 0;
959
960 if (fullKeyboard()) {
961 switch (keyCode) {
962 case KeyEvent.KEYCODE_CTRL_LEFT:
963 case KeyEvent.KEYCODE_CTRL_RIGHT:
964 metaKeyDown(META_CTRL_ON);
965 return true;
966
967 case KeyEvent.KEYCODE_ALT_LEFT:
968 case KeyEvent.KEYCODE_ALT_RIGHT:
969 metaKeyDown(META_ALT_ON);
970 return true;
971
972 case KeyEvent.KEYCODE_SHIFT_LEFT:
973 case KeyEvent.KEYCODE_SHIFT_RIGHT:
974 metaKeyDown(META_SHIFT_ON);
975 return true;
976
977 case KeyEvent.KEYCODE_BACK:
978 if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF)) {
979 // Check to see whether this is the back button on the
980 // screen (-1) or the Asus Transformer Keyboard Dock.
981 // Treat the HW button as ESC.
982 if (event.getDeviceId() > 0) {
983 sendEscape();
984 return true;
985 }
986 }
987
988 default:
989 }
990 }
991
992 if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF)) {
993 if ((metaState & META_ALT_MASK) != 0
994 && (metaState & META_SHIFT_MASK) != 0
995 && sendFunctionKey(keyCode))
996 return true;
997 }
998 else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SE_XPPRO)) {
999 // Sony Ericsson Xperia pro (MK16i) and Xperia mini Pro (SK17i)
1000 // Language key acts as CTRL
1001 if (keyCode == KeyEvent.KEYCODE_SWITCH_CHARSET) {
1002 ctrlKeySpecial();
1003 return true;
1004 }
1005
1006 if ((metaState & META_ALT_MASK) != 0) {
1007 if ((metaState & META_SHIFT_MASK) != 0) {
1008 // ALT + shift + key
1009 switch (keyCode) {
1010 case KeyEvent.KEYCODE_U:
1011 c = 0x5B;
1012 break;
1013
1014 case KeyEvent.KEYCODE_I:
1015 c = 0x5D;
1016 break;
1017
1018 case KeyEvent.KEYCODE_O:
1019 c = 0x7B;
1020 break;
1021
1022 case KeyEvent.KEYCODE_P:
1023 c = 0x7D;
1024 break;
1025 }
1026 }
1027 else {
1028 // ALT + key
1029 switch (keyCode) {
1030 case KeyEvent.KEYCODE_S:
1031 c = 0x7c;
1032 break;
1033
1034 case KeyEvent.KEYCODE_Z:
1035 c = 0x5c;
1036 break;
1037
1038 case KeyEvent.KEYCODE_DEL:
1039 termKey = vt320.KEY_DELETE;
1040 break;
1041 }
1042 }
1043 }
1044 else if ((metaState & META_SHIFT_MASK) != 0) {
1045 // shift + key
1046 switch (keyCode) {
1047 case KeyEvent.KEYCODE_AT:
1048 c = 0x3c;
1049 break;
1050
1051 case KeyEvent.KEYCODE_COMMA:
1052 c = 0x3e;
1053 break;
1054
1055 case KeyEvent.KEYCODE_PERIOD:
1056 c = 0x5e;
1057 break;
1058
1059 case KeyEvent.KEYCODE_GRAVE:
1060 c = 0x60;
1061 break;
1062
1063 case KeyEvent.KEYCODE_APOSTROPHE:
1064 c = 0x7e;
1065 break;
1066
1067 case KeyEvent.KEYCODE_DEL:
1068 termKey = vt320.KEY_BACK_SPACE;
1069 break;
1070 }
1071 }
1072 }
1073 else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927)) {
1074 // Samsung Captivate Glide (SGH-i927)
1075 if (keyCode == 115) {
1076 // .com key = ESC
1077 c = 0x00;
1078 termKey = vt320.KEY_ESCAPE;
1079 return true;
1080 }
1081 else if (keyCode == 116) {
1082 // Microphone key = TAB
1083 c = 0x00;
1084 termKey = vt320.KEY_TAB;
1085 }
1086 else if ((metaState & META_ALT_MASK) != 0 && (metaState & META_SHIFT_MASK) != 0) {
1087 switch (keyCode) {
1088 case KeyEvent.KEYCODE_O:
1089 c = 0x5B;
1090 break;
1091
1092 case KeyEvent.KEYCODE_P:
1093 c = 0x5D;
1094 break;
1095
1096 case KeyEvent.KEYCODE_A:
1097 c = 0x3C;
1098 break;
1099
1100 case KeyEvent.KEYCODE_D:
1101 c = 0x3E;
1102 break;
1103 }
1104 }
1105 }
1106 else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927_ICS)) {
1107 // Samsung Captivate Glide (SGH-i927) Ice Cream Sandwich (4.0.x)
1108 if (keyCode == 226) {
1109 // .com key = ESC
1110 c = 0x00;
1111 termKey = vt320.KEY_ESCAPE;
1112 }
1113 else if (keyCode == 220) {
1114 // Microphone key = TAB
1115 c = 0x00;
1116 termKey = vt320.KEY_TAB;
1117 }
1118 else if ((metaState & META_ALT_MASK) != 0 && (metaState & META_SHIFT_MASK) != 0) {
1119 switch (keyCode) {
1120 case KeyEvent.KEYCODE_O:
1121 c = 0x5B;
1122 break;
1123
1124 case KeyEvent.KEYCODE_P:
1125 c = 0x5D;
1126 break;
1127
1128 case KeyEvent.KEYCODE_A:
1129 c = 0x3C;
1130 break;
1131
1132 case KeyEvent.KEYCODE_D:
1133 c = 0x3E;
1134 break;
1135 }
1136 }
1137 }
1138
1139 if ((c != 0x00) || termKey != 0) {
1140 if (c != 0x00)
1141 buffer.write(c);
1142 else
1143 buffer.keyPressed(termKey, ' ', 0);
1144
1145 metaState &= ~(META_SHIFT_ON | META_ALT_ON);
1146 bridge.redraw();
1147 return true;
1148 }
1149
1150 return false;
1151 }
1152
1153 public void urlScan(View v) {
1154 //final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
1155 List<String> urls = bridge.scanForURLs();
1156 Dialog urlDialog = new Dialog(v.getContext());
1157 urlDialog.setTitle(R.string.console_menu_urlscan);
1158 ListView urlListView = new ListView(v.getContext());
1159 URLItemListener urlListener = new URLItemListener(v.getContext());
1160 urlListView.setOnItemClickListener(urlListener);
1161 urlListView.setAdapter(new ArrayAdapter<String> (v.getContext(), android.R.layout.simple_list_item_1, urls));
1162 urlDialog.setContentView(urlListView);
1163 urlDialog.show();
1164 }
1165
1166 public boolean isSymKey(int keyCode) {
1167 if (keyCode == KeyEvent.KEYCODE_SYM ||
1168 keyCode == KeyEvent.KEYCODE_PICTSYMBOLS)
1169 return true;
1170
1171 if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927_ICS) &&
1172 keyCode == 227)
1173 return true;
1174
1175 return false;
1176 }
1177
1178 protected boolean fullKeyboard() {
1179 if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_FULL) ||
1180 (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF)))
1181 return true;
1182
1183 return false;
1184 }
1185
1186 private class URLItemListener implements OnItemClickListener {
1187 private WeakReference<Context> contextRef;
1188
1189 URLItemListener(Context context) {
1190 this.contextRef = new WeakReference<Context> (context);
1191 }
1192
1193 public void onItemClick(AdapterView<?> arg0, View view, int position,
1194 long id) {
1195 Context context = contextRef.get();
1196
1197 if (context == null)
1198 return;
1199
1200 try {
1201 TextView urlView = (TextView) view;
1202 String url = urlView.getText().toString();
1203
1204 if (url.indexOf("://") < 0)
1205 url = "http://" + url;
1206
1207 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
1208 context.startActivity(intent);
1209 }
1210 catch (Exception e) {
1211 Log.e(TAG, "couldn't open URL", e);
1212 // We should probably tell the user that we couldn't find a
1213 // handler...
1214 }
1215 }
1216 }
1217 }