OSDN Git Service

Update to r512
[android-x86/packages-apps-ConnectBot.git] / src / org / connectbot / service / TerminalKeyListener.java
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 org.connectbot.service;
18
19 import java.io.IOException;
20
21 import org.connectbot.TerminalView;
22 import org.connectbot.bean.SelectionArea;
23 import org.connectbot.util.PreferenceConstants;
24
25 import android.content.SharedPreferences;
26 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
27 import android.content.res.Configuration;
28 import android.preference.PreferenceManager;
29 import android.text.ClipboardManager;
30 import android.util.Log;
31 import android.view.KeyCharacterMap;
32 import android.view.KeyEvent;
33 import android.view.View;
34 import android.view.View.OnKeyListener;
35 import de.mud.terminal.VDUBuffer;
36 import de.mud.terminal.vt320;
37
38 /**
39  * @author kenny
40  *
41  */
42 public class TerminalKeyListener implements OnKeyListener, OnSharedPreferenceChangeListener {
43         private static final String TAG = "ConnectBot.OnKeyListener";
44
45         public final static int META_CTRL_ON = 0x01;
46         public final static int META_CTRL_LOCK = 0x02;
47         public final static int META_ALT_ON = 0x04;
48         public final static int META_ALT_LOCK = 0x08;
49         public final static int META_SHIFT_ON = 0x10;
50         public final static int META_SHIFT_LOCK = 0x20;
51         public final static int META_SLASH = 0x40;
52         public final static int META_TAB = 0x80;
53
54         // The bit mask of momentary and lock states for each
55         public final static int META_CTRL_MASK = META_CTRL_ON | META_CTRL_LOCK;
56         public final static int META_ALT_MASK = META_ALT_ON | META_ALT_LOCK;
57         public final static int META_SHIFT_MASK = META_SHIFT_ON | META_SHIFT_LOCK;
58
59         // All the transient key codes
60         public final static int META_TRANSIENT = META_CTRL_ON | META_ALT_ON
61                         | META_SHIFT_ON;
62
63         private final TerminalManager manager;
64         private final TerminalBridge bridge;
65         private final VDUBuffer buffer;
66
67         protected KeyCharacterMap keymap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
68
69         private String keymode = null;
70         private boolean hardKeyboard = false;
71
72         private int metaState = 0;
73
74         private ClipboardManager clipboard = null;
75         private boolean selectingForCopy = false;
76         private final SelectionArea selectionArea;
77
78         private String encoding;
79
80         private final SharedPreferences prefs;
81
82         public TerminalKeyListener(TerminalManager manager,
83                         TerminalBridge bridge,
84                         VDUBuffer buffer,
85                         String encoding) {
86                 this.manager = manager;
87                 this.bridge = bridge;
88                 this.buffer = buffer;
89                 this.encoding = encoding;
90
91                 selectionArea = new SelectionArea();
92
93                 prefs = PreferenceManager.getDefaultSharedPreferences(manager);
94                 prefs.registerOnSharedPreferenceChangeListener(this);
95
96                 hardKeyboard = (manager.res.getConfiguration().keyboard
97                                 == Configuration.KEYBOARD_QWERTY);
98
99                 updateKeymode();
100         }
101
102         /**
103          * Handle onKey() events coming down from a {@link TerminalView} above us.
104          * Modify the keys to make more sense to a host then pass it to the transport.
105          */
106         public boolean onKey(View v, int keyCode, KeyEvent event) {
107                 try {
108                         final boolean hardKeyboardHidden = manager.hardKeyboardHidden;
109
110                         // Ignore all key-up events except for the special keys
111                         if (event.getAction() == KeyEvent.ACTION_UP) {
112                                 // There's nothing here for virtual keyboard users.
113                                 if (!hardKeyboard || (hardKeyboard && hardKeyboardHidden))
114                                         return false;
115
116                                 // skip keys if we aren't connected yet or have been disconnected
117                                 if (bridge.isDisconnected() || bridge.transport == null)
118                                         return false;
119
120                                 if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) {
121                                         if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT
122                                                         && (metaState & META_SLASH) != 0) {
123                                                 metaState &= ~(META_SLASH | META_TRANSIENT);
124                                                 bridge.transport.write('/');
125                                                 return true;
126                                         } else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT
127                                                         && (metaState & META_TAB) != 0) {
128                                                 metaState &= ~(META_TAB | META_TRANSIENT);
129                                                 bridge.transport.write(0x09);
130                                                 return true;
131                                         }
132                                 } else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) {
133                                         if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
134                                                         && (metaState & META_SLASH) != 0) {
135                                                 metaState &= ~(META_SLASH | META_TRANSIENT);
136                                                 bridge.transport.write('/');
137                                                 return true;
138                                         } else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
139                                                         && (metaState & META_TAB) != 0) {
140                                                 metaState &= ~(META_TAB | META_TRANSIENT);
141                                                 bridge.transport.write(0x09);
142                                                 return true;
143                                         }
144                                 }
145
146                                 return false;
147                         }
148
149                         // check for terminal resizing keys
150                         if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
151                                 bridge.increaseFontSize();
152                                 return true;
153                         } else if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
154                                 bridge.decreaseFontSize();
155                                 return true;
156                         }
157
158                         // skip keys if we aren't connected yet or have been disconnected
159                         if (bridge.isDisconnected() || bridge.transport == null)
160                                 return false;
161
162                         bridge.resetScrollPosition();
163
164                         boolean printing = (keymap.isPrintingKey(keyCode) || keyCode == KeyEvent.KEYCODE_SPACE);
165
166                         // otherwise pass through to existing session
167                         // print normal keys
168                         if (printing) {
169                                 int curMetaState = event.getMetaState();
170
171                                 metaState &= ~(META_SLASH | META_TAB);
172
173                                 if ((metaState & META_SHIFT_MASK) != 0) {
174                                         curMetaState |= KeyEvent.META_SHIFT_ON;
175                                         metaState &= ~META_SHIFT_ON;
176                                         bridge.redraw();
177                                 }
178
179                                 if ((metaState & META_ALT_MASK) != 0) {
180                                         curMetaState |= KeyEvent.META_ALT_ON;
181                                         metaState &= ~META_ALT_ON;
182                                         bridge.redraw();
183                                 }
184
185                                 int key = keymap.get(keyCode, curMetaState);
186
187                                 if ((metaState & META_CTRL_MASK) != 0) {
188                                         metaState &= ~META_CTRL_ON;
189                                         bridge.redraw();
190
191                                         if ((!hardKeyboard || (hardKeyboard && hardKeyboardHidden))
192                                                         && sendFunctionKey(keyCode))
193                                                 return true;
194
195                                         // Support CTRL-a through CTRL-z
196                                         if (key >= 0x61 && key <= 0x7A)
197                                                 key -= 0x60;
198                                         // Support CTRL-A through CTRL-_
199                                         else if (key >= 0x41 && key <= 0x5F)
200                                                 key -= 0x40;
201                                         else if (key == 0x20)
202                                                 key = 0x00;
203                                         else if (key == 0x3F)
204                                                 key = 0x7F;
205                                 }
206
207                                 // handle pressing f-keys
208                                 if ((hardKeyboard && !hardKeyboardHidden)
209                                                 && (curMetaState & KeyEvent.META_SHIFT_ON) != 0
210                                                 && sendFunctionKey(keyCode))
211                                         return true;
212
213                                 if (key < 0x80)
214                                         bridge.transport.write(key);
215                                 else
216                                         // TODO write encoding routine that doesn't allocate each time
217                                         bridge.transport.write(new String(Character.toChars(key))
218                                                         .getBytes(encoding));
219
220                                 return true;
221                         }
222
223                         if (keyCode == KeyEvent.KEYCODE_UNKNOWN &&
224                                         event.getAction() == KeyEvent.ACTION_MULTIPLE) {
225                                 byte[] input = event.getCharacters().getBytes(encoding);
226                                 bridge.transport.write(input);
227                                 return true;
228                         }
229
230                         // try handling keymode shortcuts
231                         if (hardKeyboard && !hardKeyboardHidden &&
232                                         event.getRepeatCount() == 0) {
233                                 if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) {
234                                         switch (keyCode) {
235                                         case KeyEvent.KEYCODE_ALT_RIGHT:
236                                                 metaState |= META_SLASH;
237                                                 return true;
238                                         case KeyEvent.KEYCODE_SHIFT_RIGHT:
239                                                 metaState |= META_TAB;
240                                                 return true;
241                                         case KeyEvent.KEYCODE_SHIFT_LEFT:
242                                                 metaPress(META_SHIFT_ON);
243                                                 return true;
244                                         case KeyEvent.KEYCODE_ALT_LEFT:
245                                                 metaPress(META_ALT_ON);
246                                                 return true;
247                                         }
248                                 } else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) {
249                                         switch (keyCode) {
250                                         case KeyEvent.KEYCODE_ALT_LEFT:
251                                                 metaState |= META_SLASH;
252                                                 return true;
253                                         case KeyEvent.KEYCODE_SHIFT_LEFT:
254                                                 metaState |= META_TAB;
255                                                 return true;
256                                         case KeyEvent.KEYCODE_SHIFT_RIGHT:
257                                                 metaPress(META_SHIFT_ON);
258                                                 return true;
259                                         case KeyEvent.KEYCODE_ALT_RIGHT:
260                                                 metaPress(META_ALT_ON);
261                                                 return true;
262                                         }
263                                 } else {
264                                         switch (keyCode) {
265                                         case KeyEvent.KEYCODE_ALT_LEFT:
266                                         case KeyEvent.KEYCODE_ALT_RIGHT:
267                                                 metaPress(META_ALT_ON);
268                                                 return true;
269                                         case KeyEvent.KEYCODE_SHIFT_LEFT:
270                                         case KeyEvent.KEYCODE_SHIFT_RIGHT:
271                                                 metaPress(META_SHIFT_ON);
272                                                 return true;
273                                         }
274                                 }
275                         }
276
277                         // look for special chars
278                         switch(keyCode) {
279                         case KeyEvent.KEYCODE_CAMERA:
280
281                                 // check to see which shortcut the camera button triggers
282                                 String camera = manager.prefs.getString(
283                                                 PreferenceConstants.CAMERA,
284                                                 PreferenceConstants.CAMERA_CTRLA_SPACE);
285                                 if(PreferenceConstants.CAMERA_CTRLA_SPACE.equals(camera)) {
286                                         bridge.transport.write(0x01);
287                                         bridge.transport.write(' ');
288                                 } else if(PreferenceConstants.CAMERA_CTRLA.equals(camera)) {
289                                         bridge.transport.write(0x01);
290                                 } else if(PreferenceConstants.CAMERA_ESC.equals(camera)) {
291                                         ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0);
292                                 } else if(PreferenceConstants.CAMERA_ESC_A.equals(camera)) {
293                                         ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0);
294                                         bridge.transport.write('a');
295                                 }
296
297                                 break;
298
299                         case KeyEvent.KEYCODE_DEL:
300                                 ((vt320) buffer).keyPressed(vt320.KEY_BACK_SPACE, ' ',
301                                                 getStateForBuffer());
302                                 metaState &= ~META_TRANSIENT;
303                                 return true;
304                         case KeyEvent.KEYCODE_ENTER:
305                                 ((vt320)buffer).keyTyped(vt320.KEY_ENTER, ' ', 0);
306                                 metaState &= ~META_TRANSIENT;
307                                 return true;
308
309                         case KeyEvent.KEYCODE_DPAD_LEFT:
310                                 if (selectingForCopy) {
311                                         selectionArea.decrementColumn();
312                                         bridge.redraw();
313                                 } else {
314                                         ((vt320) buffer).keyPressed(vt320.KEY_LEFT, ' ',
315                                                         getStateForBuffer());
316                                         metaState &= ~META_TRANSIENT;
317                                         bridge.tryKeyVibrate();
318                                 }
319                                 return true;
320
321                         case KeyEvent.KEYCODE_DPAD_UP:
322                                 if (selectingForCopy) {
323                                         selectionArea.decrementRow();
324                                         bridge.redraw();
325                                 } else {
326                                         ((vt320) buffer).keyPressed(vt320.KEY_UP, ' ',
327                                                         getStateForBuffer());
328                                         metaState &= ~META_TRANSIENT;
329                                         bridge.tryKeyVibrate();
330                                 }
331                                 return true;
332
333                         case KeyEvent.KEYCODE_DPAD_DOWN:
334                                 if (selectingForCopy) {
335                                         selectionArea.incrementRow();
336                                         bridge.redraw();
337                                 } else {
338                                         ((vt320) buffer).keyPressed(vt320.KEY_DOWN, ' ',
339                                                         getStateForBuffer());
340                                         metaState &= ~META_TRANSIENT;
341                                         bridge.tryKeyVibrate();
342                                 }
343                                 return true;
344
345                         case KeyEvent.KEYCODE_DPAD_RIGHT:
346                                 if (selectingForCopy) {
347                                         selectionArea.incrementColumn();
348                                         bridge.redraw();
349                                 } else {
350                                         ((vt320) buffer).keyPressed(vt320.KEY_RIGHT, ' ',
351                                                         getStateForBuffer());
352                                         metaState &= ~META_TRANSIENT;
353                                         bridge.tryKeyVibrate();
354                                 }
355                                 return true;
356
357                         case KeyEvent.KEYCODE_DPAD_CENTER:
358                                 if (selectingForCopy) {
359                                         if (selectionArea.isSelectingOrigin())
360                                                 selectionArea.finishSelectingOrigin();
361                                         else {
362                                                 if (clipboard != null) {
363                                                         // copy selected area to clipboard
364                                                         String copiedText = selectionArea.copyFrom(buffer);
365
366                                                         clipboard.setText(copiedText);
367                                                         // XXX STOPSHIP
368 //                                                      manager.notifyUser(manager.getString(
369 //                                                                      R.string.console_copy_done,
370 //                                                                      copiedText.length()));
371
372                                                         selectingForCopy = false;
373                                                         selectionArea.reset();
374                                                 }
375                                         }
376                                 } else {
377                                         if ((metaState & META_CTRL_ON) != 0) {
378                                                 sendEscape();
379                                                 metaState &= ~META_CTRL_ON;
380                                         } else
381                                                 metaPress(META_CTRL_ON);
382                                 }
383
384                                 bridge.redraw();
385
386                                 return true;
387                         }
388
389                 } catch (IOException e) {
390                         Log.e(TAG, "Problem while trying to handle an onKey() event", e);
391                         try {
392                                 bridge.transport.flush();
393                         } catch (IOException ioe) {
394                                 Log.d(TAG, "Our transport was closed, dispatching disconnect event");
395                                 bridge.dispatchDisconnect(false);
396                         }
397                 } catch (NullPointerException npe) {
398                         Log.d(TAG, "Input before connection established ignored.");
399                         return true;
400                 }
401
402                 return false;
403         }
404
405         public void sendEscape() {
406                 ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0);
407         }
408
409         /**
410          * @param key
411          * @return successful
412          */
413         private boolean sendFunctionKey(int keyCode) {
414                 switch (keyCode) {
415                 case KeyEvent.KEYCODE_1:
416                         ((vt320) buffer).keyPressed(vt320.KEY_F1, ' ', 0);
417                         return true;
418                 case KeyEvent.KEYCODE_2:
419                         ((vt320) buffer).keyPressed(vt320.KEY_F2, ' ', 0);
420                         return true;
421                 case KeyEvent.KEYCODE_3:
422                         ((vt320) buffer).keyPressed(vt320.KEY_F3, ' ', 0);
423                         return true;
424                 case KeyEvent.KEYCODE_4:
425                         ((vt320) buffer).keyPressed(vt320.KEY_F4, ' ', 0);
426                         return true;
427                 case KeyEvent.KEYCODE_5:
428                         ((vt320) buffer).keyPressed(vt320.KEY_F5, ' ', 0);
429                         return true;
430                 case KeyEvent.KEYCODE_6:
431                         ((vt320) buffer).keyPressed(vt320.KEY_F6, ' ', 0);
432                         return true;
433                 case KeyEvent.KEYCODE_7:
434                         ((vt320) buffer).keyPressed(vt320.KEY_F7, ' ', 0);
435                         return true;
436                 case KeyEvent.KEYCODE_8:
437                         ((vt320) buffer).keyPressed(vt320.KEY_F8, ' ', 0);
438                         return true;
439                 case KeyEvent.KEYCODE_9:
440                         ((vt320) buffer).keyPressed(vt320.KEY_F9, ' ', 0);
441                         return true;
442                 case KeyEvent.KEYCODE_0:
443                         ((vt320) buffer).keyPressed(vt320.KEY_F10, ' ', 0);
444                         return true;
445                 default:
446                         return false;
447                 }
448         }
449
450         /**
451          * Handle meta key presses where the key can be locked on.
452          * <p>
453          * 1st press: next key to have meta state<br />
454          * 2nd press: meta state is locked on<br />
455          * 3rd press: disable meta state
456          *
457          * @param code
458          */
459         public void metaPress(int code) {
460                 if ((metaState & (code << 1)) != 0) {
461                         metaState &= ~(code << 1);
462                 } else if ((metaState & code) != 0) {
463                         metaState &= ~code;
464                         metaState |= code << 1;
465                 } else
466                         metaState |= code;
467                 bridge.redraw();
468         }
469
470         public void setTerminalKeyMode(String keymode) {
471                 this.keymode = keymode;
472         }
473
474         private int getStateForBuffer() {
475                 int bufferState = 0;
476
477                 if ((metaState & META_CTRL_MASK) != 0)
478                         bufferState |= vt320.KEY_CONTROL;
479                 if ((metaState & META_SHIFT_MASK) != 0)
480                         bufferState |= vt320.KEY_SHIFT;
481                 if ((metaState & META_ALT_MASK) != 0)
482                         bufferState |= vt320.KEY_ALT;
483
484                 return bufferState;
485         }
486
487         public int getMetaState() {
488                 return metaState;
489         }
490
491         public void setClipboardManager(ClipboardManager clipboard) {
492                 this.clipboard = clipboard;
493         }
494
495         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
496                         String key) {
497                 if (PreferenceConstants.KEYMODE.equals(key)) {
498                         updateKeymode();
499                 }
500         }
501
502         private void updateKeymode() {
503                 keymode = prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT);
504         }
505
506         public void setCharset(String encoding) {
507                 this.encoding = encoding;
508         }
509 }