Mercurial > 510Connectbot
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 |