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
31typedef enum {
32 kInputNone,
33 kInputAvail,
34 kInputEof
35} InbufPollResult;
36
37static Stream read_stream = { .closed = true }; // Input before UI starts.
38static RBuffer *input_buffer = NULL;
39static bool input_eof = false;
40static int global_fd = -1;
41static bool blocking = false;
42
43#ifdef INCLUDE_GENERATED_DECLARATIONS
44# include "os/input.c.generated.h"
45#endif
46
47void input_init(void)
48{
49 input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN);
50}
51
52void 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.
58int input_global_fd(void)
59{
60 return global_fd;
61}
62
63void 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
74void 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
84static 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
91static 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.
105int 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
157bool 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.
163void 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.
181bool os_isatty(int fd)
182{
183 return uv_guess_handle(fd) == UV_TTY;
184}
185
186size_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
238static 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)
287static 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
350size_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.
375bool input_blocking(void)
376{
377 return blocking;
378}
379
380// This is a replacement for the old `WaitForChar` function in os_unix.c
381static 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
413void input_done(void)
414{
415 input_eof = true;
416}
417
418bool input_available(void)
419{
420 return rbuffer_size(input_buffer) != 0;
421}
422
423static 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
437static 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
460static 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
475static 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.
483static 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
492static bool pending_events(MultiQueue *events)
493{
494 return events && !multiqueue_empty(events);
495}
496