comparison src/com/five_ten_sg/connectbot/service/TerminalKeyListener.java @ 0:0ce5cc452d02

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