ncurses 6.2 - patch 20200321
[ncurses.git] / test / filter.c
1 /****************************************************************************
2  * Copyright 2019,2020 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.34 2020/02/02 23:34:34 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 #include <test.priv.h>
46
47 #if HAVE_FILTER
48
49 #include <time.h>
50
51 static int
52 show_prompt(int underline, bool clocked)
53 {
54     int limit = COLS;
55
56     move(0, 0);
57     attrset(A_NORMAL);
58     clrtoeol();
59     attrset(A_BOLD);
60     addstr("Command: ");
61
62     limit -= getcurx(stdscr);
63
64     if (clocked) {
65         if (limit >= 3) {
66             time_t now = time((time_t *) 0);
67             struct tm *my = localtime(&now);
68             char buffer[80];
69             int skip, y, x;
70             int margin;
71
72             _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) "%02d:%02d:%02d",
73                         my->tm_hour,
74                         my->tm_min,
75                         my->tm_sec);
76
77             if (limit > 9) {
78                 skip = 0;
79             } else if (limit > 6) {
80                 skip = 3;
81             } else {
82                 skip = 6;
83             }
84             /*
85              * Write the clock message on the right-margin so we can show the
86              * results of resizing the screen.
87              */
88             getyx(stdscr, y, x);
89             margin = (int) strlen(buffer) - skip;
90             limit -= margin;
91             move(0, COLS - margin);
92             addstr(buffer);
93             move(y, x);
94         }
95     }
96     attron(underline);
97     return limit;
98 }
99
100 static int
101 new_command(char *buffer, int length, int underline, bool clocked, bool polled)
102 {
103     int code = OK;
104
105     if (polled) {
106         bool done = FALSE;
107         bool first = TRUE;
108         int y = 0, x = 0;
109         int n;
110         int mark = 0;
111         int used = 0;
112         const int gap = 2;
113
114         timeout(20);            /* no one types 50CPS... */
115         while (!done) {
116             int limit;
117             int ch = getch();
118
119             buffer[used] = '\0';
120
121             limit = show_prompt(underline, clocked);
122             if (first) {
123                 getyx(stdscr, y, x);
124                 first = FALSE;
125             } else {
126                 int left = 0;
127
128                 /*
129                  * if the screen is too narrow to show the whole buffer,
130                  * shift the editing point left/right as needed.
131                  */
132                 move(y, x);
133                 if ((used + gap) > limit) {
134                     while ((mark - left + gap) > limit) {
135                         left += limit / 2;
136                     }
137                 }
138                 printw("%.*s", limit, buffer + left);
139                 move(y, x + mark - left);
140             }
141
142             switch (ch) {
143             case ERR:
144                 continue;
145             case '\004':
146                 code = ERR;
147                 done = TRUE;
148                 break;
149             case KEY_ENTER:
150             case '\n':
151                 done = TRUE;
152                 break;
153             case KEY_BACKSPACE:
154             case '\b':
155                 if (used) {
156                     if (mark < used) {
157                         /* getnstr does not do this */
158                         if (mark > 0) {
159                             --mark;
160                             for (n = mark; n < used; ++n) {
161                                 buffer[n] = buffer[n + 1];
162                             }
163                         } else {
164                             flash();
165                         }
166                     } else {
167                         /* getnstr does this */
168                         mark = --used;
169                         buffer[used] = '\0';
170                     }
171                 } else {
172                     flash();
173                 }
174                 break;
175                 /*
176                  * Unlike getnstr, this function can move the cursor into the
177                  * middle of the buffer and insert/delete at that point.
178                  */
179             case KEY_HOME:
180                 mark = 0;
181                 break;
182             case KEY_END:
183                 mark = used;
184                 break;
185             case KEY_LEFT:
186                 if (mark > 0) {
187                     mark--;
188                 } else {
189                     flash();
190                 }
191                 break;
192             case KEY_RIGHT:
193                 if (mark < used) {
194                     mark++;
195                 } else {
196                     flash();
197                 }
198                 break;
199 #ifdef KEY_EVENT
200             case KEY_EVENT:
201                 continue;
202 #endif
203 #ifdef KEY_RESIZE
204             case KEY_RESIZE:
205                 /*
206                  * Unlike getnstr, this function "knows" what the whole screen
207                  * is supposed to look like, and can handle resize events.
208                  */
209                 continue;
210 #endif
211             case '\t':
212                 ch = ' ';
213                 /* FALLTHRU */
214             default:
215                 if (ch >= KEY_MIN) {
216                     flash();
217                     continue;
218                 }
219                 if (mark < used) {
220                     /* getnstr does not do this... */
221                     for (n = used + 1; n > mark; --n) {
222                         buffer[n] = buffer[n - 1];
223                     }
224                     buffer[mark] = (char) ch;
225                     used++;
226                     mark++;
227                 } else {
228                     /* getnstr does this part */
229                     buffer[used] = (char) ch;
230                     mark = ++used;
231                 }
232                 break;
233             }
234         }
235     } else {
236         show_prompt(underline, clocked);
237
238         code = getnstr(buffer, length);
239         /*
240          * If this returns anything except ERR/OK, it would be one of ncurses's
241          * extensions.  Fill the buffer with something harmless that the shell
242          * will execute as a comment.
243          */
244 #ifdef KEY_EVENT
245         if (code == KEY_EVENT)
246             _nc_STRCPY(buffer, "# event!", length);
247 #endif
248 #ifdef KEY_RESIZE
249         if (code == KEY_RESIZE) {
250             _nc_STRCPY(buffer, "# resize!", length);
251             getch();
252         }
253 #endif
254     }
255     attroff(underline);
256     attroff(A_BOLD);
257     refresh();
258
259     return code;
260 }
261
262 #ifdef NCURSES_VERSION
263 /*
264  * Cancel xterm's alternate-screen mode (from dialog -TD)
265  */
266 #define isprivate(s) ((s) != 0 && strstr(s, "\033[?") != 0)
267 static void
268 cancel_altscreen(void)
269 {
270     if (isatty(fileno(stdout))
271         && key_mouse != 0       /* xterm and kindred */
272         && isprivate(enter_ca_mode)
273         && isprivate(exit_ca_mode)) {
274         /*
275          * initscr() or newterm() already wrote enter_ca_mode as a side effect
276          * of initializing the screen.  It would be nice to not even do that,
277          * but we do not really have access to the correct copy of the
278          * terminfo description until those functions have been invoked.
279          */
280         (void) refresh();
281         (void) putp(exit_ca_mode);
282         (void) fflush(stdout);
283         /*
284          * Prevent ncurses from switching "back" to the normal screen when
285          * exiting from this program.  That would move the cursor to the
286          * original location saved in xterm.  Normally curses sets the cursor
287          * position to the first line after the display, but the alternate
288          * screen switching is done after that point.
289          *
290          * Cancelling the strings altogether also works around the buggy
291          * implementation of alternate-screen in rxvt, etc., which clear more
292          * of the display than they should.
293          */
294         enter_ca_mode = 0;
295         exit_ca_mode = 0;
296     }
297 }
298 #endif
299
300 static void
301 usage(void)
302 {
303     static const char *msg[] =
304     {
305         "Usage: filter [options]"
306         ,""
307         ,"Options:"
308 #ifdef NCURSES_VERSION
309         ,"  -a   suppress xterm alternate-screen by amending smcup/rmcup"
310 #endif
311         ,"  -c   show current time on prompt line with \"Command\""
312 #if HAVE_USE_DEFAULT_COLORS
313         ,"  -d   invoke use_default_colors"
314 #endif
315         ,"  -i   use initscr() rather than newterm()"
316         ,"  -p   poll for individual characters rather than using getnstr"
317     };
318     unsigned n;
319     for (n = 0; n < SIZEOF(msg); n++)
320         fprintf(stderr, "%s\n", msg[n]);
321     ExitProgram(EXIT_FAILURE);
322 }
323
324 int
325 main(int argc, char *argv[])
326 {
327     int ch;
328     char buffer[80];
329     int underline;
330 #ifdef NCURSES_VERSION
331     bool a_option = FALSE;
332 #endif
333     bool c_option = FALSE;
334 #if HAVE_USE_DEFAULT_COLORS
335     bool d_option = FALSE;
336 #endif
337     bool i_option = FALSE;
338     bool p_option = FALSE;
339
340     setlocale(LC_ALL, "");
341
342     while ((ch = getopt(argc, argv, "adcip")) != -1) {
343         switch (ch) {
344 #ifdef NCURSES_VERSION
345         case 'a':
346             a_option = TRUE;
347             break;
348 #endif
349         case 'c':
350             c_option = TRUE;
351             break;
352 #if HAVE_USE_DEFAULT_COLORS
353         case 'd':
354             d_option = TRUE;
355             break;
356 #endif
357         case 'i':
358             i_option = TRUE;
359             break;
360         case 'p':
361             p_option = TRUE;
362             break;
363         default:
364             usage();
365         }
366     }
367
368     printf("starting filter program using %s...\n",
369            i_option ? "initscr" : "newterm");
370     filter();
371     if (i_option) {
372         initscr();
373     } else {
374         if (newterm((char *) 0, stdout, stdin) == 0) {
375             fprintf(stderr, "cannot initialize terminal\n");
376             ExitProgram(EXIT_FAILURE);
377         }
378     }
379 #ifdef NCURSES_VERSION
380     if (a_option) {
381         cancel_altscreen();
382     }
383 #endif
384     cbreak();
385     keypad(stdscr, TRUE);
386
387     if (has_colors()) {
388         int background = COLOR_BLACK;
389         start_color();
390 #if HAVE_USE_DEFAULT_COLORS
391         if (d_option && (use_default_colors() != ERR))
392             background = -1;
393 #endif
394         init_pair(1, COLOR_CYAN, (short) background);
395         underline = COLOR_PAIR(1);
396     } else {
397         underline = A_UNDERLINE;
398     }
399
400     for (;;) {
401         int code = new_command(buffer, sizeof(buffer) - 1,
402                                underline, c_option, p_option);
403         if (code == ERR || *buffer == '\0')
404             break;
405         reset_shell_mode();
406         printf("\n");
407         fflush(stdout);
408         IGNORE_RC(system(buffer));
409         reset_prog_mode();
410         touchwin(stdscr);
411         erase();
412         refresh();
413     }
414     clear();
415     refresh();
416     endwin();
417     ExitProgram(EXIT_SUCCESS);
418 }
419 #else
420 int
421 main(void)
422 {
423     printf("This program requires the filter function\n");
424     ExitProgram(EXIT_FAILURE);
425 }
426 #endif /* HAVE_FILTER */