1 // Copyright (c) 2015 Ryan Prichard
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to
5 // deal in the Software without restriction, including without limitation the
6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 // sell copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 #include "DefaultInputMap.h"
28 #include "../shared/StringBuilder.h"
29 #include "../shared/WinptyAssert.h"
33 #define DIM(x) (sizeof(x) / sizeof((x)[0]))
37 struct EscapeEncoding {
38 bool alt_prefix_allowed;
45 // Modifiers. A "modifier" is an integer from 2 to 8 that conveys the status
46 // of Shift(1), Alt(2), and Ctrl(4). The value is constructed by OR'ing the
47 // appropriate value for each active modifier, then adding 1.
50 // - kBare: expands to: ESC <prefix> <suffix>
51 // - kSemiMod: expands to: ESC <prefix> <numid> ; <mod> <suffix>
52 // - kBareMod: expands to: ESC <prefix> <mod> <suffix>
53 const int kBare = 0x01;
54 const int kSemiMod = 0x02;
55 const int kBareMod = 0x04;
57 // Numeric escape sequences suffixes:
58 // - with no flag: accept: ~
59 // - kSuffixCtrl: accept: ~ ^
60 // - kSuffixShift: accept: ~ $
61 // - kSuffixBoth: accept: ~ ^ $ @
62 const int kSuffixCtrl = 0x08;
63 const int kSuffixShift = 0x10;
64 const int kSuffixBoth = kSuffixCtrl | kSuffixShift;
66 static const EscapeEncoding escapeLetterEncodings[] = {
67 // Conventional arrow keys
68 // kBareMod: Ubuntu /etc/inputrc and IntelliJ/JediTerm use escapes like: ESC [ n ABCD
69 { true, '[', 'A', kBare | kBareMod | kSemiMod, { VK_UP, '\0', 0 } },
70 { true, '[', 'B', kBare | kBareMod | kSemiMod, { VK_DOWN, '\0', 0 } },
71 { true, '[', 'C', kBare | kBareMod | kSemiMod, { VK_RIGHT, '\0', 0 } },
72 { true, '[', 'D', kBare | kBareMod | kSemiMod, { VK_LEFT, '\0', 0 } },
74 // putty. putty uses this sequence for Ctrl-Arrow, Shift-Arrow, and
75 // Ctrl-Shift-Arrow, but I can only decode to one choice, so I'm just
76 // leaving the modifier off altogether.
77 { true, 'O', 'A', kBare, { VK_UP, '\0', 0 } },
78 { true, 'O', 'B', kBare, { VK_DOWN, '\0', 0 } },
79 { true, 'O', 'C', kBare, { VK_RIGHT, '\0', 0 } },
80 { true, 'O', 'D', kBare, { VK_LEFT, '\0', 0 } },
83 // Shift-Ctrl-Arrow can't be identified. It's the same as Shift-Arrow.
84 { true, '[', 'a', kBare, { VK_UP, '\0', SHIFT_PRESSED } },
85 { true, '[', 'b', kBare, { VK_DOWN, '\0', SHIFT_PRESSED } },
86 { true, '[', 'c', kBare, { VK_RIGHT, '\0', SHIFT_PRESSED } },
87 { true, '[', 'd', kBare, { VK_LEFT, '\0', SHIFT_PRESSED } },
88 { true, 'O', 'a', kBare, { VK_UP, '\0', LEFT_CTRL_PRESSED } },
89 { true, 'O', 'b', kBare, { VK_DOWN, '\0', LEFT_CTRL_PRESSED } },
90 { true, 'O', 'c', kBare, { VK_RIGHT, '\0', LEFT_CTRL_PRESSED } },
91 { true, 'O', 'd', kBare, { VK_LEFT, '\0', LEFT_CTRL_PRESSED } },
93 // Numpad 5 with NumLock off
94 // * xterm, mintty, and gnome-terminal use `ESC [ E`.
95 // * putty, TERM=cygwin, TERM=linux all use `ESC [ G` for 5
96 // * putty uses `ESC O G` for Ctrl-5 and Shift-5. Omit the modifier
97 // as with putty's arrow keys.
98 // * I never saw modifiers inserted into these escapes, but I think
99 // it should be completely OK with the CSI escapes.
100 { true, '[', 'E', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } },
101 { true, '[', 'G', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } },
102 { true, 'O', 'G', kBare, { VK_CLEAR, '\0', 0 } },
104 // Home/End, letter version
105 // * gnome-terminal uses `ESC O [HF]`. I never saw it modified.
106 // kBareMod: IntelliJ/JediTerm uses escapes like: ESC [ n HF
107 { true, '[', 'H', kBare | kBareMod | kSemiMod, { VK_HOME, '\0', 0 } },
108 { true, '[', 'F', kBare | kBareMod | kSemiMod, { VK_END, '\0', 0 } },
109 { true, 'O', 'H', kBare, { VK_HOME, '\0', 0 } },
110 { true, 'O', 'F', kBare, { VK_END, '\0', 0 } },
112 // F1-F4, letter version (xterm, VTE, konsole)
113 { true, '[', 'P', kSemiMod, { VK_F1, '\0', 0 } },
114 { true, '[', 'Q', kSemiMod, { VK_F2, '\0', 0 } },
115 { true, '[', 'R', kSemiMod, { VK_F3, '\0', 0 } },
116 { true, '[', 'S', kSemiMod, { VK_F4, '\0', 0 } },
118 // GNOME VTE and Konsole have special encodings for modified F1-F4:
119 // * [VTE] ESC O 1 ; n [PQRS]
120 // * [Konsole] ESC O n [PQRS]
121 { false, 'O', 'P', kBare | kBareMod | kSemiMod, { VK_F1, '\0', 0 } },
122 { false, 'O', 'Q', kBare | kBareMod | kSemiMod, { VK_F2, '\0', 0 } },
123 { false, 'O', 'R', kBare | kBareMod | kSemiMod, { VK_F3, '\0', 0 } },
124 { false, 'O', 'S', kBare | kBareMod | kSemiMod, { VK_F4, '\0', 0 } },
126 // Handle the "application numpad" escape sequences.
128 // Terminals output these codes under various circumstances:
129 // * rxvt-unicode: numpad, hold down SHIFT
130 // * rxvt: numpad, by default
131 // * xterm: numpad, after enabling app-mode using DECPAM (`ESC =`). xterm
132 // generates `ESC O <mod> <letter>` for modified numpad presses,
133 // necessitating kBareMod.
134 // * mintty: by combining Ctrl with various keys such as '1' or ','.
135 // Handling those keys is difficult, because mintty is generating the
136 // same sequence for Ctrl-1 and Ctrl-NumPadEnd -- should the virtualKey
137 // be '1' or VK_HOME?
139 { true, 'O', 'M', kBare | kBareMod, { VK_RETURN, '\r', 0 } },
140 { true, 'O', 'j', kBare | kBareMod, { VK_MULTIPLY, '*', 0 } },
141 { true, 'O', 'k', kBare | kBareMod, { VK_ADD, '+', 0 } },
142 { true, 'O', 'm', kBare | kBareMod, { VK_SUBTRACT, '-', 0 } },
143 { true, 'O', 'n', kBare | kBareMod, { VK_DELETE, '\0', 0 } },
144 { true, 'O', 'o', kBare | kBareMod, { VK_DIVIDE, '/', 0 } },
145 { true, 'O', 'p', kBare | kBareMod, { VK_INSERT, '\0', 0 } },
146 { true, 'O', 'q', kBare | kBareMod, { VK_END, '\0', 0 } },
147 { true, 'O', 'r', kBare | kBareMod, { VK_DOWN, '\0', 0 } },
148 { true, 'O', 's', kBare | kBareMod, { VK_NEXT, '\0', 0 } },
149 { true, 'O', 't', kBare | kBareMod, { VK_LEFT, '\0', 0 } },
150 { true, 'O', 'u', kBare | kBareMod, { VK_CLEAR, '\0', 0 } },
151 { true, 'O', 'v', kBare | kBareMod, { VK_RIGHT, '\0', 0 } },
152 { true, 'O', 'w', kBare | kBareMod, { VK_HOME, '\0', 0 } },
153 { true, 'O', 'x', kBare | kBareMod, { VK_UP, '\0', 0 } },
154 { true, 'O', 'y', kBare | kBareMod, { VK_PRIOR, '\0', 0 } },
156 { true, '[', 'M', kBare | kSemiMod, { VK_RETURN, '\r', 0 } },
157 { true, '[', 'j', kBare | kSemiMod, { VK_MULTIPLY, '*', 0 } },
158 { true, '[', 'k', kBare | kSemiMod, { VK_ADD, '+', 0 } },
159 { true, '[', 'm', kBare | kSemiMod, { VK_SUBTRACT, '-', 0 } },
160 { true, '[', 'n', kBare | kSemiMod, { VK_DELETE, '\0', 0 } },
161 { true, '[', 'o', kBare | kSemiMod, { VK_DIVIDE, '/', 0 } },
162 { true, '[', 'p', kBare | kSemiMod, { VK_INSERT, '\0', 0 } },
163 { true, '[', 'q', kBare | kSemiMod, { VK_END, '\0', 0 } },
164 { true, '[', 'r', kBare | kSemiMod, { VK_DOWN, '\0', 0 } },
165 { true, '[', 's', kBare | kSemiMod, { VK_NEXT, '\0', 0 } },
166 { true, '[', 't', kBare | kSemiMod, { VK_LEFT, '\0', 0 } },
167 { true, '[', 'u', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } },
168 { true, '[', 'v', kBare | kSemiMod, { VK_RIGHT, '\0', 0 } },
169 { true, '[', 'w', kBare | kSemiMod, { VK_HOME, '\0', 0 } },
170 { true, '[', 'x', kBare | kSemiMod, { VK_UP, '\0', 0 } },
171 { true, '[', 'y', kBare | kSemiMod, { VK_PRIOR, '\0', 0 } },
173 { false, '[', 'Z', kBare, { VK_TAB, '\t', SHIFT_PRESSED } },
176 static const EscapeEncoding escapeNumericEncodings[] = {
177 { true, '[', 1, kBare | kSemiMod | kSuffixBoth, { VK_HOME, '\0', 0 } },
178 { true, '[', 2, kBare | kSemiMod | kSuffixBoth, { VK_INSERT, '\0', 0 } },
179 { true, '[', 3, kBare | kSemiMod | kSuffixBoth, { VK_DELETE, '\0', 0 } },
180 { true, '[', 4, kBare | kSemiMod | kSuffixBoth, { VK_END, '\0', 0 } },
181 { true, '[', 5, kBare | kSemiMod | kSuffixBoth, { VK_PRIOR, '\0', 0 } },
182 { true, '[', 6, kBare | kSemiMod | kSuffixBoth, { VK_NEXT, '\0', 0 } },
183 { true, '[', 7, kBare | kSemiMod | kSuffixBoth, { VK_HOME, '\0', 0 } },
184 { true, '[', 8, kBare | kSemiMod | kSuffixBoth, { VK_END, '\0', 0 } },
185 { true, '[', 11, kBare | kSemiMod | kSuffixBoth, { VK_F1, '\0', 0 } },
186 { true, '[', 12, kBare | kSemiMod | kSuffixBoth, { VK_F2, '\0', 0 } },
187 { true, '[', 13, kBare | kSemiMod | kSuffixBoth, { VK_F3, '\0', 0 } },
188 { true, '[', 14, kBare | kSemiMod | kSuffixBoth, { VK_F4, '\0', 0 } },
189 { true, '[', 15, kBare | kSemiMod | kSuffixBoth, { VK_F5, '\0', 0 } },
190 { true, '[', 17, kBare | kSemiMod | kSuffixBoth, { VK_F6, '\0', 0 } },
191 { true, '[', 18, kBare | kSemiMod | kSuffixBoth, { VK_F7, '\0', 0 } },
192 { true, '[', 19, kBare | kSemiMod | kSuffixBoth, { VK_F8, '\0', 0 } },
193 { true, '[', 20, kBare | kSemiMod | kSuffixBoth, { VK_F9, '\0', 0 } },
194 { true, '[', 21, kBare | kSemiMod | kSuffixBoth, { VK_F10, '\0', 0 } },
195 { true, '[', 23, kBare | kSemiMod | kSuffixBoth, { VK_F11, '\0', 0 } },
196 { true, '[', 24, kBare | kSemiMod | kSuffixBoth, { VK_F12, '\0', 0 } },
197 { true, '[', 25, kBare | kSemiMod | kSuffixBoth, { VK_F3, '\0', SHIFT_PRESSED } },
198 { true, '[', 26, kBare | kSemiMod | kSuffixBoth, { VK_F4, '\0', SHIFT_PRESSED } },
199 { true, '[', 28, kBare | kSemiMod | kSuffixBoth, { VK_F5, '\0', SHIFT_PRESSED } },
200 { true, '[', 29, kBare | kSemiMod | kSuffixBoth, { VK_F6, '\0', SHIFT_PRESSED } },
201 { true, '[', 31, kBare | kSemiMod | kSuffixBoth, { VK_F7, '\0', SHIFT_PRESSED } },
202 { true, '[', 32, kBare | kSemiMod | kSuffixBoth, { VK_F8, '\0', SHIFT_PRESSED } },
203 { true, '[', 33, kBare | kSemiMod | kSuffixBoth, { VK_F9, '\0', SHIFT_PRESSED } },
204 { true, '[', 34, kBare | kSemiMod | kSuffixBoth, { VK_F10, '\0', SHIFT_PRESSED } },
207 const int kCsiShiftModifier = 1;
208 const int kCsiAltModifier = 2;
209 const int kCsiCtrlModifier = 4;
211 static inline bool useEnhancedForVirtualKey(uint16_t vk) {
229 static void addSimpleEntries(InputMap &inputMap) {
230 struct SimpleEncoding {
231 const char *encoding;
235 static const SimpleEncoding simpleEncodings[] = {
236 // Ctrl-<letter/digit> seems to be handled OK by the default code path.
238 { "\x7F", { VK_BACK, '\x08', 0, } },
239 { ESC "\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } },
240 { "\x03", { 'C', '\x03', LEFT_CTRL_PRESSED, } },
242 // Handle special F1-F5 for TERM=linux and TERM=cygwin.
243 { ESC "[[A", { VK_F1, '\0', 0 } },
244 { ESC "[[B", { VK_F2, '\0', 0 } },
245 { ESC "[[C", { VK_F3, '\0', 0 } },
246 { ESC "[[D", { VK_F4, '\0', 0 } },
247 { ESC "[[E", { VK_F5, '\0', 0 } },
249 { ESC ESC "[[A", { VK_F1, '\0', LEFT_ALT_PRESSED } },
250 { ESC ESC "[[B", { VK_F2, '\0', LEFT_ALT_PRESSED } },
251 { ESC ESC "[[C", { VK_F3, '\0', LEFT_ALT_PRESSED } },
252 { ESC ESC "[[D", { VK_F4, '\0', LEFT_ALT_PRESSED } },
253 { ESC ESC "[[E", { VK_F5, '\0', LEFT_ALT_PRESSED } },
256 for (size_t i = 0; i < DIM(simpleEncodings); ++i) {
257 auto k = simpleEncodings[i].key;
258 if (useEnhancedForVirtualKey(k.virtualKey)) {
259 k.keyState |= ENHANCED_KEY;
261 inputMap.set(simpleEncodings[i].encoding,
262 strlen(simpleEncodings[i].encoding),
267 struct ExpandContext {
269 const EscapeEncoding &e;
274 static inline void setEncoding(const ExpandContext &ctx, char *end,
275 uint16_t extraKeyState) {
276 InputMap::Key k = ctx.e.key;
277 k.keyState |= extraKeyState;
278 if (k.keyState & LEFT_CTRL_PRESSED) {
279 switch (k.virtualKey) {
284 k.unicodeChar = '\0';
287 k.unicodeChar = '\n';
291 if (useEnhancedForVirtualKey(k.virtualKey)) {
292 k.keyState |= ENHANCED_KEY;
294 ctx.inputMap.set(ctx.buffer, end - ctx.buffer, k);
297 static inline uint16_t keyStateForMod(int mod) {
299 if ((mod - 1) & kCsiShiftModifier) ret |= SHIFT_PRESSED;
300 if ((mod - 1) & kCsiAltModifier) ret |= LEFT_ALT_PRESSED;
301 if ((mod - 1) & kCsiCtrlModifier) ret |= LEFT_CTRL_PRESSED;
305 static void expandNumericEncodingSuffix(const ExpandContext &ctx, char *p,
306 uint16_t extraKeyState) {
307 ASSERT(p <= ctx.bufferEnd - 1);
311 setEncoding(ctx, q, extraKeyState);
313 if (ctx.e.modifiers & kSuffixShift) {
316 setEncoding(ctx, q, extraKeyState | SHIFT_PRESSED);
318 if (ctx.e.modifiers & kSuffixCtrl) {
321 setEncoding(ctx, q, extraKeyState | LEFT_CTRL_PRESSED);
323 if (ctx.e.modifiers & (kSuffixCtrl | kSuffixShift)) {
326 setEncoding(ctx, q, extraKeyState | SHIFT_PRESSED | LEFT_CTRL_PRESSED);
330 template <bool is_numeric>
331 static inline void expandEncodingAfterAltPrefix(
332 const ExpandContext &ctx, char *p, uint16_t extraKeyState) {
333 auto appendId = [&](char *&ptr) {
334 const auto idstr = decOfInt(ctx.e.id);
335 ASSERT(ptr <= ctx.bufferEnd - idstr.size());
336 std::copy(idstr.data(), idstr.data() + idstr.size(), ptr);
339 ASSERT(p <= ctx.bufferEnd - 2);
342 if (ctx.e.modifiers & kBare) {
346 expandNumericEncodingSuffix(ctx, q, extraKeyState);
348 ASSERT(q <= ctx.bufferEnd - 1);
350 setEncoding(ctx, q, extraKeyState);
353 if (ctx.e.modifiers & kBareMod) {
354 ASSERT(!is_numeric && "kBareMod is invalid with numeric sequences");
355 for (int mod = 2; mod <= 8; ++mod) {
357 ASSERT(q <= ctx.bufferEnd - 2);
360 setEncoding(ctx, q, extraKeyState | keyStateForMod(mod));
363 if (ctx.e.modifiers & kSemiMod) {
364 for (int mod = 2; mod <= 8; ++mod) {
368 ASSERT(q <= ctx.bufferEnd - 2);
371 expandNumericEncodingSuffix(
372 ctx, q, extraKeyState | keyStateForMod(mod));
374 ASSERT(q <= ctx.bufferEnd - 4);
379 setEncoding(ctx, q, extraKeyState | keyStateForMod(mod));
385 template <bool is_numeric>
386 static inline void expandEncoding(const ExpandContext &ctx) {
387 if (ctx.e.alt_prefix_allowed) {
388 // For better or for worse, this code expands all of:
389 // * ESC [ <key> -- <key>
390 // * ESC ESC [ <key> -- Alt-<key>
391 // * ESC [ 1 ; 3 <key> -- Alt-<key>
392 // * ESC ESC [ 1 ; 3 <key> -- Alt-<key> specified twice
393 // I suspect no terminal actually emits the last one (i.e. specifying
394 // the Alt modifier using both methods), but I have seen a terminal
395 // that emitted a prefix ESC for Alt and a non-Alt modifier.
396 char *p = ctx.buffer;
397 ASSERT(p <= ctx.bufferEnd - 1);
399 expandEncodingAfterAltPrefix<is_numeric>(ctx, p, LEFT_ALT_PRESSED);
401 expandEncodingAfterAltPrefix<is_numeric>(ctx, ctx.buffer, 0);
404 template <bool is_numeric, size_t N>
405 static void addEscapes(InputMap &inputMap, const EscapeEncoding (&encodings)[N]) {
407 for (size_t i = 0; i < DIM(encodings); ++i) {
408 ExpandContext ctx = {
409 inputMap, encodings[i],
410 buffer, buffer + sizeof(buffer)
412 expandEncoding<is_numeric>(ctx);
416 } // anonymous namespace
418 void addDefaultEntriesToInputMap(InputMap &inputMap) {
419 addEscapes<false>(inputMap, escapeLetterEncodings);
420 addEscapes<true>(inputMap, escapeNumericEncodings);
421 addSimpleEntries(inputMap);