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