1 /*
2 Interface to the terminal controlling library.
3 Ncurses wrapper.
4
5 Copyright (C) 2005-2021
6 Free Software Foundation, Inc.
7
8 Written by:
9 Andrew Borodin <aborodin@vmail.ru>, 2009.
10 Ilia Maslakov <il.smind@gmail.com>, 2009.
11
12 This file is part of the Midnight Commander.
13
14 The Midnight Commander is free software: you can redistribute it
15 and/or modify it under the terms of the GNU General Public License as
16 published by the Free Software Foundation, either version 3 of the License,
17 or (at your option) any later version.
18
19 The Midnight Commander is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28 /** \file
29 * \brief Source: NCurses-based tty layer of Midnight-commander
30 */
31
32 #include <config.h>
33
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include <signal.h>
37 #ifdef HAVE_SYS_IOCTL_H
38 #include <sys/ioctl.h>
39 #endif
40 #include <termios.h>
41
42 #include "lib/global.h"
43 #include "lib/strutil.h" /* str_term_form */
44
45 #ifndef WANT_TERM_H
46 #define WANT_TERM_H
47 #endif
48
49 #include "tty-internal.h" /* mc_tty_normalize_from_utf8() */
50 #include "tty.h"
51 #include "color.h" /* tty_setcolor */
52 #include "color-internal.h"
53 #include "key.h"
54 #include "mouse.h"
55 #include "win.h"
56
57 /* include at last !!! */
58 #ifdef WANT_TERM_H
59 #ifdef HAVE_NCURSES_TERM_H
60 #include <ncurses/term.h>
61 #else
62 #include <term.h>
63 #endif /* HAVE_NCURSES_TERM_H */
64 #endif /* WANT_TERM_H */
65
66 /*** global variables ****************************************************************************/
67
68 /*** file scope macro definitions ****************************************************************/
69
70 #if !defined(CTRL)
71 #define CTRL(x) ((x) & 0x1f)
72 #endif
73
74 #define yx_in_screen(y, x) \
75 (y >= 0 && y < LINES && x >= 0 && x < COLS)
76
77 /*** global variables ****************************************************************************/
78
79 /*** file scope type declarations ****************************************************************/
80
81 /*** file scope variables ************************************************************************/
82
83 /* ncurses supports cursor positions only within window */
84 /* We use our own cursor coordinates to support partially visible widgets */
85 static int mc_curs_row, mc_curs_col;
86
87 /*** file scope functions ************************************************************************/
88 /* --------------------------------------------------------------------------------------------- */
89
90 /* --------------------------------------------------------------------------------------------- */
91
92 static void
tty_setup_sigwinch(void (* handler)(int))93 tty_setup_sigwinch (void (*handler) (int))
94 {
95 #if (NCURSES_VERSION_MAJOR >= 4) && defined (SIGWINCH)
96 struct sigaction act, oact;
97
98 memset (&act, 0, sizeof (act));
99 act.sa_handler = handler;
100 sigemptyset (&act.sa_mask);
101 #ifdef SA_RESTART
102 act.sa_flags = SA_RESTART;
103 #endif /* SA_RESTART */
104 sigaction (SIGWINCH, &act, &oact);
105 #endif /* SIGWINCH */
106
107 tty_create_winch_pipe ();
108 }
109
110 /* --------------------------------------------------------------------------------------------- */
111
112 static void
sigwinch_handler(int dummy)113 sigwinch_handler (int dummy)
114 {
115 ssize_t n = 0;
116
117 (void) dummy;
118
119 n = write (sigwinch_pipe[1], "", 1);
120 (void) n;
121 }
122
123 /* --------------------------------------------------------------------------------------------- */
124
125 /**
126 * Get visible part of area.
127 *
128 * @returns TRUE if any part of area is in screen bounds, FALSE otherwise.
129 */
130 static gboolean
tty_clip(int * y,int * x,int * rows,int * cols)131 tty_clip (int *y, int *x, int *rows, int *cols)
132 {
133 if (*y < 0)
134 {
135 *rows += *y;
136
137 if (*rows <= 0)
138 return FALSE;
139
140 *y = 0;
141 }
142
143 if (*x < 0)
144 {
145 *cols += *x;
146
147 if (*cols <= 0)
148 return FALSE;
149
150 *x = 0;
151 }
152
153 if (*y + *rows > LINES)
154 *rows = LINES - *y;
155
156 if (*rows <= 0)
157 return FALSE;
158
159 if (*x + *cols > COLS)
160 *cols = COLS - *x;
161
162 if (*cols <= 0)
163 return FALSE;
164
165 return TRUE;
166 }
167
168 /* --------------------------------------------------------------------------------------------- */
169 /*** public functions ****************************************************************************/
170 /* --------------------------------------------------------------------------------------------- */
171
172 int
mc_tty_normalize_lines_char(const char * ch)173 mc_tty_normalize_lines_char (const char *ch)
174 {
175 char *str2;
176 int res;
177
178 struct mc_tty_lines_struct
179 {
180 const char *line;
181 int line_code;
182 } const lines_codes[] = {
183 {"\342\224\230", ACS_LRCORNER}, /* ┌ */
184 {"\342\224\224", ACS_LLCORNER}, /* └ */
185 {"\342\224\220", ACS_URCORNER}, /* ┐ */
186 {"\342\224\214", ACS_ULCORNER}, /* ┘ */
187 {"\342\224\234", ACS_LTEE}, /* ├ */
188 {"\342\224\244", ACS_RTEE}, /* ┤ */
189 {"\342\224\254", ACS_TTEE}, /* ┬ */
190 {"\342\224\264", ACS_BTEE}, /* ┴ */
191 {"\342\224\200", ACS_HLINE}, /* ─ */
192 {"\342\224\202", ACS_VLINE}, /* │ */
193 {"\342\224\274", ACS_PLUS}, /* ┼ */
194
195 {"\342\225\235", ACS_LRCORNER | A_BOLD}, /* ╔ */
196 {"\342\225\232", ACS_LLCORNER | A_BOLD}, /* ╚ */
197 {"\342\225\227", ACS_URCORNER | A_BOLD}, /* ╗ */
198 {"\342\225\224", ACS_ULCORNER | A_BOLD}, /* ╝ */
199 {"\342\225\237", ACS_LTEE | A_BOLD}, /* ╟ */
200 {"\342\225\242", ACS_RTEE | A_BOLD}, /* ╢ */
201 {"\342\225\244", ACS_TTEE | A_BOLD}, /* ╤ */
202 {"\342\225\247", ACS_BTEE | A_BOLD}, /* ╧ */
203 {"\342\225\220", ACS_HLINE | A_BOLD}, /* ═ */
204 {"\342\225\221", ACS_VLINE | A_BOLD}, /* ║ */
205
206 {NULL, 0}
207 };
208
209 if (ch == NULL)
210 return (int) ' ';
211
212 for (res = 0; lines_codes[res].line; res++)
213 {
214 if (strcmp (ch, lines_codes[res].line) == 0)
215 return lines_codes[res].line_code;
216 }
217
218 str2 = mc_tty_normalize_from_utf8 (ch);
219 res = g_utf8_get_char_validated (str2, -1);
220
221 if (res < 0)
222 res = (unsigned char) str2[0];
223 g_free (str2);
224
225 return res;
226 }
227
228 /* --------------------------------------------------------------------------------------------- */
229
230 void
tty_init(gboolean mouse_enable,gboolean is_xterm)231 tty_init (gboolean mouse_enable, gboolean is_xterm)
232 {
233 struct termios mode;
234
235 initscr ();
236
237 #ifdef HAVE_ESCDELAY
238 /*
239 * If ncurses exports the ESCDELAY variable, it should be set to
240 * a low value, or you'll experience a delay in processing escape
241 * sequences that are recognized by mc (e.g. Esc-Esc). On the other
242 * hand, making ESCDELAY too small can result in some sequences
243 * (e.g. cursor arrows) being reported as separate keys under heavy
244 * processor load, and this can be a problem if mc hasn't learned
245 * them in the "Learn Keys" dialog. The value is in milliseconds.
246 */
247 ESCDELAY = 200;
248 #endif /* HAVE_ESCDELAY */
249
250 tcgetattr (STDIN_FILENO, &mode);
251 /* use Ctrl-g to generate SIGINT */
252 mode.c_cc[VINTR] = CTRL ('g'); /* ^g */
253 /* disable SIGQUIT to allow use Ctrl-\ key */
254 mode.c_cc[VQUIT] = NULL_VALUE;
255 tcsetattr (STDIN_FILENO, TCSANOW, &mode);
256
257 /* curses remembers the "in-program" modes after this call */
258 def_prog_mode ();
259
260 tty_start_interrupt_key ();
261
262 if (!mouse_enable)
263 use_mouse_p = MOUSE_DISABLED;
264 tty_init_xterm_support (is_xterm); /* do it before tty_enter_ca_mode() call */
265 tty_enter_ca_mode ();
266 tty_raw_mode ();
267 noecho ();
268 keypad (stdscr, TRUE);
269 nodelay (stdscr, FALSE);
270
271 tty_setup_sigwinch (sigwinch_handler);
272 }
273
274 /* --------------------------------------------------------------------------------------------- */
275
276 void
tty_shutdown(void)277 tty_shutdown (void)
278 {
279 tty_destroy_winch_pipe ();
280 tty_reset_shell_mode ();
281 tty_noraw_mode ();
282 tty_keypad (FALSE);
283 tty_reset_screen ();
284 tty_exit_ca_mode ();
285 }
286
287 /* --------------------------------------------------------------------------------------------- */
288
289 void
tty_enter_ca_mode(void)290 tty_enter_ca_mode (void)
291 {
292 if (mc_global.tty.xterm_flag)
293 {
294 fprintf (stdout, /* ESC_STR ")0" */ ESC_STR "7" ESC_STR "[?47h");
295 fflush (stdout);
296 }
297 }
298
299 /* --------------------------------------------------------------------------------------------- */
300
301 void
tty_exit_ca_mode(void)302 tty_exit_ca_mode (void)
303 {
304 if (mc_global.tty.xterm_flag)
305 {
306 fprintf (stdout, ESC_STR "[?47l" ESC_STR "8" ESC_STR "[m");
307 fflush (stdout);
308 }
309 }
310
311 /* --------------------------------------------------------------------------------------------- */
312
313 void
tty_change_screen_size(void)314 tty_change_screen_size (void)
315 {
316 #if defined(TIOCGWINSZ) && NCURSES_VERSION_MAJOR >= 4
317 struct winsize winsz;
318
319 winsz.ws_col = winsz.ws_row = 0;
320
321 #ifndef NCURSES_VERSION
322 tty_noraw_mode ();
323 tty_reset_screen ();
324 #endif
325
326 /* Ioctl on the STDIN_FILENO */
327 ioctl (fileno (stdout), TIOCGWINSZ, &winsz);
328 if (winsz.ws_col != 0 && winsz.ws_row != 0)
329 {
330 #if defined(NCURSES_VERSION) && defined(HAVE_RESIZETERM)
331 resizeterm (winsz.ws_row, winsz.ws_col);
332 clearok (stdscr, TRUE); /* sigwinch's should use a semaphore! */
333 #else
334 COLS = winsz.ws_col;
335 LINES = winsz.ws_row;
336 #endif
337 }
338 #endif /* defined(TIOCGWINSZ) || NCURSES_VERSION_MAJOR >= 4 */
339
340 #ifdef ENABLE_SUBSHELL
341 if (mc_global.tty.use_subshell)
342 tty_resize (mc_global.tty.subshell_pty);
343 #endif
344 }
345
346 /* --------------------------------------------------------------------------------------------- */
347
348 void
tty_reset_prog_mode(void)349 tty_reset_prog_mode (void)
350 {
351 reset_prog_mode ();
352 }
353
354 /* --------------------------------------------------------------------------------------------- */
355
356 void
tty_reset_shell_mode(void)357 tty_reset_shell_mode (void)
358 {
359 reset_shell_mode ();
360 }
361
362 /* --------------------------------------------------------------------------------------------- */
363
364 void
tty_raw_mode(void)365 tty_raw_mode (void)
366 {
367 raw (); /* FIXME: uneeded? */
368 cbreak ();
369 }
370
371 /* --------------------------------------------------------------------------------------------- */
372
373 void
tty_noraw_mode(void)374 tty_noraw_mode (void)
375 {
376 nocbreak (); /* FIXME: unneeded? */
377 noraw ();
378 }
379
380 /* --------------------------------------------------------------------------------------------- */
381
382 void
tty_noecho(void)383 tty_noecho (void)
384 {
385 noecho ();
386 }
387
388 /* --------------------------------------------------------------------------------------------- */
389
390 int
tty_flush_input(void)391 tty_flush_input (void)
392 {
393 return flushinp ();
394 }
395
396 /* --------------------------------------------------------------------------------------------- */
397
398 void
tty_keypad(gboolean set)399 tty_keypad (gboolean set)
400 {
401 keypad (stdscr, (bool) set);
402 }
403
404 /* --------------------------------------------------------------------------------------------- */
405
406 void
tty_nodelay(gboolean set)407 tty_nodelay (gboolean set)
408 {
409 nodelay (stdscr, (bool) set);
410 }
411
412 /* --------------------------------------------------------------------------------------------- */
413
414 int
tty_baudrate(void)415 tty_baudrate (void)
416 {
417 return baudrate ();
418 }
419
420 /* --------------------------------------------------------------------------------------------- */
421
422 int
tty_lowlevel_getch(void)423 tty_lowlevel_getch (void)
424 {
425 return getch ();
426 }
427
428 /* --------------------------------------------------------------------------------------------- */
429
430 int
tty_reset_screen(void)431 tty_reset_screen (void)
432 {
433 return endwin ();
434 }
435
436 /* --------------------------------------------------------------------------------------------- */
437
438 void
tty_touch_screen(void)439 tty_touch_screen (void)
440 {
441 touchwin (stdscr);
442 }
443
444 /* --------------------------------------------------------------------------------------------- */
445
446 void
tty_gotoyx(int y,int x)447 tty_gotoyx (int y, int x)
448 {
449 mc_curs_row = y;
450 mc_curs_col = x;
451
452 if (y < 0)
453 y = 0;
454 if (y >= LINES)
455 y = LINES - 1;
456
457 if (x < 0)
458 x = 0;
459 if (x >= COLS)
460 x = COLS - 1;
461
462 move (y, x);
463 }
464
465 /* --------------------------------------------------------------------------------------------- */
466
467 void
tty_getyx(int * py,int * px)468 tty_getyx (int *py, int *px)
469 {
470 *py = mc_curs_row;
471 *px = mc_curs_col;
472 }
473
474 /* --------------------------------------------------------------------------------------------- */
475
476 void
tty_draw_hline(int y,int x,int ch,int len)477 tty_draw_hline (int y, int x, int ch, int len)
478 {
479 int x1;
480
481 if (y < 0 || y >= LINES || x >= COLS)
482 return;
483
484 x1 = x;
485
486 if (x < 0)
487 {
488 len += x;
489 if (len <= 0)
490 return;
491 x = 0;
492 }
493
494 if ((chtype) ch == ACS_HLINE)
495 ch = mc_tty_frm[MC_TTY_FRM_HORIZ];
496
497 move (y, x);
498 hline (ch, len);
499 move (y, x1);
500
501 mc_curs_row = y;
502 mc_curs_col = x1;
503 }
504
505 /* --------------------------------------------------------------------------------------------- */
506
507 void
tty_draw_vline(int y,int x,int ch,int len)508 tty_draw_vline (int y, int x, int ch, int len)
509 {
510 int y1;
511
512 if (x < 0 || x >= COLS || y >= LINES)
513 return;
514
515 y1 = y;
516
517 if (y < 0)
518 {
519 len += y;
520 if (len <= 0)
521 return;
522 y = 0;
523 }
524
525 if ((chtype) ch == ACS_VLINE)
526 ch = mc_tty_frm[MC_TTY_FRM_VERT];
527
528 move (y, x);
529 vline (ch, len);
530 move (y1, x);
531
532 mc_curs_row = y1;
533 mc_curs_col = x;
534 }
535
536 /* --------------------------------------------------------------------------------------------- */
537
538 void
tty_fill_region(int y,int x,int rows,int cols,unsigned char ch)539 tty_fill_region (int y, int x, int rows, int cols, unsigned char ch)
540 {
541 int i;
542
543 if (!tty_clip (&y, &x, &rows, &cols))
544 return;
545
546 for (i = 0; i < rows; i++)
547 {
548 move (y + i, x);
549 hline (ch, cols);
550 }
551
552 move (y, x);
553
554 mc_curs_row = y;
555 mc_curs_col = x;
556 }
557
558 /* --------------------------------------------------------------------------------------------- */
559
560 void
tty_colorize_area(int y,int x,int rows,int cols,int color)561 tty_colorize_area (int y, int x, int rows, int cols, int color)
562 {
563 cchar_t *ctext;
564 wchar_t wch[10]; /* TODO not sure if the length is correct */
565 attr_t attrs;
566 short color_pair;
567
568 if (!use_colors || !tty_clip (&y, &x, &rows, &cols))
569 return;
570
571 tty_setcolor (color);
572 ctext = g_malloc (sizeof (cchar_t) * (cols + 1));
573
574 for (int row = 0; row < rows; row++)
575 {
576 mvin_wchnstr (y + row, x, ctext, cols);
577
578 for (int col = 0; col < cols; col++)
579 {
580 getcchar (&ctext[col], wch, &attrs, &color_pair, NULL);
581 setcchar (&ctext[col], wch, attrs, color, NULL);
582 }
583
584 mvadd_wchnstr (y + row, x, ctext, cols);
585 }
586
587 g_free (ctext);
588 }
589
590 /* --------------------------------------------------------------------------------------------- */
591
592 void
tty_set_alt_charset(gboolean alt_charset)593 tty_set_alt_charset (gboolean alt_charset)
594 {
595 (void) alt_charset;
596 }
597
598 /* --------------------------------------------------------------------------------------------- */
599
600 void
tty_display_8bit(gboolean what)601 tty_display_8bit (gboolean what)
602 {
603 meta (stdscr, (int) what);
604 }
605
606 /* --------------------------------------------------------------------------------------------- */
607
608 void
tty_print_char(int c)609 tty_print_char (int c)
610 {
611 if (yx_in_screen (mc_curs_row, mc_curs_col))
612 addch (c);
613 mc_curs_col++;
614 }
615
616 /* --------------------------------------------------------------------------------------------- */
617
618 void
tty_print_anychar(int c)619 tty_print_anychar (int c)
620 {
621 if (mc_global.utf8_display || c > 255)
622 {
623 int res;
624 unsigned char str[UTF8_CHAR_LEN + 1];
625
626 res = g_unichar_to_utf8 (c, (char *) str);
627 if (res == 0)
628 {
629 if (yx_in_screen (mc_curs_row, mc_curs_col))
630 addch ('.');
631 mc_curs_col++;
632 }
633 else
634 {
635 const char *s;
636
637 str[res] = '\0';
638 s = str_term_form ((char *) str);
639
640 if (yx_in_screen (mc_curs_row, mc_curs_col))
641 addstr (s);
642
643 if (g_unichar_iswide (c))
644 mc_curs_col += 2;
645 else if (!g_unichar_iszerowidth (c))
646 mc_curs_col++;
647 }
648 }
649 else
650 {
651 if (yx_in_screen (mc_curs_row, mc_curs_col))
652 addch (c);
653 mc_curs_col++;
654 }
655 }
656
657 /* --------------------------------------------------------------------------------------------- */
658
659 void
tty_print_alt_char(int c,gboolean single)660 tty_print_alt_char (int c, gboolean single)
661 {
662 if (yx_in_screen (mc_curs_row, mc_curs_col))
663 {
664 if ((chtype) c == ACS_VLINE)
665 c = mc_tty_frm[single ? MC_TTY_FRM_VERT : MC_TTY_FRM_DVERT];
666 else if ((chtype) c == ACS_HLINE)
667 c = mc_tty_frm[single ? MC_TTY_FRM_HORIZ : MC_TTY_FRM_DHORIZ];
668 else if ((chtype) c == ACS_LTEE)
669 c = mc_tty_frm[single ? MC_TTY_FRM_LEFTMIDDLE : MC_TTY_FRM_DLEFTMIDDLE];
670 else if ((chtype) c == ACS_RTEE)
671 c = mc_tty_frm[single ? MC_TTY_FRM_RIGHTMIDDLE : MC_TTY_FRM_DRIGHTMIDDLE];
672 else if ((chtype) c == ACS_ULCORNER)
673 c = mc_tty_frm[single ? MC_TTY_FRM_LEFTTOP : MC_TTY_FRM_DLEFTTOP];
674 else if ((chtype) c == ACS_LLCORNER)
675 c = mc_tty_frm[single ? MC_TTY_FRM_LEFTBOTTOM : MC_TTY_FRM_DLEFTBOTTOM];
676 else if ((chtype) c == ACS_URCORNER)
677 c = mc_tty_frm[single ? MC_TTY_FRM_RIGHTTOP : MC_TTY_FRM_DRIGHTTOP];
678 else if ((chtype) c == ACS_LRCORNER)
679 c = mc_tty_frm[single ? MC_TTY_FRM_RIGHTBOTTOM : MC_TTY_FRM_DRIGHTBOTTOM];
680 else if ((chtype) c == ACS_PLUS)
681 c = mc_tty_frm[MC_TTY_FRM_CROSS];
682
683 addch (c);
684 }
685
686 mc_curs_col++;
687 }
688
689 /* --------------------------------------------------------------------------------------------- */
690
691 void
tty_print_string(const char * s)692 tty_print_string (const char *s)
693 {
694 int len;
695 int start = 0;
696
697 s = str_term_form (s);
698 len = str_term_width1 (s);
699
700 /* line is upper or below the screen or entire line is before or after screen */
701 if (mc_curs_row < 0 || mc_curs_row >= LINES || mc_curs_col + len <= 0 || mc_curs_col >= COLS)
702 {
703 mc_curs_col += len;
704 return;
705 }
706
707 /* skip invisible left part */
708 if (mc_curs_col < 0)
709 {
710 start = -mc_curs_col;
711 len += mc_curs_col;
712 mc_curs_col = 0;
713 }
714
715 mc_curs_col += len;
716 if (mc_curs_col >= COLS)
717 len = COLS - (mc_curs_col - len);
718
719 addstr (str_term_substring (s, start, len));
720 }
721
722 /* --------------------------------------------------------------------------------------------- */
723
724 void
tty_printf(const char * fmt,...)725 tty_printf (const char *fmt, ...)
726 {
727 va_list args;
728 char buf[BUF_1K]; /* FIXME: is it enough? */
729
730 va_start (args, fmt);
731 g_vsnprintf (buf, sizeof (buf), fmt, args);
732 va_end (args);
733 tty_print_string (buf);
734 }
735
736 /* --------------------------------------------------------------------------------------------- */
737
738 char *
tty_tgetstr(const char * cap)739 tty_tgetstr (const char *cap)
740 {
741 char *unused = NULL;
742
743 return tgetstr ((NCURSES_CONST char *) cap, &unused);
744 }
745
746 /* --------------------------------------------------------------------------------------------- */
747
748 void
tty_refresh(void)749 tty_refresh (void)
750 {
751 refresh ();
752 doupdate ();
753 }
754
755 /* --------------------------------------------------------------------------------------------- */
756
757 void
tty_beep(void)758 tty_beep (void)
759 {
760 beep ();
761 }
762
763 /* --------------------------------------------------------------------------------------------- */
764