1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2007 Nicholas Marriott <[email protected]> |
5 | * |
6 | * Permission to use, copy, modify, and distribute this software for any |
7 | * purpose with or without fee is hereby granted, provided that the above |
8 | * copyright notice and this permission notice appear in all copies. |
9 | * |
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | */ |
18 | |
19 | #include <sys/types.h> |
20 | |
21 | #include <stdlib.h> |
22 | #include <string.h> |
23 | |
24 | #include "tmux.h" |
25 | |
26 | /* |
27 | * This file is rather misleadingly named, it contains the code which takes a |
28 | * key code and translates it into something suitable to be sent to the |
29 | * application running in a pane (similar to input.c does in the other |
30 | * direction with output). |
31 | */ |
32 | |
33 | static void input_key_mouse(struct window_pane *, struct mouse_event *); |
34 | |
35 | struct input_key_ent { |
36 | key_code key; |
37 | const char *data; |
38 | |
39 | int flags; |
40 | #define INPUTKEY_KEYPAD 0x1 /* keypad key */ |
41 | #define INPUTKEY_CURSOR 0x2 /* cursor key */ |
42 | }; |
43 | |
44 | static const struct input_key_ent input_keys[] = { |
45 | /* Paste keys. */ |
46 | { KEYC_PASTE_START, "\033[200~" , 0 }, |
47 | { KEYC_PASTE_END, "\033[201~" , 0 }, |
48 | |
49 | /* Function keys. */ |
50 | { KEYC_F1, "\033OP" , 0 }, |
51 | { KEYC_F2, "\033OQ" , 0 }, |
52 | { KEYC_F3, "\033OR" , 0 }, |
53 | { KEYC_F4, "\033OS" , 0 }, |
54 | { KEYC_F5, "\033[15~" , 0 }, |
55 | { KEYC_F6, "\033[17~" , 0 }, |
56 | { KEYC_F7, "\033[18~" , 0 }, |
57 | { KEYC_F8, "\033[19~" , 0 }, |
58 | { KEYC_F9, "\033[20~" , 0 }, |
59 | { KEYC_F10, "\033[21~" , 0 }, |
60 | { KEYC_F11, "\033[23~" , 0 }, |
61 | { KEYC_F12, "\033[24~" , 0 }, |
62 | { KEYC_F1|KEYC_SHIFT, "\033[25~" , 0 }, |
63 | { KEYC_F2|KEYC_SHIFT, "\033[26~" , 0 }, |
64 | { KEYC_F3|KEYC_SHIFT, "\033[28~" , 0 }, |
65 | { KEYC_F4|KEYC_SHIFT, "\033[29~" , 0 }, |
66 | { KEYC_F5|KEYC_SHIFT, "\033[31~" , 0 }, |
67 | { KEYC_F6|KEYC_SHIFT, "\033[32~" , 0 }, |
68 | { KEYC_F7|KEYC_SHIFT, "\033[33~" , 0 }, |
69 | { KEYC_F8|KEYC_SHIFT, "\033[34~" , 0 }, |
70 | { KEYC_IC, "\033[2~" , 0 }, |
71 | { KEYC_DC, "\033[3~" , 0 }, |
72 | { KEYC_HOME, "\033[1~" , 0 }, |
73 | { KEYC_END, "\033[4~" , 0 }, |
74 | { KEYC_NPAGE, "\033[6~" , 0 }, |
75 | { KEYC_PPAGE, "\033[5~" , 0 }, |
76 | { KEYC_BTAB, "\033[Z" , 0 }, |
77 | |
78 | /* |
79 | * Arrow keys. Cursor versions must come first. The codes are toggled |
80 | * between CSI and SS3 versions when ctrl is pressed. |
81 | */ |
82 | { KEYC_UP|KEYC_CTRL, "\033[A" , INPUTKEY_CURSOR }, |
83 | { KEYC_DOWN|KEYC_CTRL, "\033[B" , INPUTKEY_CURSOR }, |
84 | { KEYC_RIGHT|KEYC_CTRL, "\033[C" , INPUTKEY_CURSOR }, |
85 | { KEYC_LEFT|KEYC_CTRL, "\033[D" , INPUTKEY_CURSOR }, |
86 | |
87 | { KEYC_UP, "\033OA" , INPUTKEY_CURSOR }, |
88 | { KEYC_DOWN, "\033OB" , INPUTKEY_CURSOR }, |
89 | { KEYC_RIGHT, "\033OC" , INPUTKEY_CURSOR }, |
90 | { KEYC_LEFT, "\033OD" , INPUTKEY_CURSOR }, |
91 | |
92 | { KEYC_UP|KEYC_CTRL, "\033OA" , 0 }, |
93 | { KEYC_DOWN|KEYC_CTRL, "\033OB" , 0 }, |
94 | { KEYC_RIGHT|KEYC_CTRL, "\033OC" , 0 }, |
95 | { KEYC_LEFT|KEYC_CTRL, "\033OD" , 0 }, |
96 | |
97 | { KEYC_UP, "\033[A" , 0 }, |
98 | { KEYC_DOWN, "\033[B" , 0 }, |
99 | { KEYC_RIGHT, "\033[C" , 0 }, |
100 | { KEYC_LEFT, "\033[D" , 0 }, |
101 | |
102 | /* Keypad keys. Keypad versions must come first. */ |
103 | { KEYC_KP_SLASH, "\033Oo" , INPUTKEY_KEYPAD }, |
104 | { KEYC_KP_STAR, "\033Oj" , INPUTKEY_KEYPAD }, |
105 | { KEYC_KP_MINUS, "\033Om" , INPUTKEY_KEYPAD }, |
106 | { KEYC_KP_SEVEN, "\033Ow" , INPUTKEY_KEYPAD }, |
107 | { KEYC_KP_EIGHT, "\033Ox" , INPUTKEY_KEYPAD }, |
108 | { KEYC_KP_NINE, "\033Oy" , INPUTKEY_KEYPAD }, |
109 | { KEYC_KP_PLUS, "\033Ok" , INPUTKEY_KEYPAD }, |
110 | { KEYC_KP_FOUR, "\033Ot" , INPUTKEY_KEYPAD }, |
111 | { KEYC_KP_FIVE, "\033Ou" , INPUTKEY_KEYPAD }, |
112 | { KEYC_KP_SIX, "\033Ov" , INPUTKEY_KEYPAD }, |
113 | { KEYC_KP_ONE, "\033Oq" , INPUTKEY_KEYPAD }, |
114 | { KEYC_KP_TWO, "\033Or" , INPUTKEY_KEYPAD }, |
115 | { KEYC_KP_THREE, "\033Os" , INPUTKEY_KEYPAD }, |
116 | { KEYC_KP_ENTER, "\033OM" , INPUTKEY_KEYPAD }, |
117 | { KEYC_KP_ZERO, "\033Op" , INPUTKEY_KEYPAD }, |
118 | { KEYC_KP_PERIOD, "\033On" , INPUTKEY_KEYPAD }, |
119 | |
120 | { KEYC_KP_SLASH, "/" , 0 }, |
121 | { KEYC_KP_STAR, "*" , 0 }, |
122 | { KEYC_KP_MINUS, "-" , 0 }, |
123 | { KEYC_KP_SEVEN, "7" , 0 }, |
124 | { KEYC_KP_EIGHT, "8" , 0 }, |
125 | { KEYC_KP_NINE, "9" , 0 }, |
126 | { KEYC_KP_PLUS, "+" , 0 }, |
127 | { KEYC_KP_FOUR, "4" , 0 }, |
128 | { KEYC_KP_FIVE, "5" , 0 }, |
129 | { KEYC_KP_SIX, "6" , 0 }, |
130 | { KEYC_KP_ONE, "1" , 0 }, |
131 | { KEYC_KP_TWO, "2" , 0 }, |
132 | { KEYC_KP_THREE, "3" , 0 }, |
133 | { KEYC_KP_ENTER, "\n" , 0 }, |
134 | { KEYC_KP_ZERO, "0" , 0 }, |
135 | { KEYC_KP_PERIOD, "." , 0 }, |
136 | }; |
137 | |
138 | /* Split a character into two UTF-8 bytes. */ |
139 | static size_t |
140 | input_split2(u_int c, u_char *dst) |
141 | { |
142 | if (c > 0x7f) { |
143 | dst[0] = (c >> 6) | 0xc0; |
144 | dst[1] = (c & 0x3f) | 0x80; |
145 | return (2); |
146 | } |
147 | dst[0] = c; |
148 | return (1); |
149 | } |
150 | |
151 | /* Translate a key code into an output key sequence. */ |
152 | int |
153 | input_key(struct window_pane *wp, key_code key, struct mouse_event *m) |
154 | { |
155 | const struct input_key_ent *ike; |
156 | u_int i; |
157 | size_t dlen; |
158 | char *out; |
159 | key_code justkey, newkey; |
160 | struct utf8_data ud; |
161 | |
162 | log_debug("writing key 0x%llx (%s) to %%%u" , key, |
163 | key_string_lookup_key(key), wp->id); |
164 | |
165 | /* If this is a mouse key, pass off to mouse function. */ |
166 | if (KEYC_IS_MOUSE(key)) { |
167 | if (m != NULL && m->wp != -1 && (u_int)m->wp == wp->id) |
168 | input_key_mouse(wp, m); |
169 | return (0); |
170 | } |
171 | |
172 | /* Literal keys go as themselves (can't be more than eight bits). */ |
173 | if (key & KEYC_LITERAL) { |
174 | ud.data[0] = (u_char)key; |
175 | bufferevent_write(wp->event, &ud.data[0], 1); |
176 | return (0); |
177 | } |
178 | |
179 | /* Is this backspace? */ |
180 | if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) { |
181 | newkey = options_get_number(global_options, "backspace" ); |
182 | if (newkey >= 0x7f) |
183 | newkey = '\177'; |
184 | key = newkey|(key & KEYC_MASK_MOD); |
185 | } |
186 | |
187 | /* |
188 | * If this is a normal 7-bit key, just send it, with a leading escape |
189 | * if necessary. If it is a UTF-8 key, split it and send it. |
190 | */ |
191 | justkey = (key & ~(KEYC_XTERM|KEYC_ESCAPE)); |
192 | if (justkey <= 0x7f) { |
193 | if (key & KEYC_ESCAPE) |
194 | bufferevent_write(wp->event, "\033" , 1); |
195 | ud.data[0] = justkey; |
196 | bufferevent_write(wp->event, &ud.data[0], 1); |
197 | return (0); |
198 | } |
199 | if (justkey > 0x7f && justkey < KEYC_BASE) { |
200 | if (utf8_split(justkey, &ud) != UTF8_DONE) |
201 | return (-1); |
202 | if (key & KEYC_ESCAPE) |
203 | bufferevent_write(wp->event, "\033" , 1); |
204 | bufferevent_write(wp->event, ud.data, ud.size); |
205 | return (0); |
206 | } |
207 | |
208 | /* |
209 | * Then try to look this up as an xterm key, if the flag to output them |
210 | * is set. |
211 | */ |
212 | if (options_get_number(wp->window->options, "xterm-keys" )) { |
213 | if ((out = xterm_keys_lookup(key)) != NULL) { |
214 | bufferevent_write(wp->event, out, strlen(out)); |
215 | free(out); |
216 | return (0); |
217 | } |
218 | } |
219 | key &= ~KEYC_XTERM; |
220 | |
221 | /* Otherwise look the key up in the table. */ |
222 | for (i = 0; i < nitems(input_keys); i++) { |
223 | ike = &input_keys[i]; |
224 | |
225 | if ((ike->flags & INPUTKEY_KEYPAD) && |
226 | !(wp->screen->mode & MODE_KKEYPAD)) |
227 | continue; |
228 | if ((ike->flags & INPUTKEY_CURSOR) && |
229 | !(wp->screen->mode & MODE_KCURSOR)) |
230 | continue; |
231 | |
232 | if ((key & KEYC_ESCAPE) && (ike->key | KEYC_ESCAPE) == key) |
233 | break; |
234 | if (ike->key == key) |
235 | break; |
236 | } |
237 | if (i == nitems(input_keys)) { |
238 | log_debug("key 0x%llx missing" , key); |
239 | return (-1); |
240 | } |
241 | dlen = strlen(ike->data); |
242 | log_debug("found key 0x%llx: \"%s\"" , key, ike->data); |
243 | |
244 | /* Prefix a \033 for escape. */ |
245 | if (key & KEYC_ESCAPE) |
246 | bufferevent_write(wp->event, "\033" , 1); |
247 | bufferevent_write(wp->event, ike->data, dlen); |
248 | return (0); |
249 | } |
250 | |
251 | /* Translate mouse and output. */ |
252 | static void |
253 | input_key_mouse(struct window_pane *wp, struct mouse_event *m) |
254 | { |
255 | struct screen *s = wp->screen; |
256 | int mode = s->mode; |
257 | char buf[40]; |
258 | size_t len; |
259 | u_int x, y; |
260 | |
261 | if ((mode & ALL_MOUSE_MODES) == 0) |
262 | return; |
263 | if (cmd_mouse_at(wp, m, &x, &y, 0) != 0) |
264 | return; |
265 | if (!window_pane_visible(wp)) |
266 | return; |
267 | |
268 | /* If this pane is not in button or all mode, discard motion events. */ |
269 | if (MOUSE_DRAG(m->b) && |
270 | (mode & (MODE_MOUSE_BUTTON|MODE_MOUSE_ALL)) == 0) |
271 | return; |
272 | |
273 | /* |
274 | * If this event is a release event and not in all mode, discard it. |
275 | * In SGR mode we can tell absolutely because a release is normally |
276 | * shown by the last character. Without SGR, we check if the last |
277 | * buttons was also a release. |
278 | */ |
279 | if (m->sgr_type != ' ') { |
280 | if (MOUSE_DRAG(m->sgr_b) && |
281 | MOUSE_BUTTONS(m->sgr_b) == 3 && |
282 | (~mode & MODE_MOUSE_ALL)) |
283 | return; |
284 | } else { |
285 | if (MOUSE_DRAG(m->b) && |
286 | MOUSE_BUTTONS(m->b) == 3 && |
287 | MOUSE_BUTTONS(m->lb) == 3 && |
288 | (~mode & MODE_MOUSE_ALL)) |
289 | return; |
290 | } |
291 | |
292 | /* |
293 | * Use the SGR (1006) extension only if the application requested it |
294 | * and the underlying terminal also sent the event in this format (this |
295 | * is because an old style mouse release event cannot be converted into |
296 | * the new SGR format, since the released button is unknown). Otherwise |
297 | * pretend that tmux doesn't speak this extension, and fall back to the |
298 | * UTF-8 (1005) extension if the application requested, or to the |
299 | * legacy format. |
300 | */ |
301 | if (m->sgr_type != ' ' && (s->mode & MODE_MOUSE_SGR)) { |
302 | len = xsnprintf(buf, sizeof buf, "\033[<%u;%u;%u%c" , |
303 | m->sgr_b, x + 1, y + 1, m->sgr_type); |
304 | } else if (s->mode & MODE_MOUSE_UTF8) { |
305 | if (m->b > 0x7ff - 32 || x > 0x7ff - 33 || y > 0x7ff - 33) |
306 | return; |
307 | len = xsnprintf(buf, sizeof buf, "\033[M" ); |
308 | len += input_split2(m->b + 32, &buf[len]); |
309 | len += input_split2(x + 33, &buf[len]); |
310 | len += input_split2(y + 33, &buf[len]); |
311 | } else { |
312 | if (m->b > 223) |
313 | return; |
314 | len = xsnprintf(buf, sizeof buf, "\033[M" ); |
315 | buf[len++] = m->b + 32; |
316 | buf[len++] = x + 33; |
317 | buf[len++] = y + 33; |
318 | } |
319 | log_debug("writing mouse %.*s to %%%u" , (int)len, buf, wp->id); |
320 | bufferevent_write(wp->event, buf, len); |
321 | } |
322 | |