1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include <assert.h> |
5 | #include <string.h> |
6 | #include <stdbool.h> |
7 | |
8 | #include <uv.h> |
9 | |
10 | #include "nvim/api/private/defs.h" |
11 | #include "nvim/os/input.h" |
12 | #include "nvim/event/loop.h" |
13 | #include "nvim/event/rstream.h" |
14 | #include "nvim/ascii.h" |
15 | #include "nvim/vim.h" |
16 | #include "nvim/ui.h" |
17 | #include "nvim/memory.h" |
18 | #include "nvim/keymap.h" |
19 | #include "nvim/mbyte.h" |
20 | #include "nvim/fileio.h" |
21 | #include "nvim/ex_cmds2.h" |
22 | #include "nvim/getchar.h" |
23 | #include "nvim/main.h" |
24 | #include "nvim/misc1.h" |
25 | #include "nvim/state.h" |
26 | #include "nvim/msgpack_rpc/channel.h" |
27 | |
28 | #define READ_BUFFER_SIZE 0xfff |
29 | #define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4) |
30 | |
31 | typedef enum { |
32 | kInputNone, |
33 | kInputAvail, |
34 | kInputEof |
35 | } InbufPollResult; |
36 | |
37 | static Stream read_stream = { .closed = true }; // Input before UI starts. |
38 | static RBuffer *input_buffer = NULL; |
39 | static bool input_eof = false; |
40 | static int global_fd = -1; |
41 | static bool blocking = false; |
42 | |
43 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
44 | # include "os/input.c.generated.h" |
45 | #endif |
46 | |
47 | void input_init(void) |
48 | { |
49 | input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN); |
50 | } |
51 | |
52 | void input_global_fd_init(int fd) |
53 | { |
54 | global_fd = fd; |
55 | } |
56 | |
57 | /// Global TTY (or pipe for "-es") input stream, before UI starts. |
58 | int input_global_fd(void) |
59 | { |
60 | return global_fd; |
61 | } |
62 | |
63 | void input_start(int fd) |
64 | { |
65 | if (!read_stream.closed) { |
66 | return; |
67 | } |
68 | |
69 | input_global_fd_init(fd); |
70 | rstream_init_fd(&main_loop, &read_stream, fd, READ_BUFFER_SIZE); |
71 | rstream_start(&read_stream, input_read_cb, NULL); |
72 | } |
73 | |
74 | void input_stop(void) |
75 | { |
76 | if (read_stream.closed) { |
77 | return; |
78 | } |
79 | |
80 | rstream_stop(&read_stream); |
81 | stream_close(&read_stream, NULL, NULL); |
82 | } |
83 | |
84 | static void cursorhold_event(void **argv) |
85 | { |
86 | event_T event = State & INSERT ? EVENT_CURSORHOLDI : EVENT_CURSORHOLD; |
87 | apply_autocmds(event, NULL, NULL, false, curbuf); |
88 | did_cursorhold = true; |
89 | } |
90 | |
91 | static void create_cursorhold_event(bool events_enabled) |
92 | { |
93 | // If events are enabled and the queue has any items, this function should not |
94 | // have been called(inbuf_poll would return kInputAvail) |
95 | // TODO(tarruda): Cursorhold should be implemented as a timer set during the |
96 | // `state_check` callback for the states where it can be triggered. |
97 | assert(!events_enabled || multiqueue_empty(main_loop.events)); |
98 | multiqueue_put(main_loop.events, cursorhold_event, 0); |
99 | } |
100 | |
101 | /// Low level input function |
102 | /// |
103 | /// wait until either the input buffer is non-empty or , if `events` is not NULL |
104 | /// until `events` is non-empty. |
105 | int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, |
106 | MultiQueue *events) |
107 | { |
108 | if (maxlen && rbuffer_size(input_buffer)) { |
109 | return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); |
110 | } |
111 | |
112 | InbufPollResult result; |
113 | if (ms >= 0) { |
114 | if ((result = inbuf_poll(ms, events)) == kInputNone) { |
115 | return 0; |
116 | } |
117 | } else { |
118 | if ((result = inbuf_poll((int)p_ut, events)) == kInputNone) { |
119 | if (read_stream.closed && silent_mode) { |
120 | // Drained eventloop & initial input; exit silent/batch-mode (-es/-Es). |
121 | read_error_exit(); |
122 | } |
123 | |
124 | if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) { |
125 | create_cursorhold_event(events == main_loop.events); |
126 | } else { |
127 | before_blocking(); |
128 | result = inbuf_poll(-1, events); |
129 | } |
130 | } |
131 | } |
132 | |
133 | // If input was put directly in typeahead buffer bail out here. |
134 | if (typebuf_changed(tb_change_cnt)) { |
135 | return 0; |
136 | } |
137 | |
138 | if (maxlen && rbuffer_size(input_buffer)) { |
139 | // Safe to convert rbuffer_read to int, it will never overflow since we use |
140 | // relatively small buffers. |
141 | return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); |
142 | } |
143 | |
144 | // If there are events, return the keys directly |
145 | if (maxlen && pending_events(events)) { |
146 | return push_event_key(buf, maxlen); |
147 | } |
148 | |
149 | if (result == kInputEof) { |
150 | read_error_exit(); |
151 | } |
152 | |
153 | return 0; |
154 | } |
155 | |
156 | // Check if a character is available for reading |
157 | bool os_char_avail(void) |
158 | { |
159 | return inbuf_poll(0, NULL) == kInputAvail; |
160 | } |
161 | |
162 | // Check for CTRL-C typed by reading all available characters. |
163 | void os_breakcheck(void) |
164 | { |
165 | int save_us = updating_screen; |
166 | // We do not want screen_resize() to redraw here. |
167 | updating_screen++; |
168 | |
169 | if (!got_int) { |
170 | loop_poll_events(&main_loop, 0); |
171 | } |
172 | |
173 | updating_screen = save_us; |
174 | } |
175 | |
176 | |
177 | /// Test whether a file descriptor refers to a terminal. |
178 | /// |
179 | /// @param fd File descriptor. |
180 | /// @return `true` if file descriptor refers to a terminal. |
181 | bool os_isatty(int fd) |
182 | { |
183 | return uv_guess_handle(fd) == UV_TTY; |
184 | } |
185 | |
186 | size_t input_enqueue(String keys) |
187 | { |
188 | char *ptr = keys.data; |
189 | char *end = ptr + keys.size; |
190 | |
191 | while (rbuffer_space(input_buffer) >= 6 && ptr < end) { |
192 | uint8_t buf[6] = { 0 }; |
193 | unsigned int new_size |
194 | = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, |
195 | false); |
196 | |
197 | if (new_size) { |
198 | new_size = handle_mouse_event(&ptr, buf, new_size); |
199 | rbuffer_write(input_buffer, (char *)buf, new_size); |
200 | continue; |
201 | } |
202 | |
203 | if (*ptr == '<') { |
204 | char *old_ptr = ptr; |
205 | // Invalid or incomplete key sequence, skip until the next '>' or *end. |
206 | do { |
207 | ptr++; |
208 | } while (ptr < end && *ptr != '>'); |
209 | if (*ptr != '>') { |
210 | // Incomplete key sequence, return without consuming. |
211 | ptr = old_ptr; |
212 | break; |
213 | } |
214 | ptr++; |
215 | continue; |
216 | } |
217 | |
218 | // copy the character, escaping CSI and K_SPECIAL |
219 | if ((uint8_t)*ptr == CSI) { |
220 | rbuffer_write(input_buffer, (char *)&(uint8_t){K_SPECIAL}, 1); |
221 | rbuffer_write(input_buffer, (char *)&(uint8_t){KS_EXTRA}, 1); |
222 | rbuffer_write(input_buffer, (char *)&(uint8_t){KE_CSI}, 1); |
223 | } else if ((uint8_t)*ptr == K_SPECIAL) { |
224 | rbuffer_write(input_buffer, (char *)&(uint8_t){K_SPECIAL}, 1); |
225 | rbuffer_write(input_buffer, (char *)&(uint8_t){KS_SPECIAL}, 1); |
226 | rbuffer_write(input_buffer, (char *)&(uint8_t){KE_FILLER}, 1); |
227 | } else { |
228 | rbuffer_write(input_buffer, ptr, 1); |
229 | } |
230 | ptr++; |
231 | } |
232 | |
233 | size_t rv = (size_t)(ptr - keys.data); |
234 | process_interrupts(); |
235 | return rv; |
236 | } |
237 | |
238 | static uint8_t check_multiclick(int code, int grid, int row, int col) |
239 | { |
240 | static int orig_num_clicks = 0; |
241 | static int orig_mouse_code = 0; |
242 | static int orig_mouse_grid = 0; |
243 | static int orig_mouse_col = 0; |
244 | static int orig_mouse_row = 0; |
245 | static uint64_t orig_mouse_time = 0; // time of previous mouse click |
246 | |
247 | if (code == KE_LEFTRELEASE || code == KE_RIGHTRELEASE |
248 | || code == KE_MIDDLERELEASE) { |
249 | return 0; |
250 | } |
251 | uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns) |
252 | |
253 | // compute the time elapsed since the previous mouse click and |
254 | // convert p_mouse from ms to ns |
255 | uint64_t timediff = mouse_time - orig_mouse_time; |
256 | uint64_t mouset = (uint64_t)p_mouset * 1000000; |
257 | if (code == orig_mouse_code |
258 | && timediff < mouset |
259 | && orig_num_clicks != 4 |
260 | && orig_mouse_grid == grid |
261 | && orig_mouse_col == col |
262 | && orig_mouse_row == row) { |
263 | orig_num_clicks++; |
264 | } else { |
265 | orig_num_clicks = 1; |
266 | } |
267 | orig_mouse_code = code; |
268 | orig_mouse_grid = grid; |
269 | orig_mouse_col = col; |
270 | orig_mouse_row = row; |
271 | orig_mouse_time = mouse_time; |
272 | |
273 | uint8_t modifiers = 0; |
274 | if (orig_num_clicks == 2) { |
275 | modifiers |= MOD_MASK_2CLICK; |
276 | } else if (orig_num_clicks == 3) { |
277 | modifiers |= MOD_MASK_3CLICK; |
278 | } else if (orig_num_clicks == 4) { |
279 | modifiers |= MOD_MASK_4CLICK; |
280 | } |
281 | return modifiers; |
282 | } |
283 | |
284 | |
285 | // Mouse event handling code(Extract row/col if available and detect multiple |
286 | // clicks) |
287 | static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, |
288 | unsigned int bufsize) |
289 | { |
290 | int mouse_code = 0; |
291 | int type = 0; |
292 | |
293 | if (bufsize == 3) { |
294 | mouse_code = buf[2]; |
295 | type = buf[1]; |
296 | } else if (bufsize == 6) { |
297 | // prefixed with K_SPECIAL KS_MODIFIER mod |
298 | mouse_code = buf[5]; |
299 | type = buf[4]; |
300 | } |
301 | |
302 | if (type != KS_EXTRA |
303 | || !((mouse_code >= KE_LEFTMOUSE && mouse_code <= KE_RIGHTRELEASE) |
304 | || (mouse_code >= KE_MOUSEDOWN && mouse_code <= KE_MOUSERIGHT))) { |
305 | return bufsize; |
306 | } |
307 | |
308 | // a <[COL],[ROW]> sequence can follow and will set the mouse_row/mouse_col |
309 | // global variables. This is ugly but its how the rest of the code expects to |
310 | // find mouse coordinates, and it would be too expensive to refactor this |
311 | // now. |
312 | int col, row, advance; |
313 | if (sscanf(*ptr, "<%d,%d>%n" , &col, &row, &advance) != EOF && advance) { |
314 | if (col >= 0 && row >= 0) { |
315 | // Make sure the mouse position is valid. Some terminals may |
316 | // return weird values. |
317 | if (col >= Columns) { |
318 | col = Columns - 1; |
319 | } |
320 | if (row >= Rows) { |
321 | row = Rows - 1; |
322 | } |
323 | mouse_grid = 0; |
324 | mouse_row = row; |
325 | mouse_col = col; |
326 | } |
327 | *ptr += advance; |
328 | } |
329 | |
330 | uint8_t modifiers = check_multiclick(mouse_code, mouse_grid, |
331 | mouse_row, mouse_col); |
332 | |
333 | if (modifiers) { |
334 | if (buf[1] != KS_MODIFIER) { |
335 | // no modifiers in the buffer yet, shift the bytes 3 positions |
336 | memcpy(buf + 3, buf, 3); |
337 | // add the modifier sequence |
338 | buf[0] = K_SPECIAL; |
339 | buf[1] = KS_MODIFIER; |
340 | buf[2] = modifiers; |
341 | bufsize += 3; |
342 | } else { |
343 | buf[2] |= modifiers; |
344 | } |
345 | } |
346 | |
347 | return bufsize; |
348 | } |
349 | |
350 | size_t input_enqueue_mouse(int code, uint8_t modifier, |
351 | int grid, int row, int col) |
352 | { |
353 | modifier |= check_multiclick(code, grid, row, col); |
354 | uint8_t buf[7], *p = buf; |
355 | if (modifier) { |
356 | p[0] = K_SPECIAL; |
357 | p[1] = KS_MODIFIER; |
358 | p[2] = modifier; |
359 | p += 3; |
360 | } |
361 | p[0] = K_SPECIAL; |
362 | p[1] = KS_EXTRA; |
363 | p[2] = (uint8_t)code; |
364 | |
365 | mouse_grid = grid; |
366 | mouse_row = row; |
367 | mouse_col = col; |
368 | |
369 | size_t written = 3 + (size_t)(p-buf); |
370 | rbuffer_write(input_buffer, (char *)buf, written); |
371 | return written; |
372 | } |
373 | |
374 | /// @return true if the main loop is blocked and waiting for input. |
375 | bool input_blocking(void) |
376 | { |
377 | return blocking; |
378 | } |
379 | |
380 | // This is a replacement for the old `WaitForChar` function in os_unix.c |
381 | static InbufPollResult inbuf_poll(int ms, MultiQueue *events) |
382 | { |
383 | if (input_ready(events)) { |
384 | return kInputAvail; |
385 | } |
386 | |
387 | if (do_profiling == PROF_YES && ms) { |
388 | prof_inchar_enter(); |
389 | } |
390 | |
391 | if ((ms == - 1 || ms > 0) && events == NULL && !input_eof) { |
392 | // The pending input provoked a blocking wait. Do special events now. #6247 |
393 | blocking = true; |
394 | multiqueue_process_events(ch_before_blocking_events); |
395 | } |
396 | DLOG("blocking... events_enabled=%d events_pending=%d" , events != NULL, |
397 | events && !multiqueue_empty(events)); |
398 | LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, |
399 | input_ready(events) || input_eof); |
400 | blocking = false; |
401 | |
402 | if (do_profiling == PROF_YES && ms) { |
403 | prof_inchar_exit(); |
404 | } |
405 | |
406 | if (input_ready(events)) { |
407 | return kInputAvail; |
408 | } else { |
409 | return input_eof ? kInputEof : kInputNone; |
410 | } |
411 | } |
412 | |
413 | void input_done(void) |
414 | { |
415 | input_eof = true; |
416 | } |
417 | |
418 | bool input_available(void) |
419 | { |
420 | return rbuffer_size(input_buffer) != 0; |
421 | } |
422 | |
423 | static void input_read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, |
424 | bool at_eof) |
425 | { |
426 | if (at_eof) { |
427 | input_done(); |
428 | } |
429 | |
430 | assert(rbuffer_space(input_buffer) >= rbuffer_size(buf)); |
431 | RBUFFER_UNTIL_EMPTY(buf, ptr, len) { |
432 | (void)rbuffer_write(input_buffer, ptr, len); |
433 | rbuffer_consumed(buf, len); |
434 | } |
435 | } |
436 | |
437 | static void process_interrupts(void) |
438 | { |
439 | if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) { |
440 | return; |
441 | } |
442 | |
443 | size_t consume_count = 0; |
444 | RBUFFER_EACH_REVERSE(input_buffer, c, i) { |
445 | if ((uint8_t)c == Ctrl_C) { |
446 | got_int = true; |
447 | consume_count = i; |
448 | break; |
449 | } |
450 | } |
451 | |
452 | if (got_int && consume_count) { |
453 | // Remove all unprocessed input (typeahead) before the CTRL-C. |
454 | rbuffer_consumed(input_buffer, consume_count); |
455 | } |
456 | } |
457 | |
458 | // Helper function used to push bytes from the 'event' key sequence partially |
459 | // between calls to os_inchar when maxlen < 3 |
460 | static int push_event_key(uint8_t *buf, int maxlen) |
461 | { |
462 | static const uint8_t key[3] = { K_SPECIAL, KS_EXTRA, KE_EVENT }; |
463 | static int key_idx = 0; |
464 | int buf_idx = 0; |
465 | |
466 | do { |
467 | buf[buf_idx++] = key[key_idx++]; |
468 | key_idx %= 3; |
469 | } while (key_idx > 0 && buf_idx < maxlen); |
470 | |
471 | return buf_idx; |
472 | } |
473 | |
474 | // Check if there's pending input |
475 | static bool input_ready(MultiQueue *events) |
476 | { |
477 | return (typebuf_was_filled // API call filled typeahead |
478 | || rbuffer_size(input_buffer) // Input buffer filled |
479 | || pending_events(events)); // Events must be processed |
480 | } |
481 | |
482 | // Exit because of an input read error. |
483 | static void read_error_exit(void) |
484 | { |
485 | if (silent_mode) { // Normal way to exit for "nvim -es". |
486 | getout(0); |
487 | } |
488 | STRCPY(IObuff, _("Vim: Error reading input, exiting...\n" )); |
489 | preserve_exit(); |
490 | } |
491 | |
492 | static bool pending_events(MultiQueue *events) |
493 | { |
494 | return events && !multiqueue_empty(events); |
495 | } |
496 | |