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