Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | package com.googlecode.lanterna.input; |
2 | ||
3 | import static com.googlecode.lanterna.input.KeyDecodingProfile.ESC_CODE; | |
4 | ||
5 | import java.util.HashMap; | |
6 | import java.util.List; | |
7 | import java.util.Map; | |
8 | ||
9 | /** | |
10 | * This implementation of CharacterPattern matches two similar patterns | |
11 | * of Escape sequences, that many terminals produce for special keys.<p> | |
12 | * | |
13 | * These sequences all start with Escape, followed by either an open bracket | |
14 | * or a capital letter O (these two are treated as equivalent).<p> | |
15 | * | |
16 | * Then follows a list of zero or up to two decimals separated by a | |
17 | * semicolon, and a non-digit last character.<p> | |
18 | * | |
19 | * If the last character is a tilde (~) then the first number defines | |
20 | * the key (through stdMap), otherwise the last character itself defines | |
21 | * the key (through finMap).<p> | |
22 | * | |
23 | * The second number, if provided by the terminal, specifies the modifier | |
24 | * state (shift,alt,ctrl). The value is 1 + sum(modifiers), where shift is 1, | |
25 | * alt is 2 and ctrl is 4.<p> | |
26 | * | |
27 | * The two maps stdMap and finMap can be customized in subclasses to add, | |
28 | * remove or replace keys - to support non-standard Terminals.<p> | |
29 | * | |
30 | * Examples: (on a gnome terminal)<br> | |
31 | * ArrowUp is "Esc [ A"; Alt-ArrowUp is "Esc [ 1 ; 3 A"<br> | |
32 | * both are handled by finMap mapping 'A' to ArrowUp <br><br> | |
33 | * F6 is "Esc [ 1 7 ~"; Ctrl-Shift-F6 is "Esc [ 1 7 ; 6 R"<br> | |
34 | * both are handled by stdMap mapping 17 to F6 <br><br> | |
35 | * | |
36 | * @author Andreas | |
37 | * | |
38 | */ | |
39 | public class EscapeSequenceCharacterPattern implements CharacterPattern { | |
40 | // state machine used to match key sequence: | |
41 | private enum State { | |
42 | START, INTRO, NUM1, NUM2, DONE | |
43 | } | |
44 | // bit-values for modifier keys: only used internally | |
45 | public static final int SHIFT = 1, ALT = 2, CTRL = 4; | |
46 | ||
47 | /** | |
48 | * Map of recognized "standard pattern" sequences:<br> | |
49 | * e.g.: 24 -> F12 : "Esc [ <b>24</b> ~" | |
50 | */ | |
51 | protected final Map<Integer, KeyType> stdMap = new HashMap<Integer, KeyType>(); | |
52 | /** | |
53 | * Map of recognized "finish pattern" sequences:<br> | |
54 | * e.g.: 'A' -> ArrowUp : "Esc [ <b>A</b>" | |
55 | */ | |
56 | protected final Map<Character, KeyType> finMap = new HashMap<Character, KeyType>(); | |
57 | /** | |
58 | * A flag to control, whether an Esc-prefix for an Esc-sequence is to be treated | |
59 | * as Alt-pressed. Some Terminals (e.g. putty) report the Alt-modifier like that.<p> | |
60 | * If the application is e.g. more interested in seeing separate Escape and plain | |
61 | * Arrow keys, then it should replace this class by a subclass that sets this flag | |
62 | * to false. (It might then also want to remove the CtrlAltAndCharacterPattern.) | |
63 | */ | |
64 | protected boolean useEscEsc = true; | |
65 | ||
66 | /** | |
67 | * Create an instance with a standard set of mappings. | |
68 | */ | |
69 | public EscapeSequenceCharacterPattern() { | |
70 | finMap.put('A', KeyType.ArrowUp); | |
71 | finMap.put('B', KeyType.ArrowDown); | |
72 | finMap.put('C', KeyType.ArrowRight); | |
73 | finMap.put('D', KeyType.ArrowLeft); | |
74 | finMap.put('E', KeyType.Unknown); // gnome-terminal center key on numpad | |
75 | finMap.put('G', KeyType.Unknown); // putty center key on numpad | |
76 | finMap.put('H', KeyType.Home); | |
77 | finMap.put('F', KeyType.End); | |
78 | finMap.put('P', KeyType.F1); | |
79 | finMap.put('Q', KeyType.F2); | |
80 | finMap.put('R', KeyType.F3); | |
81 | finMap.put('S', KeyType.F4); | |
82 | finMap.put('Z', KeyType.ReverseTab); | |
83 | ||
84 | stdMap.put(1, KeyType.Home); | |
85 | stdMap.put(2, KeyType.Insert); | |
86 | stdMap.put(3, KeyType.Delete); | |
87 | stdMap.put(4, KeyType.End); | |
88 | stdMap.put(5, KeyType.PageUp); | |
89 | stdMap.put(6, KeyType.PageDown); | |
90 | stdMap.put(11, KeyType.F1); | |
91 | stdMap.put(12, KeyType.F2); | |
92 | stdMap.put(13, KeyType.F3); | |
93 | stdMap.put(14, KeyType.F4); | |
94 | stdMap.put(15, KeyType.F5); | |
95 | stdMap.put(16, KeyType.F5); | |
96 | stdMap.put(17, KeyType.F6); | |
97 | stdMap.put(18, KeyType.F7); | |
98 | stdMap.put(19, KeyType.F8); | |
99 | stdMap.put(20, KeyType.F9); | |
100 | stdMap.put(21, KeyType.F10); | |
101 | stdMap.put(23, KeyType.F11); | |
102 | stdMap.put(24, KeyType.F12); | |
103 | stdMap.put(25, KeyType.F13); | |
104 | stdMap.put(26, KeyType.F14); | |
105 | stdMap.put(28, KeyType.F15); | |
106 | stdMap.put(29, KeyType.F16); | |
107 | stdMap.put(31, KeyType.F17); | |
108 | stdMap.put(32, KeyType.F18); | |
109 | stdMap.put(33, KeyType.F19); | |
110 | } | |
111 | ||
112 | /** | |
113 | * combines a KeyType and modifiers into a KeyStroke. | |
114 | * Subclasses can override this for customization purposes. | |
115 | * | |
116 | * @param key the KeyType as determined by parsing the sequence. | |
117 | * It will be null, if the pattern looked like a key sequence but wasn't | |
118 | * identified. | |
119 | * @param mods the bitmask of the modifer keys pressed along with the key. | |
120 | * @return either null (to report mis-match), or a valid KeyStroke. | |
121 | */ | |
122 | protected KeyStroke getKeyStroke(KeyType key, int mods) { | |
123 | boolean bShift = false, bCtrl = false, bAlt = false; | |
124 | if (key == null) { return null; } // alternative: key = KeyType.Unknown; | |
125 | if (mods >= 0) { // only use when non-negative! | |
126 | bShift = (mods & SHIFT) != 0; | |
127 | bAlt = (mods & ALT) != 0; | |
128 | bCtrl = (mods & CTRL) != 0; | |
129 | } | |
130 | return new KeyStroke( key , bCtrl, bAlt, bShift); | |
131 | } | |
132 | ||
133 | /** | |
134 | * combines the raw parts of the sequence into a KeyStroke. | |
135 | * This method does not check the first char, but overrides may do so. | |
136 | * | |
137 | * @param first the char following after Esc in the sequence (either [ or O) | |
138 | * @param num1 the first decimal, or 0 if not in the sequence | |
139 | * @param num2 the second decimal, or 0 if not in the sequence | |
140 | * @param last the terminating char. | |
141 | * @param bEsc whether an extra Escape-prefix was found. | |
142 | * @return either null (to report mis-match), or a valid KeyStroke. | |
143 | */ | |
144 | protected KeyStroke getKeyStrokeRaw(char first,int num1,int num2,char last,boolean bEsc) { | |
145 | KeyType kt = null; boolean bPuttyCtrl = false; | |
146 | if (last == '~' && stdMap.containsKey(num1)) { | |
147 | kt = stdMap.get(num1); | |
148 | } else if (finMap.containsKey(last)) { | |
149 | kt = finMap.get(last); | |
150 | // Putty sends ^[OA for ctrl arrow-up, ^[[A for plain arrow-up: | |
151 | // but only for A-D -- other ^[O... sequences are just plain keys | |
152 | if (first == 'O' && last >= 'A' && last <= 'D') { bPuttyCtrl = true; } | |
153 | // if we ever stumble into "keypad-mode", then it will end up inverted. | |
154 | } else { | |
155 | kt = null; // unknown key. | |
156 | } | |
157 | int mods = num2 - 1; | |
158 | if (bEsc) { | |
159 | if (mods >= 0) { mods |= ALT; } | |
160 | else { mods = ALT; } | |
161 | } | |
162 | if (bPuttyCtrl) { | |
163 | if (mods >= 0) { mods |= CTRL; } | |
164 | else { mods = CTRL; } | |
165 | } | |
166 | return getKeyStroke( kt, mods ); | |
167 | } | |
168 | ||
169 | @Override | |
170 | public Matching match(List<Character> cur) { | |
171 | State state = State.START; | |
172 | int num1 = 0, num2 = 0; | |
173 | char first = '\0', last = '\0'; | |
174 | boolean bEsc = false; | |
175 | ||
176 | for (char ch : cur) { | |
177 | switch (state) { | |
178 | case START: | |
179 | if (ch != ESC_CODE) { | |
180 | return null; // nope | |
181 | } | |
182 | state = State.INTRO; | |
183 | continue; | |
184 | case INTRO: | |
185 | // Recognize a second Escape to mean "Alt is pressed". | |
186 | // (at least putty sends it that way) | |
187 | if (useEscEsc && ch == ESC_CODE && ! bEsc) { | |
188 | bEsc = true; continue; | |
189 | } | |
190 | ||
191 | // Key sequences supported by this class must | |
192 | // start either with Esc-[ or Esc-O | |
193 | if (ch != '[' && ch != 'O') { | |
194 | return null; // nope | |
195 | } | |
196 | first = ch; state = State.NUM1; | |
197 | continue; | |
198 | case NUM1: | |
199 | if (ch == ';') { | |
200 | state = State.NUM2; | |
201 | } else if (Character.isDigit(ch)) { | |
202 | num1 = num1 * 10 + Character.digit(ch, 10); | |
203 | } else { | |
204 | last = ch; state = State.DONE; | |
205 | } | |
206 | continue; | |
207 | case NUM2: | |
208 | if (Character.isDigit(ch)) { | |
209 | num2 = num2 * 10 + Character.digit(ch, 10); | |
210 | } else { | |
211 | last = ch; state = State.DONE; | |
212 | } | |
213 | continue; | |
214 | case DONE: // once done, extra characters spoil it | |
215 | return null; // nope | |
216 | } | |
217 | } | |
218 | if (state == State.DONE) { | |
219 | KeyStroke ks = getKeyStrokeRaw(first,num1,num2,last,bEsc); | |
220 | return ks != null ? new Matching( ks ) : null; // depends | |
221 | } else { | |
222 | return Matching.NOT_YET; // maybe later | |
223 | } | |
224 | } | |
225 | } |