1 /*  term.c: terminal handling, cursor movement etc.
2     (C) 2000-2007 Hans Lub
3 
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License , or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program; see the file COPYING.  If not, write to
16     the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18     You may contact the author by:
19     e-mail:  hanslub42@gmail.com
20 */
21 
22 
23 #include "rlwrap.h"
24 
25 /* rlwrap needs a little bit of cursor acrobatics to cope with multi-line prompts, recovering from CTRL-Z and editors that may have used
26 the alternate screen. All such cursor movements are done via the functions in this file. In the past, rlwrap relied on only the termcap
27 library (or an emulation of it), and would fall back on reasonable defaults ("\r", "\b" etc) if this was not found.
28 
29 In the future it will also use terminfo or even ncurses. But the fall-back to older libraries is still important, as rlwrap seems
30 to be especially needed on older machines                                                                                            */
31 
32 /*global vars */
33 char term_eof;                       /* end_of_file char */
34 char term_stop;                      /* stop (suspend) key */
35 char *term_backspace;                /* backspace control seq  (or 0, if none defined in terminfo) */
36 char *term_cursor_hpos;              /* control seq to position cursor st given position on current line */
37 char *term_clear_screen;
38 char *term_cursor_up;
39 char *term_cursor_down;
40 char *term_cursor_left;              /* only used for debugging (the SHOWCURSOR macro)                */
41 char *term_cursor_right;             /* only used to emulate a missing term_cursor_hpos               */
42 char *term_smcup;                    /* smcup - char sequence to switch to  alternate screen          */
43 char *term_rmcup;                    /* rmcup - char sequence to return from alternate screen         */
44 char *term_rmkx;                     /* rmkx - char sequence to return from keyboard application mode */
45 char *term_enable_bracketed_paste;   /* If we write this to some terminals (xterm etc.) they  will    */
46                                      /* "bracket" pasted input with \e[200 and \e201                  */
47 char *term_disable_bracketed_paste;
48 int term_has_colours;
49 
50 int redisplay = 1;
51 int newline_came_last = TRUE; /* used to determine whether rlwrap needs to ouptut a newline at the very end */
52 
53 struct termios saved_terminal_settings; /* original terminal settings */
54 int terminal_settings_saved = FALSE;    /*  saved_terminal_settings is valid */
55 struct winsize winsize; /* current window size */
56 
57 static char *term_cr;           /* carriage return (or 0, if none defined in terminfo) */
58 static char *term_clear_line;
59 char *term_name;
60 
61 
my_tgetstr(char * id,const char * capability_name)62 static char *my_tgetstr (char *id, const char *capability_name) {
63 #ifdef HAVE_TERMCAP_H
64   char *term_string_buf = (char *)mymalloc(2048), *tb = term_string_buf;
65   char *stringcap = tgetstr(id, &tb); /*  rl_get_termcap(id) only gets capabilities used by readline */
66   char *retval = stringcap ? mysavestring(stringcap) : NULL;
67   DPRINTF3(DEBUG_TERMIO, "tgetstr(\"%s\") = %s (%s)", id, (stringcap ? M(stringcap) : "NULL"), capability_name);
68   free(term_string_buf);
69   return retval;
70 #else
71   return NULL;
72 #endif
73 }
74 
my_tigetstr(char * tid,const char * capability_name)75 static char *my_tigetstr (char *tid, const char *capability_name) {
76 #ifdef HAVE_CURSES_H
77   static int term_has_been_setup = FALSE;
78   char *stringcap, *retval;
79   if (!term_has_been_setup) {
80      setupterm(term_name, STDIN_FILENO, (int *)0); /* like tgetent() before tgetstr(), we have to call setupterm() befor tigetstr() */
81      term_has_been_setup = TRUE;
82   }
83   stringcap = tigetstr(tid);
84   retval = stringcap ? mysavestring(stringcap) : NULL;
85   DPRINTF3(DEBUG_TERMIO, "tigetstr(\"%s\") = %s (%s)", tid, (stringcap ? M(stringcap) : "NULL"), capability_name);
86   return retval;
87 #else
88   return NULL;
89 #endif
90 }
91 
92 /* Get (copies of) escape codes ("string capabilities") by name. First try terminfo name, then termcap name  */
93 static char *
tigetstr_or_else_tgetstr(char * capname,char * tcap_code,const char * capability_name)94 tigetstr_or_else_tgetstr(char *capname, char *tcap_code, const char *capability_name)
95 {
96   char *retval;
97   retval = my_tigetstr(capname, capability_name);
98   if (!retval)
99     retval = my_tgetstr(tcap_code, capability_name);
100   return retval;
101 }
102 
103 #define FALLBACK_TERMINAL "vt100" /* hard-coded terminal name to use when we don't know which terminal we're on */
104 
105 void
init_terminal(void)106 init_terminal(void)
107 {                               /* save term settings and determine term type */
108   char *term_buf = mymalloc(2048);
109   int  we_have_stringcaps;
110   int  we_are_on_hp_ux11;
111 
112   if (!isatty(STDIN_FILENO))
113     myerror(FATAL|NOERRNO, "stdin is not a tty");
114   if (tcgetattr(STDIN_FILENO, &saved_terminal_settings) < 0)
115     myerror(FATAL|USE_ERRNO, "tcgetattr error on stdin");
116   else
117     terminal_settings_saved = TRUE;
118   if (ioctl(STDIN_FILENO, TIOCGWINSZ, &winsize) < 0)
119     myerror(FATAL|USE_ERRNO, "Could not get terminal size");
120 
121   DPRINTF2(DEBUG_TERMIO, "winsize.ws_col (term width): %d; winsize.ws_row (term height): %d", winsize.ws_col, winsize.ws_row);
122   if (winsize.ws_col == 0)
123     myerror(FATAL|NOERRNO, "My terminal reports width=0 (is it emacs?)  I can't handle this, sorry!");
124 
125   /* init some variables: */
126   term_name = getenv("TERM");
127   DPRINTF1(DEBUG_TERMIO, "found TERM = %s", term_name);
128 
129 
130 
131 
132 
133 
134   /* On weird and scary HP-UX 11 succesful tgetent() returns 0, so we need the followig to portably catch errors: */
135   we_are_on_hp_ux11 = tgetent(term_buf, "vt100") == 0 && tgetent(term_buf,"QzBgt57gr6xwxw") == -1; /* assuming there is no terminal called Qz... */
136   #define T_OK(retval) ((retval) > 0 || ((retval)== 0 && we_are_on_hp_ux11))
137 
138   /* if TERM is not set, use FALLBACK_TERMINAL */
139   if (!term_name || strlen(term_name)==0) {
140     myerror(WARNING|USE_ERRNO, "environment variable TERM not set, assuming %s", FALLBACK_TERMINAL);
141     term_name = FALLBACK_TERMINAL;
142   }
143 
144   /* If we cannot find stringcaps, use FALLBACK_TERMINAL  */
145   if (! (we_have_stringcaps = T_OK(tgetent(term_buf, term_name))) && strcmp(term_name, FALLBACK_TERMINAL)) {
146     myerror(WARNING|NOERRNO, "your $TERM is '%s' but %s couldn't find it in the terminfo database. We'll use '%s'",
147                             term_name, program_name, FALLBACK_TERMINAL);
148     term_name = FALLBACK_TERMINAL;
149   }
150 
151 
152   /* If we cannot find stringcaps, even for FALLBACK_TERMINAL, complain */
153   if (!we_have_stringcaps && ! (we_have_stringcaps = T_OK(tgetent(term_buf, FALLBACK_TERMINAL))))
154     myerror(WARNING|NOERRNO, "Even %s is not found in the terminfo database. Expect some problems.", FALLBACK_TERMINAL);
155 
156   DPRINTF1(DEBUG_TERMIO, "using TERM = %s", term_name);
157   mysetenv("TERM", term_name);
158 
159 
160 
161 
162   if (we_have_stringcaps)  {
163     term_backspace      = tigetstr_or_else_tgetstr("cub1",   "le", "backspace");
164     term_cr             = tigetstr_or_else_tgetstr("cr",     "cr", "carriage return");
165     term_clear_line     = tigetstr_or_else_tgetstr("el",     "ce", "clear line");
166     term_clear_screen   = tigetstr_or_else_tgetstr("clear",  "cl", "clear screen");
167     term_cursor_hpos    = tigetstr_or_else_tgetstr("hpa",    "ch", "set cursor horizontal position");
168     term_cursor_left    = tigetstr_or_else_tgetstr("cub1",   "le", "move cursor left");
169     term_cursor_right   = tigetstr_or_else_tgetstr("cuf1",   "nd", "move cursor right");
170     term_cursor_up      = tigetstr_or_else_tgetstr("cuu1",   "up", "move cursor up");
171     term_cursor_down    = tigetstr_or_else_tgetstr("cud1",   "do", "move cursor down");
172     term_has_colours    = tigetstr_or_else_tgetstr("initc", "Ic",  "initialise colour") ? TRUE : FALSE;
173 
174     /* the following codes are never output by rlwrap, but used to recognize the use of the the alternate screen by the client, and
175        to filter out "garbage" that is coming from commands that use them */
176     term_smcup          = tigetstr_or_else_tgetstr("smcup",  "ti", "switch to alternate screen");
177     term_rmcup          = tigetstr_or_else_tgetstr("rmcup",  "te", "exit alternate screen");
178     term_rmkx           = tigetstr_or_else_tgetstr("rmkx",   "ke", "leave keypad-transmit mode");
179 
180     /* there is no way (yet) to determine whether a terminal knows about bracketed_paste_enabled paste - we cannot use tigetstr(). "Dumb" terminals do not, of course */
181 
182     term_enable_bracketed_paste  = strings_are_equal(term_name, "dumb") ? NULL : "\033[?2004h";
183     term_disable_bracketed_paste = strings_are_equal(term_name, "dumb") ? NULL : "\033[?2004l";
184 
185 
186     if (!term_cursor_right) /* probably only on 'dumb' terminal */
187       term_cursor_right = " ";
188 
189     /*
190     term_colors         = tigetnum("colors");
191     DPRINTF1(DEBUG_TERMIO, "terminal colors: %d", term_colors);
192     */
193 
194   }
195 
196   if (!(term_cursor_hpos || term_cursor_right) || !term_cursor_up || !term_cursor_down)
197     myerror(WARNING|NOERRNO, "Your terminal '%s' is not fully functional, expect some problems.", term_name);
198   term_eof = saved_terminal_settings.c_cc[VEOF];
199   term_stop = saved_terminal_settings.c_cc[VSTOP];
200 
201   DPRINTF1(DEBUG_TERMIO, "term_eof=%d", term_eof);
202   free(term_buf);
203 }
204 
205 
206 void
set_echo(int yes)207 set_echo(int yes)
208 {
209   struct termios *pterm = my_tcgetattr(slave_pty_sensing_fd, "slave pty");      /* mimic terminal settings of client */
210 
211   if (!pterm)                   /* child has probably died */
212     return;
213   pterm->c_lflag &= ~ICANON;    /* except a few details... */
214   pterm->c_cc[VMIN] = 1;
215   pterm->c_cc[VTIME] = 0;
216   if (yes)
217     pterm->c_lflag |= ECHO;
218   else                          /* no */
219     pterm->c_lflag &= ~ECHO;
220 
221   log_terminal_settings(pterm);
222   if (tcsetattr(STDIN_FILENO, TCSANOW, pterm) < 0 && errno != ENOTTY)
223     { /* nothing */ }   /* myerror(FATAL|USE_ERRNO, "cannot prepare terminal (tcsetattr error on stdin)"); */
224   free(pterm);
225 }
226 
227 
228 int
cursor_hpos(int col)229 cursor_hpos(int col)
230 {
231   char *instantiated;
232   if (term_cursor_hpos) {
233     instantiated = tgoto(term_cursor_hpos, 0, col); /* tgoto with a command that takes one parameter: parameter goes to 2nd arg ("vertical position"). */
234     assert(instantiated);
235     DPRINTF2(DEBUG_TERMIO, "tgoto(term_cursor_hpos, 0, %d) = %s", col, M(instantiated));
236     tputs(instantiated, 1, my_putchar);
237   } else {
238     int i;
239     cr();
240     for (i = 0; i < col; i++)
241       tputs(term_cursor_right, 1, my_putchar);
242   }
243   return TRUE;
244 }
245 
246 void
cr()247 cr()
248 {
249   if (term_cr)
250     tputs(term_cr, 1, my_putchar);
251   else
252     my_putchar('\r');
253 }
254 
255 void
clear_the_screen()256 clear_the_screen()
257 {                               /* clear_screen is a macro in term.h */
258   int i;
259 
260   if (term_clear_screen)
261     tputs(term_clear_screen, 1, my_putchar);
262   else
263     for (i = 0; i < winsize.ws_row; i++)
264       my_putchar('\n');         /* poor man's clear screen */
265 }
266 
267 void
clear_line()268 clear_line()
269 {
270   int i;
271   int width = winsize.ws_col;
272   char *p, *spaces;
273 
274   cr();
275   if (term_clear_line)
276     tputs(term_clear_line, 1, my_putchar);
277   else {
278     spaces = (char *) mymalloc(width +1);
279     for (i = 0, p = spaces; i < width; i++, p++)
280       *p = ' ';
281     *p = '\0';
282     my_putstr(spaces);  /* poor man's clear line */
283     free((void *)spaces);
284   }
285   cr();
286 }
287 
288 
289 
290 void
backspace(int count)291 backspace(int count)
292 {
293   int i;
294 
295   if (term_backspace)
296     for (i = 0; i < count; i++)
297       tputs(term_backspace, 1, my_putchar);
298   else
299     for (i = 0; i < count; i++)
300       my_putchar('\b');
301 }
302 
303 void
curs_left()304 curs_left()
305 {
306   if (term_cursor_left)
307     tputs(term_cursor_left, 1, my_putchar);
308   else
309     backspace(1);
310 }
311 
312 
313 void
curs_up()314 curs_up()
315 {
316   if (term_cursor_up)
317     tputs(term_cursor_up, 1, my_putchar);
318 }
319 
320 void
curs_down()321 curs_down()
322 {
323   if (term_cursor_down)
324     tputs(term_cursor_down, 1, my_putchar);
325 }
326 
my_putchar(TPUTS_PUTC_ARGTYPE c)327 int my_putchar(TPUTS_PUTC_ARGTYPE c)
328 {
329   char ch = c;
330   ssize_t nwritten = write(STDOUT_FILENO, &ch, 1);
331   return (nwritten == -1 ? -1 : c);
332 }
333 
334 
335 void
my_putstr(const char * string)336 my_putstr(const char *string)
337 {
338   int string_length = strlen(string);
339   DPRINTF2(DEBUG_TERMIO,"wrote %d bytes to stdout: %s", string_length, M(string));
340   if (string_length == 0)
341     return;
342   write_patiently(STDOUT_FILENO, string, string_length, "to stdout");
343   newline_came_last = (string[string_length - 1] == '\n'); /* remember whether newline came last */
344 
345 }
346 
347 
test_termfunc(char * control_string,char * control_string_name,char * start,void (* termfunc)(),char * end)348 static void test_termfunc(char *control_string, char *control_string_name, char* start, void (* termfunc)(), char *end)
349 {
350   char *mangled_control_string =
351     (control_string ?
352      add3strings("\"", mangle_string_for_debug_log(control_string,20),"\"")  :
353      mysavestring("NULL"));
354   printf("\n%s = %s\n", control_string_name, mangled_control_string);
355   if (!control_string)
356     printf("trying without suitable control string, fasten seatbelts and brace for impact... \n");
357   my_putstr(start);
358   sleep(1);
359   termfunc();
360   my_putstr(end);
361   sleep(1);
362   free(mangled_control_string);
363 }
364 
backspace1()365 static void backspace1 () { backspace(1); }
cursor_hpos4()366 static void cursor_hpos4 () { cursor_hpos(4);}
367 
368 
test_terminal()369 void test_terminal()
370 {
371   if (debug) {
372     debug_fp = fopen(DEBUG_FILENAME, "w");
373     setbuf(debug_fp, NULL);
374   }
375   init_terminal();
376   printf("\nTerminal is \"%s\"\n", term_name);
377   test_termfunc(term_backspace, "term_backspace", "This should print \"grape\": \ngras", &backspace1, "pe\n");
378   test_termfunc(term_cr, "term_cr", "This should print \"lemon juice\": \napple", &cr, "lemon juice\n");
379   test_termfunc(term_clear_line, "term_clear_line","This should print \"apple\": \npomegranate", &clear_line, "apple\n");
380   test_termfunc(term_cursor_hpos, "term_cursor_hpos", "This should print \"pomegranate\": \npomelo", &cursor_hpos4, "granate\n");
381   test_termfunc(term_cursor_up, "term_cursor_up", "This should print \"blackberry\": \n blueberry\n", &curs_up,"black\n");
382   printf("\n");
383 
384 }
385 
386 
387