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