• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /****************************************************************************
2  * Copyright 2019-2020,2022 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 
30 /*
31  * Author:  Thomas E. Dickey 1998
32  *
33  * $Id: filter.c,v 1.38 2022/12/04 00:40:11 tom Exp $
34  *
35  * An example of the 'filter()' function in ncurses, this program prompts
36  * for commands and executes them (like a command shell).  It illustrates
37  * how ncurses can be used to implement programs that are not full-screen.
38  *
39  * Ncurses differs slightly from SVr4 curses.  The latter does not flush its
40  * state when exiting program mode, so the attributes on the command lines of
41  * this program 'bleed' onto the executed commands.  Rather than use the
42  * reset_shell_mode() and reset_prog_mode() functions, we could invoke endwin()
43  * and refresh(), but that does not work any better.
44  */
45 #define NEED_KEY_EVENT
46 #include <test.priv.h>
47 
48 #if HAVE_FILTER
49 
50 #include <time.h>
51 
52 static int
show_prompt(int underline,bool clocked)53 show_prompt(int underline, bool clocked)
54 {
55     int limit = COLS;
56 
57     move(0, 0);
58     attrset(A_NORMAL);
59     clrtoeol();
60     attrset(A_BOLD);
61     addstr("Command: ");
62 
63     limit -= getcurx(stdscr);
64 
65     if (clocked) {
66 	if (limit >= 3) {
67 	    time_t now = time((time_t *) 0);
68 	    struct tm *my = localtime(&now);
69 	    char buffer[80];
70 	    int skip, y, x;
71 	    int margin;
72 
73 	    _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "%02d:%02d:%02d",
74 			my->tm_hour,
75 			my->tm_min,
76 			my->tm_sec);
77 
78 	    if (limit > 9) {
79 		skip = 0;
80 	    } else if (limit > 6) {
81 		skip = 3;
82 	    } else {
83 		skip = 6;
84 	    }
85 	    /*
86 	     * Write the clock message on the right-margin so we can show the
87 	     * results of resizing the screen.
88 	     */
89 	    getyx(stdscr, y, x);
90 	    margin = (int) strlen(buffer) - skip;
91 	    limit -= margin;
92 	    move(0, COLS - margin);
93 	    addstr(buffer);
94 	    move(y, x);
95 	}
96     }
97     attron(underline);
98     return limit;
99 }
100 
101 static int
new_command(char * buffer,int length,int underline,bool clocked,bool polled)102 new_command(char *buffer, int length, int underline, bool clocked, bool polled)
103 {
104     int code = OK;
105 
106     if (polled) {
107 	bool done = FALSE;
108 	bool first = TRUE;
109 	int y = 0, x = 0;
110 	int n;
111 	int mark = 0;
112 	int used = 0;
113 	const int gap = 2;
114 
115 	timeout(20);		/* no one types 50CPS... */
116 	while (!done) {
117 	    int limit;
118 	    int ch = getch();
119 
120 	    buffer[used] = '\0';
121 
122 	    limit = show_prompt(underline, clocked);
123 	    if (first) {
124 		getyx(stdscr, y, x);
125 		first = FALSE;
126 	    } else {
127 		int left = 0;
128 
129 		/*
130 		 * if the screen is too narrow to show the whole buffer,
131 		 * shift the editing point left/right as needed.
132 		 */
133 		move(y, x);
134 		if ((used + gap) > limit) {
135 		    while ((mark - left + gap) > limit) {
136 			left += limit / 2;
137 		    }
138 		}
139 		printw("%.*s", limit, buffer + left);
140 		move(y, x + mark - left);
141 	    }
142 
143 	    switch (ch) {
144 	    case ERR:
145 		continue;
146 	    case '\004':
147 		code = ERR;
148 		done = TRUE;
149 		break;
150 	    case KEY_ENTER:
151 	    case '\n':
152 		done = TRUE;
153 		break;
154 	    case KEY_BACKSPACE:
155 	    case '\b':
156 		if (used) {
157 		    if (mark < used) {
158 			/* getnstr does not do this */
159 			if (mark > 0) {
160 			    --mark;
161 			    for (n = mark; n < used; ++n) {
162 				buffer[n] = buffer[n + 1];
163 			    }
164 			} else {
165 			    flash();
166 			}
167 		    } else {
168 			/* getnstr does this */
169 			mark = --used;
170 			buffer[used] = '\0';
171 		    }
172 		} else {
173 		    flash();
174 		}
175 		break;
176 		/*
177 		 * Unlike getnstr, this function can move the cursor into the
178 		 * middle of the buffer and insert/delete at that point.
179 		 */
180 	    case KEY_HOME:
181 		mark = 0;
182 		break;
183 	    case KEY_END:
184 		mark = used;
185 		break;
186 	    case KEY_LEFT:
187 		if (mark > 0) {
188 		    mark--;
189 		} else {
190 		    flash();
191 		}
192 		break;
193 	    case KEY_RIGHT:
194 		if (mark < used) {
195 		    mark++;
196 		} else {
197 		    flash();
198 		}
199 		break;
200 #ifdef KEY_EVENT
201 	    case KEY_EVENT:
202 		continue;
203 #endif
204 #ifdef KEY_RESIZE
205 	    case KEY_RESIZE:
206 		/*
207 		 * Unlike getnstr, this function "knows" what the whole screen
208 		 * is supposed to look like, and can handle resize events.
209 		 */
210 		continue;
211 #endif
212 	    case '\t':
213 		ch = ' ';
214 		/* FALLTHRU */
215 	    default:
216 		if (ch >= KEY_MIN) {
217 		    flash();
218 		    continue;
219 		}
220 		if (mark < used) {
221 		    /* getnstr does not do this... */
222 		    for (n = used + 1; n > mark; --n) {
223 			buffer[n] = buffer[n - 1];
224 		    }
225 		    buffer[mark] = (char) ch;
226 		    used++;
227 		    mark++;
228 		} else {
229 		    /* getnstr does this part */
230 		    buffer[used] = (char) ch;
231 		    mark = ++used;
232 		}
233 		break;
234 	    }
235 	}
236     } else {
237 	show_prompt(underline, clocked);
238 
239 	code = getnstr(buffer, length);
240 	/*
241 	 * If this returns anything except ERR/OK, it would be one of ncurses's
242 	 * extensions.  Fill the buffer with something harmless that the shell
243 	 * will execute as a comment.
244 	 */
245 #ifdef KEY_EVENT
246 	if (code == KEY_EVENT)
247 	    _nc_STRCPY(buffer, "# event!", length);
248 #endif
249 #ifdef KEY_RESIZE
250 	if (code == KEY_RESIZE) {
251 	    _nc_STRCPY(buffer, "# resize!", length);
252 	    getch();
253 	}
254 #endif
255     }
256     attroff(underline);
257     attroff(A_BOLD);
258     refresh();
259 
260     return code;
261 }
262 
263 #ifdef NCURSES_VERSION
264 /*
265  * Cancel xterm's alternate-screen mode (from dialog -TD)
266  */
267 #define isprivate(s) ((s) != 0 && strstr(s, "\033[?") != 0)
268 static void
cancel_altscreen(void)269 cancel_altscreen(void)
270 {
271     if (isatty(fileno(stdout))
272 	&& key_mouse != 0	/* xterm and kindred */
273 	&& isprivate(enter_ca_mode)
274 	&& isprivate(exit_ca_mode)) {
275 	/*
276 	 * initscr() or newterm() already wrote enter_ca_mode as a side effect
277 	 * of initializing the screen.  It would be nice to not even do that,
278 	 * but we do not really have access to the correct copy of the
279 	 * terminfo description until those functions have been invoked.
280 	 */
281 	(void) refresh();
282 	(void) putp(exit_ca_mode);
283 	(void) fflush(stdout);
284 	/*
285 	 * Prevent ncurses from switching "back" to the normal screen when
286 	 * exiting from this program.  That would move the cursor to the
287 	 * original location saved in xterm.  Normally curses sets the cursor
288 	 * position to the first line after the display, but the alternate
289 	 * screen switching is done after that point.
290 	 *
291 	 * Cancelling the strings altogether also works around the buggy
292 	 * implementation of alternate-screen in rxvt, etc., which clear more
293 	 * of the display than they should.
294 	 */
295 	enter_ca_mode = 0;
296 	exit_ca_mode = 0;
297     }
298 }
299 #endif
300 
301 static void
usage(int ok)302 usage(int ok)
303 {
304     static const char *msg[] =
305     {
306 	"Usage: filter [options]"
307 	,""
308 	,USAGE_COMMON
309 	,"Options:"
310 #ifdef NCURSES_VERSION
311 	," -a       suppress xterm alternate-screen by amending smcup/rmcup"
312 #endif
313 	," -c       show current time on prompt line with \"Command\""
314 #if HAVE_USE_DEFAULT_COLORS
315 	," -d       invoke use_default_colors"
316 #endif
317 	," -i       use initscr() rather than newterm()"
318 	," -p       poll for individual characters rather than using getnstr"
319     };
320     unsigned n;
321     for (n = 0; n < SIZEOF(msg); n++)
322 	fprintf(stderr, "%s\n", msg[n]);
323     ExitProgram(ok ? EXIT_SUCCESS : EXIT_FAILURE);
324 }
325 /* *INDENT-OFF* */
VERSION_COMMON()326 VERSION_COMMON()
327 /* *INDENT-ON* */
328 
329 int
330 main(int argc, char *argv[])
331 {
332     int ch;
333     char buffer[80];
334     int underline;
335 #ifdef NCURSES_VERSION
336     bool a_option = FALSE;
337 #endif
338     bool c_option = FALSE;
339 #if HAVE_USE_DEFAULT_COLORS
340     bool d_option = FALSE;
341 #endif
342     bool i_option = FALSE;
343     bool p_option = FALSE;
344 
345     setlocale(LC_ALL, "");
346 
347     while ((ch = getopt(argc, argv, OPTS_COMMON "adcip")) != -1) {
348 	switch (ch) {
349 #ifdef NCURSES_VERSION
350 	case 'a':
351 	    a_option = TRUE;
352 	    break;
353 #endif
354 	case 'c':
355 	    c_option = TRUE;
356 	    break;
357 #if HAVE_USE_DEFAULT_COLORS
358 	case 'd':
359 	    d_option = TRUE;
360 	    break;
361 #endif
362 	case 'i':
363 	    i_option = TRUE;
364 	    break;
365 	case 'p':
366 	    p_option = TRUE;
367 	    break;
368 	case OPTS_VERSION:
369 	    show_version(argv);
370 	    ExitProgram(EXIT_SUCCESS);
371 	default:
372 	    usage(ch == OPTS_USAGE);
373 	    /* NOTREACHED */
374 	}
375     }
376 
377     printf("starting filter program using %s...\n",
378 	   i_option ? "initscr" : "newterm");
379     filter();
380     if (i_option) {
381 	initscr();
382     } else {
383 	if (newterm((char *) 0, stdout, stdin) == 0) {
384 	    fprintf(stderr, "cannot initialize terminal\n");
385 	    ExitProgram(EXIT_FAILURE);
386 	}
387     }
388 #ifdef NCURSES_VERSION
389     if (a_option) {
390 	cancel_altscreen();
391     }
392 #endif
393     cbreak();
394     keypad(stdscr, TRUE);
395 
396     if (has_colors()) {
397 	int background = COLOR_BLACK;
398 	start_color();
399 #if HAVE_USE_DEFAULT_COLORS
400 	if (d_option && (use_default_colors() != ERR))
401 	    background = -1;
402 #endif
403 	init_pair(1, COLOR_CYAN, (short) background);
404 	underline = COLOR_PAIR(1);
405     } else {
406 	underline = A_UNDERLINE;
407     }
408 
409     for (;;) {
410 	int code = new_command(buffer, sizeof(buffer) - 1,
411 			       underline, c_option, p_option);
412 	if (code == ERR || *buffer == '\0')
413 	    break;
414 	reset_shell_mode();
415 	printf("\n");
416 	fflush(stdout);
417 	IGNORE_RC(system(buffer));
418 	reset_prog_mode();
419 	touchwin(stdscr);
420 	erase();
421 	refresh();
422     }
423     clear();
424     refresh();
425     endwin();
426     ExitProgram(EXIT_SUCCESS);
427 }
428 #else
429 int
main(void)430 main(void)
431 {
432     printf("This program requires the filter function\n");
433     ExitProgram(EXIT_FAILURE);
434 }
435 #endif /* HAVE_FILTER */
436