/src/mpv/osdep/terminal-unix.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Based on GyS-TermIO v2.0 (for GySmail v3) (copyright (C) 1999 A'rpi/ESP-team) |
3 | | * |
4 | | * This file is part of mpv. |
5 | | * |
6 | | * mpv is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * mpv is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public |
17 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | #include <stdlib.h> |
21 | | #include <stdint.h> |
22 | | #include <string.h> |
23 | | #include <signal.h> |
24 | | #include <errno.h> |
25 | | #include <sys/ioctl.h> |
26 | | #include <assert.h> |
27 | | |
28 | | #include <termios.h> |
29 | | #include <unistd.h> |
30 | | |
31 | | #include "osdep/io.h" |
32 | | #include "osdep/threads.h" |
33 | | #include "osdep/poll_wrapper.h" |
34 | | |
35 | | #include "common/common.h" |
36 | | #include "misc/bstr.h" |
37 | | #include "input/input.h" |
38 | | #include "input/keycodes.h" |
39 | | #include "misc/ctype.h" |
40 | | #include "terminal.h" |
41 | | |
42 | | // Timeout in ms after which the (normally ambiguous) ESC key is detected. |
43 | 0 | #define ESC_TIMEOUT 100 |
44 | | |
45 | | // Timeout in ms after which the poll for input is aborted. The FG/BG state is |
46 | | // tested before every wait, and a positive value allows reactivating input |
47 | | // processing when mpv is brought to the foreground while it was running in the |
48 | | // background. In such a situation, an infinite timeout (-1) will keep mpv |
49 | | // waiting for input without realizing the terminal state changed - and thus |
50 | | // buffer all keypresses until ENTER is pressed. |
51 | 15.4k | #define INPUT_TIMEOUT 1000 |
52 | | |
53 | | static struct termios tio_orig; |
54 | | |
55 | | static int tty_in = -1, tty_out = -1; |
56 | | |
57 | | enum entry_type { |
58 | | ENTRY_TYPE_KEY = 0, |
59 | | ENTRY_TYPE_MOUSE_BUTTON, |
60 | | ENTRY_TYPE_MOUSE_MOVE, |
61 | | }; |
62 | | |
63 | | struct key_entry { |
64 | | const char *seq; |
65 | | int mpkey; |
66 | | // If this is not NULL, then if seq is matched as unique prefix, the |
67 | | // existing sequence is replaced by the following string. Matching |
68 | | // continues normally, and mpkey is or-ed into the final result. |
69 | | const char *replace; |
70 | | // Extend the match length by a certain length, so the contents |
71 | | // after the match can be processed with custom logic. |
72 | | int skip; |
73 | | enum entry_type type; |
74 | | }; |
75 | | |
76 | | static const struct key_entry keys[] = { |
77 | | {"\010", MP_KEY_BS}, |
78 | | {"\011", MP_KEY_TAB}, |
79 | | {"\012", MP_KEY_ENTER}, |
80 | | {"\177", MP_KEY_BS}, |
81 | | |
82 | | {"\033[1~", MP_KEY_HOME}, |
83 | | {"\033[2~", MP_KEY_INS}, |
84 | | {"\033[3~", MP_KEY_DEL}, |
85 | | {"\033[4~", MP_KEY_END}, |
86 | | {"\033[5~", MP_KEY_PGUP}, |
87 | | {"\033[6~", MP_KEY_PGDWN}, |
88 | | {"\033[7~", MP_KEY_HOME}, |
89 | | {"\033[8~", MP_KEY_END}, |
90 | | |
91 | | {"\033[11~", MP_KEY_F+1}, |
92 | | {"\033[12~", MP_KEY_F+2}, |
93 | | {"\033[13~", MP_KEY_F+3}, |
94 | | {"\033[14~", MP_KEY_F+4}, |
95 | | {"\033[15~", MP_KEY_F+5}, |
96 | | {"\033[17~", MP_KEY_F+6}, |
97 | | {"\033[18~", MP_KEY_F+7}, |
98 | | {"\033[19~", MP_KEY_F+8}, |
99 | | {"\033[20~", MP_KEY_F+9}, |
100 | | {"\033[21~", MP_KEY_F+10}, |
101 | | {"\033[23~", MP_KEY_F+11}, |
102 | | {"\033[24~", MP_KEY_F+12}, |
103 | | |
104 | | {"\033OA", MP_KEY_UP}, |
105 | | {"\033OB", MP_KEY_DOWN}, |
106 | | {"\033OC", MP_KEY_RIGHT}, |
107 | | {"\033OD", MP_KEY_LEFT}, |
108 | | {"\033[A", MP_KEY_UP}, |
109 | | {"\033[B", MP_KEY_DOWN}, |
110 | | {"\033[C", MP_KEY_RIGHT}, |
111 | | {"\033[D", MP_KEY_LEFT}, |
112 | | {"\033[E", MP_KEY_KPBEGIN}, |
113 | | {"\033[F", MP_KEY_END}, |
114 | | {"\033[H", MP_KEY_HOME}, |
115 | | |
116 | | {"\033[[A", MP_KEY_F+1}, |
117 | | {"\033[[B", MP_KEY_F+2}, |
118 | | {"\033[[C", MP_KEY_F+3}, |
119 | | {"\033[[D", MP_KEY_F+4}, |
120 | | {"\033[[E", MP_KEY_F+5}, |
121 | | |
122 | | {"\033OE", MP_KEY_KP5}, // mintty? |
123 | | {"\033OM", MP_KEY_KPENTER}, |
124 | | {"\033OP", MP_KEY_F+1}, |
125 | | {"\033OQ", MP_KEY_F+2}, |
126 | | {"\033OR", MP_KEY_F+3}, |
127 | | {"\033OS", MP_KEY_F+4}, |
128 | | |
129 | | {"\033Oa", MP_KEY_UP | MP_KEY_MODIFIER_CTRL}, // urxvt |
130 | | {"\033Ob", MP_KEY_DOWN | MP_KEY_MODIFIER_CTRL}, |
131 | | {"\033Oc", MP_KEY_RIGHT | MP_KEY_MODIFIER_CTRL}, |
132 | | {"\033Od", MP_KEY_LEFT | MP_KEY_MODIFIER_CTRL}, |
133 | | {"\033Oj", '*'}, // also keypad, but we don't have separate codes for them |
134 | | {"\033Ok", '+'}, |
135 | | {"\033Om", '-'}, |
136 | | {"\033On", MP_KEY_KPDEC}, |
137 | | {"\033Oo", '/'}, |
138 | | {"\033Op", MP_KEY_KP0}, |
139 | | {"\033Oq", MP_KEY_KP1}, |
140 | | {"\033Or", MP_KEY_KP2}, |
141 | | {"\033Os", MP_KEY_KP3}, |
142 | | {"\033Ot", MP_KEY_KP4}, |
143 | | {"\033Ou", MP_KEY_KP5}, |
144 | | {"\033Ov", MP_KEY_KP6}, |
145 | | {"\033Ow", MP_KEY_KP7}, |
146 | | {"\033Ox", MP_KEY_KP8}, |
147 | | {"\033Oy", MP_KEY_KP9}, |
148 | | |
149 | | {"\033[a", MP_KEY_UP | MP_KEY_MODIFIER_SHIFT}, // urxvt |
150 | | {"\033[b", MP_KEY_DOWN | MP_KEY_MODIFIER_SHIFT}, |
151 | | {"\033[c", MP_KEY_RIGHT | MP_KEY_MODIFIER_SHIFT}, |
152 | | {"\033[d", MP_KEY_LEFT | MP_KEY_MODIFIER_SHIFT}, |
153 | | {"\033[2^", MP_KEY_INS | MP_KEY_MODIFIER_CTRL}, |
154 | | {"\033[3^", MP_KEY_DEL | MP_KEY_MODIFIER_CTRL}, |
155 | | {"\033[5^", MP_KEY_PGUP | MP_KEY_MODIFIER_CTRL}, |
156 | | {"\033[6^", MP_KEY_PGDWN | MP_KEY_MODIFIER_CTRL}, |
157 | | {"\033[7^", MP_KEY_HOME | MP_KEY_MODIFIER_CTRL}, |
158 | | {"\033[8^", MP_KEY_END | MP_KEY_MODIFIER_CTRL}, |
159 | | |
160 | | {"\033[1;2", MP_KEY_MODIFIER_SHIFT, .replace = "\033["}, // xterm |
161 | | {"\033[1;3", MP_KEY_MODIFIER_ALT, .replace = "\033["}, |
162 | | {"\033[1;5", MP_KEY_MODIFIER_CTRL, .replace = "\033["}, |
163 | | {"\033[1;4", MP_KEY_MODIFIER_ALT | MP_KEY_MODIFIER_SHIFT, .replace = "\033["}, |
164 | | {"\033[1;6", MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_SHIFT, .replace = "\033["}, |
165 | | {"\033[1;7", MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_ALT, .replace = "\033["}, |
166 | | {"\033[1;8", |
167 | | MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_ALT | MP_KEY_MODIFIER_SHIFT, |
168 | | .replace = "\033["}, |
169 | | |
170 | | {"\033[29~", MP_KEY_MENU}, |
171 | | {"\033[Z", MP_KEY_TAB | MP_KEY_MODIFIER_SHIFT}, |
172 | | |
173 | | // Mouse button inputs. 2 bytes of position information requires special processing. |
174 | | {"\033[M ", MP_MBTN_LEFT | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, |
175 | | {"\033[M!", MP_MBTN_MID | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, |
176 | | {"\033[M\"", MP_MBTN_RIGHT | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, |
177 | | {"\033[M#", MP_INPUT_RELEASE_ALL, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, |
178 | | {"\033[M`", MP_WHEEL_UP, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, |
179 | | {"\033[Ma", MP_WHEEL_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, |
180 | | // Mouse move inputs. No key events should be generated for them. |
181 | | {"\033[M@", MP_MBTN_LEFT | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_MOVE}, |
182 | | {"\033[MA", MP_MBTN_MID | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_MOVE}, |
183 | | {"\033[MB", MP_MBTN_RIGHT | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_MOVE}, |
184 | | {"\033[MC", MP_INPUT_RELEASE_ALL, .skip = 2, .type = ENTRY_TYPE_MOUSE_MOVE}, |
185 | | {0} |
186 | | }; |
187 | | |
188 | 0 | #define BUF_LEN 256 |
189 | | |
190 | | struct termbuf { |
191 | | unsigned char b[BUF_LEN]; |
192 | | int len; |
193 | | int mods; |
194 | | }; |
195 | | |
196 | | static void skip_buf(struct termbuf *b, unsigned int count) |
197 | 0 | { |
198 | 0 | mp_assert(count <= b->len); |
199 | | |
200 | 0 | memmove(&b->b[0], &b->b[count], b->len - count); |
201 | 0 | b->len -= count; |
202 | 0 | b->mods = 0; |
203 | 0 | } |
204 | | |
205 | | static struct termbuf buf; |
206 | | |
207 | | static void process_input(struct input_ctx *input_ctx, bool timeout) |
208 | 0 | { |
209 | 0 | while (buf.len) { |
210 | | // Lone ESC is ambiguous, so accept it only after a timeout. |
211 | 0 | if (timeout && |
212 | 0 | ((buf.len == 1 && buf.b[0] == '\033') || |
213 | 0 | (buf.len > 1 && buf.b[0] == '\033' && buf.b[1] == '\033'))) |
214 | 0 | { |
215 | 0 | mp_input_put_key(input_ctx, MP_KEY_ESC); |
216 | 0 | skip_buf(&buf, 1); |
217 | 0 | } |
218 | |
|
219 | 0 | int utf8_len = bstr_parse_utf8_code_length(buf.b[0]); |
220 | 0 | if (utf8_len > 1) { |
221 | 0 | if (buf.len < utf8_len) |
222 | 0 | goto read_more; |
223 | | |
224 | 0 | mp_input_put_key_utf8(input_ctx, buf.mods, (bstr){buf.b, utf8_len}); |
225 | 0 | skip_buf(&buf, utf8_len); |
226 | 0 | continue; |
227 | 0 | } |
228 | | |
229 | 0 | const struct key_entry *match = NULL; // may be a partial match |
230 | 0 | for (int n = 0; keys[n].seq; n++) { |
231 | 0 | const struct key_entry *e = &keys[n]; |
232 | 0 | if (memcmp(e->seq, buf.b, MPMIN(buf.len, strlen(e->seq))) == 0) { |
233 | 0 | if (match) |
234 | 0 | goto read_more; /* need more bytes to disambiguate */ |
235 | 0 | match = e; |
236 | 0 | } |
237 | 0 | } |
238 | | |
239 | 0 | if (!match) { // normal or unknown key |
240 | 0 | int mods = 0; |
241 | 0 | if (buf.b[0] == '\033') { |
242 | 0 | if (buf.len > 1 && buf.b[1] == '[') { |
243 | | // Throw away unrecognized mouse CSI sequences. |
244 | | // Cannot be handled by the loop below since the bytes |
245 | | // afterwards can be out of that range. |
246 | 0 | if (buf.len > 2 && buf.b[2] == 'M') { |
247 | 0 | skip_buf(&buf, buf.len); |
248 | 0 | continue; |
249 | 0 | } |
250 | | // unknown CSI sequence. wait till it completes |
251 | 0 | for (int i = 2; i < buf.len; i++) { |
252 | 0 | if (buf.b[i] >= 0x40 && buf.b[i] <= 0x7E) { |
253 | 0 | skip_buf(&buf, i+1); |
254 | 0 | continue; // complete - throw it away |
255 | 0 | } |
256 | 0 | } |
257 | 0 | goto read_more; // not yet complete |
258 | 0 | } |
259 | | // non-CSI esc sequence |
260 | 0 | skip_buf(&buf, 1); |
261 | 0 | if (buf.len > 0 && buf.b[0] > 0 && buf.b[0] < 127) { |
262 | | // meta+normal key |
263 | 0 | mods |= MP_KEY_MODIFIER_ALT; |
264 | 0 | } else { |
265 | | // Throw it away. Typically, this will be a complete, |
266 | | // unsupported sequence, and dropping this will skip it. |
267 | 0 | skip_buf(&buf, buf.len); |
268 | 0 | continue; |
269 | 0 | } |
270 | 0 | } |
271 | 0 | unsigned char c = buf.b[0]; |
272 | 0 | skip_buf(&buf, 1); |
273 | 0 | if (c < 32) { |
274 | | // 1..26 is ^A..^Z, and 27..31 is ^3..^7 |
275 | 0 | c = c <= 26 ? (c + 'a' - 1) : (c + '3' - 27); |
276 | 0 | mods |= MP_KEY_MODIFIER_CTRL; |
277 | 0 | } |
278 | 0 | mp_input_put_key(input_ctx, c | mods); |
279 | 0 | continue; |
280 | 0 | } |
281 | | |
282 | 0 | int seq_len = strlen(match->seq) + match->skip; |
283 | 0 | if (seq_len > buf.len) |
284 | 0 | goto read_more; /* partial match */ |
285 | | |
286 | 0 | if (match->replace) { |
287 | 0 | int rep = strlen(match->replace); |
288 | 0 | mp_assert(rep <= seq_len); |
289 | 0 | memcpy(buf.b, match->replace, rep); |
290 | 0 | memmove(buf.b + rep, buf.b + seq_len, buf.len - seq_len); |
291 | 0 | buf.len = rep + buf.len - seq_len; |
292 | 0 | buf.mods |= match->mpkey; |
293 | 0 | continue; |
294 | 0 | } |
295 | | |
296 | | // Parse the initially skipped mouse position information. |
297 | | // The positions are 1-based character cell positions plus 32. |
298 | | // Treat mouse position as the pixel values at the center of the cell. |
299 | 0 | if ((match->type == ENTRY_TYPE_MOUSE_BUTTON || |
300 | 0 | match->type == ENTRY_TYPE_MOUSE_MOVE) && seq_len >= 6) |
301 | 0 | { |
302 | 0 | int num_rows = 80; |
303 | 0 | int num_cols = 25; |
304 | 0 | int total_px_width = 0; |
305 | 0 | int total_px_height = 0; |
306 | 0 | terminal_get_size2(&num_rows, &num_cols, &total_px_width, &total_px_height); |
307 | 0 | mp_input_set_mouse_pos(input_ctx, |
308 | 0 | (buf.b[4] - 32.5) * (total_px_width / num_cols), |
309 | 0 | (buf.b[5] - 32.5) * (total_px_height / num_rows), false); |
310 | 0 | } |
311 | 0 | if (match->type != ENTRY_TYPE_MOUSE_MOVE) |
312 | 0 | mp_input_put_key(input_ctx, buf.mods | match->mpkey); |
313 | 0 | skip_buf(&buf, seq_len); |
314 | 0 | } |
315 | | |
316 | 0 | read_more: ; /* need more bytes */ |
317 | 0 | } |
318 | | |
319 | | static int getch2_active = 0; |
320 | | static int getch2_enabled = 0; |
321 | | static bool read_terminal; |
322 | | |
323 | | static void enable_kx(bool enable) |
324 | 0 | { |
325 | | // This check is actually always true, as enable_kx calls are all guarded |
326 | | // by read_terminal, which is true only if both stdin and stdout are a |
327 | | // tty. Note that stderr being redirected away has no influence over mpv's |
328 | | // I/O handling except for disabling the terminal OSD, and thus stderr |
329 | | // shouldn't be relied on here either. |
330 | 0 | if (isatty(tty_out)) { |
331 | 0 | char *cmd = enable ? "\033=" : "\033>"; |
332 | 0 | (void)write(tty_out, cmd, strlen(cmd)); |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | | static void do_activate_getch2(void) |
337 | 0 | { |
338 | 0 | if (getch2_active || !read_terminal) |
339 | 0 | return; |
340 | | |
341 | 0 | enable_kx(true); |
342 | |
|
343 | 0 | struct termios tio_new; |
344 | 0 | tcgetattr(tty_in, &tio_new); |
345 | |
|
346 | 0 | tio_new.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */ |
347 | 0 | tio_new.c_cc[VMIN] = 1; |
348 | 0 | tio_new.c_cc[VTIME] = 0; |
349 | 0 | tcsetattr(tty_in, TCSANOW, &tio_new); |
350 | |
|
351 | 0 | getch2_active = 1; |
352 | 0 | } |
353 | | |
354 | | static void do_deactivate_getch2(void) |
355 | 19.8k | { |
356 | 19.8k | if (!getch2_active) |
357 | 19.8k | return; |
358 | | |
359 | 0 | enable_kx(false); |
360 | 0 | tcsetattr(tty_in, TCSANOW, &tio_orig); |
361 | |
|
362 | 0 | getch2_active = 0; |
363 | 0 | } |
364 | | |
365 | | // sigaction wrapper |
366 | | static int setsigaction(int signo, void (*handler) (int), |
367 | | int flags, bool do_mask) |
368 | 23.4k | { |
369 | 23.4k | struct sigaction sa; |
370 | 23.4k | sa.sa_handler = handler; |
371 | | |
372 | 23.4k | if (do_mask) |
373 | 8.55k | sigfillset(&sa.sa_mask); |
374 | 14.9k | else |
375 | 14.9k | sigemptyset(&sa.sa_mask); |
376 | | |
377 | 23.4k | sa.sa_flags = flags | SA_RESTART; |
378 | 23.4k | return sigaction(signo, &sa, NULL); |
379 | 23.4k | } |
380 | | |
381 | | static void getch2_poll(void) |
382 | 17.6k | { |
383 | 17.6k | if (!getch2_enabled) |
384 | 0 | return; |
385 | | |
386 | | // check if stdin is in the foreground process group |
387 | 17.6k | int newstatus = (tcgetpgrp(tty_in) == getpgrp()); |
388 | | |
389 | | // and activate getch2 if it is, deactivate otherwise |
390 | 17.6k | if (newstatus) |
391 | 0 | do_activate_getch2(); |
392 | 17.6k | else |
393 | 17.6k | do_deactivate_getch2(); |
394 | 17.6k | } |
395 | | |
396 | | static mp_thread input_thread; |
397 | | static struct input_ctx *input_ctx; |
398 | | static int death_pipe[2] = {-1, -1}; |
399 | | enum { PIPE_STOP, PIPE_CONT }; |
400 | | static int stop_cont_pipe[2] = {-1, -1}; |
401 | | |
402 | | static void stop_cont_sighandler(int signum) |
403 | 0 | { |
404 | 0 | int saved_errno = errno; |
405 | 0 | char sig = signum == SIGCONT ? PIPE_CONT : PIPE_STOP; |
406 | 0 | (void)write(stop_cont_pipe[1], &sig, 1); |
407 | 0 | errno = saved_errno; |
408 | 0 | } |
409 | | |
410 | | static void safe_close(int *p) |
411 | 8.51k | { |
412 | 8.51k | if (*p >= 0) |
413 | 4.30k | close(*p); |
414 | 8.51k | *p = -1; |
415 | 8.51k | } |
416 | | |
417 | | static void close_sig_pipes(void) |
418 | 2.12k | { |
419 | 6.38k | for (int n = 0; n < 2; n++) { |
420 | 4.25k | safe_close(&death_pipe[n]); |
421 | 4.25k | safe_close(&stop_cont_pipe[n]); |
422 | 4.25k | } |
423 | 2.12k | } |
424 | | |
425 | | static void close_tty(void) |
426 | 2.12k | { |
427 | 2.12k | if (tty_in >= 0 && tty_in != STDIN_FILENO) |
428 | 0 | close(tty_in); |
429 | | |
430 | 2.12k | tty_in = tty_out = -1; |
431 | 2.12k | } |
432 | | |
433 | | static void quit_request_sighandler(int signum) |
434 | 0 | { |
435 | 0 | int saved_errno = errno; |
436 | 0 | (void)write(death_pipe[1], &(char){1}, 1); |
437 | 0 | errno = saved_errno; |
438 | 0 | } |
439 | | |
440 | | static MP_THREAD_VOID terminal_thread(void *ptr) |
441 | 21 | { |
442 | 21 | mp_thread_set_name("terminal/input"); |
443 | 21 | bool stdin_ok = read_terminal; // if false, we still wait for SIGTERM |
444 | 15.4k | while (1) { |
445 | 15.4k | getch2_poll(); |
446 | 15.4k | struct pollfd fds[3] = { |
447 | 15.4k | { .events = POLLIN, .fd = death_pipe[0] }, |
448 | 15.4k | { .events = POLLIN, .fd = stop_cont_pipe[0] }, |
449 | 15.4k | { .events = POLLIN, .fd = tty_in } |
450 | 15.4k | }; |
451 | | /* |
452 | | * if the process isn't in foreground process group, then on macos |
453 | | * polldev() doesn't rest and gets into 100% cpu usage (see issue #11795) |
454 | | * with read() returning EIO. but we shouldn't quit on EIO either since |
455 | | * the process might be foregrounded later. |
456 | | * |
457 | | * so just avoid poll-ing tty_in when we know the process is not in the |
458 | | * foreground. there's a small race window, but the timeout will take |
459 | | * care of it so it's fine. |
460 | | */ |
461 | 15.4k | bool is_fg = tcgetpgrp(tty_in) == getpgrp(); |
462 | 15.4k | int r = polldev(fds, stdin_ok && is_fg ? 3 : 2, buf.len ? ESC_TIMEOUT : INPUT_TIMEOUT); |
463 | 15.4k | if (fds[0].revents) { |
464 | 21 | do_deactivate_getch2(); |
465 | 21 | break; |
466 | 21 | } |
467 | 15.4k | if (fds[1].revents & POLLIN) { |
468 | 15.4k | int8_t c = -1; |
469 | 15.4k | (void)read(stop_cont_pipe[0], &c, 1); |
470 | 15.4k | if (c == PIPE_STOP) { |
471 | 0 | do_deactivate_getch2(); |
472 | 0 | if (isatty(STDERR_FILENO)) { |
473 | 0 | (void)write(STDERR_FILENO, TERM_ESC_RESTORE_CURSOR, |
474 | 0 | sizeof(TERM_ESC_RESTORE_CURSOR) - 1); |
475 | 0 | } |
476 | | // trying to reset SIGTSTP handler to default and raise it will |
477 | | // result in a race and there's no other way to invoke the |
478 | | // default handler. so just invoke SIGSTOP since it's |
479 | | // effectively the same thing. |
480 | 0 | raise(SIGSTOP); |
481 | 15.4k | } else if (c == PIPE_CONT) { |
482 | 66 | getch2_poll(); |
483 | 66 | } |
484 | 15.4k | } |
485 | 15.4k | if (fds[2].revents) { |
486 | 0 | int retval = read(tty_in, &buf.b[buf.len], BUF_LEN - buf.len); |
487 | 0 | if (!retval || (retval == -1 && errno != EINTR && errno != EAGAIN && errno != EIO)) |
488 | 0 | break; // EOF/closed |
489 | 0 | if (retval > 0) { |
490 | 0 | buf.len += retval; |
491 | 0 | process_input(input_ctx, false); |
492 | 0 | } |
493 | 0 | } |
494 | 15.4k | if (r == 0) |
495 | 0 | process_input(input_ctx, true); |
496 | 15.4k | } |
497 | 21 | char c; |
498 | 21 | bool quit = read(death_pipe[0], &c, 1) == 1 && c == 1; |
499 | | // Important if we received SIGTERM, rather than regular quit. |
500 | 21 | if (quit) { |
501 | 0 | struct mp_cmd *cmd = mp_input_parse_cmd(input_ctx, bstr0("quit 4"), ""); |
502 | 0 | if (cmd) |
503 | 0 | mp_input_queue_cmd(input_ctx, cmd); |
504 | 0 | } |
505 | 21 | MP_THREAD_RETURN(); |
506 | 21 | } |
507 | | |
508 | | void terminal_setup_getch(struct input_ctx *ictx) |
509 | 22 | { |
510 | 22 | if (!getch2_enabled || input_ctx) |
511 | 1 | return; |
512 | | |
513 | 21 | if (mp_make_wakeup_pipe(death_pipe) < 0) |
514 | 0 | return; |
515 | | |
516 | | // Disable reading from the terminal even if stdout is not a tty, to make |
517 | | // mpv ... | less |
518 | | // do the right thing. |
519 | 21 | read_terminal = isatty(tty_in) && isatty(STDOUT_FILENO); |
520 | | |
521 | 21 | input_ctx = ictx; |
522 | | |
523 | 21 | if (mp_thread_create(&input_thread, terminal_thread, NULL)) { |
524 | 0 | input_ctx = NULL; |
525 | 0 | close_sig_pipes(); |
526 | 0 | close_tty(); |
527 | 0 | return; |
528 | 0 | } |
529 | | |
530 | 21 | setsigaction(SIGINT, quit_request_sighandler, SA_RESETHAND, false); |
531 | 21 | setsigaction(SIGQUIT, quit_request_sighandler, 0, true); |
532 | 21 | setsigaction(SIGTERM, quit_request_sighandler, 0, true); |
533 | 21 | } |
534 | | |
535 | | void terminal_uninit(void) |
536 | 2.12k | { |
537 | 2.12k | if (!getch2_enabled) |
538 | 0 | return; |
539 | | |
540 | | // restore signals |
541 | 2.12k | setsigaction(SIGCONT, SIG_DFL, 0, false); |
542 | 2.12k | setsigaction(SIGTSTP, SIG_DFL, 0, false); |
543 | 2.12k | setsigaction(SIGINT, SIG_DFL, 0, false); |
544 | 2.12k | setsigaction(SIGQUIT, SIG_DFL, 0, false); |
545 | 2.12k | setsigaction(SIGTERM, SIG_DFL, 0, false); |
546 | 2.12k | setsigaction(SIGTTIN, SIG_DFL, 0, false); |
547 | 2.12k | setsigaction(SIGTTOU, SIG_DFL, 0, false); |
548 | | |
549 | 2.12k | if (input_ctx) { |
550 | 21 | (void)write(death_pipe[1], &(char){0}, 1); |
551 | 21 | mp_thread_join(input_thread); |
552 | 21 | } |
553 | | |
554 | 2.12k | close_sig_pipes(); |
555 | 2.12k | input_ctx = NULL; |
556 | | |
557 | 2.12k | do_deactivate_getch2(); |
558 | 2.12k | close_tty(); |
559 | | |
560 | 2.12k | getch2_enabled = 0; |
561 | 2.12k | read_terminal = false; |
562 | 2.12k | } |
563 | | |
564 | | bool terminal_in_background(void) |
565 | 4.56k | { |
566 | 4.56k | return read_terminal && tcgetpgrp(STDERR_FILENO) != getpgrp(); |
567 | 4.56k | } |
568 | | |
569 | | void terminal_get_size(int *w, int *h) |
570 | 218 | { |
571 | 218 | struct winsize ws; |
572 | 218 | if (ioctl(tty_in, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col) |
573 | 218 | return; |
574 | | |
575 | 0 | *w = ws.ws_col; |
576 | 0 | *h = ws.ws_row; |
577 | 0 | } |
578 | | |
579 | | void terminal_get_size2(int *rows, int *cols, int *px_width, int *px_height) |
580 | 17 | { |
581 | 17 | struct winsize ws; |
582 | 17 | if (ioctl(tty_in, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col |
583 | 17 | || !ws.ws_xpixel || !ws.ws_ypixel) |
584 | 17 | return; |
585 | | |
586 | 0 | *rows = ws.ws_row; |
587 | 0 | *cols = ws.ws_col; |
588 | 0 | *px_width = ws.ws_xpixel; |
589 | 0 | *px_height = ws.ws_ypixel; |
590 | 0 | } |
591 | | |
592 | | void terminal_set_mouse_input(bool enable) |
593 | 44 | { |
594 | 44 | printf(enable ? TERM_ESC_ENABLE_MOUSE : TERM_ESC_DISABLE_MOUSE); |
595 | 44 | fflush(stdout); |
596 | 44 | } |
597 | | |
598 | | void terminal_init(void) |
599 | 2.12k | { |
600 | 2.12k | mp_assert(!getch2_enabled); |
601 | 2.12k | getch2_enabled = 1; |
602 | | |
603 | 2.12k | if (mp_make_wakeup_pipe(stop_cont_pipe) < 0) { |
604 | 0 | getch2_enabled = 0; |
605 | 0 | return; |
606 | 0 | } |
607 | | |
608 | 2.12k | tty_in = tty_out = open("/dev/tty", O_RDWR | O_CLOEXEC); |
609 | 2.12k | if (tty_in < 0) { |
610 | 2.12k | tty_in = STDIN_FILENO; |
611 | 2.12k | tty_out = STDOUT_FILENO; |
612 | 2.12k | } |
613 | | |
614 | 2.12k | tcgetattr(tty_in, &tio_orig); |
615 | | |
616 | | // handlers to fix terminal settings |
617 | 2.12k | setsigaction(SIGCONT, stop_cont_sighandler, 0, true); |
618 | 2.12k | setsigaction(SIGTSTP, stop_cont_sighandler, 0, true); |
619 | 2.12k | setsigaction(SIGTTIN, SIG_IGN, 0, true); |
620 | 2.12k | setsigaction(SIGTTOU, SIG_IGN, 0, true); |
621 | | |
622 | 2.12k | getch2_poll(); |
623 | 2.12k | } |