6cd9206b274d44b1d6646e839c6cba1dabfa52d0
[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.35 2020/07/25 22:40:57 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
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
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
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
302 usage(void)
303 {
304     static const char *msg[] =
305     {
306         "Usage: filter [options]"
307         ,""
308         ,"Options:"
309 #ifdef NCURSES_VERSION
310         ,"  -a   suppress xterm alternate-screen by amending smcup/rmcup"
311 #endif
312         ,"  -c   show current time on prompt line with \"Command\""
313 #if HAVE_USE_DEFAULT_COLORS
314         ,"  -d   invoke use_default_colors"
315 #endif
316         ,"  -i   use initscr() rather than newterm()"
317         ,"  -p   poll for individual characters rather than using getnstr"
318     };
319     unsigned n;
320     for (n = 0; n < SIZEOF(msg); n++)
321         fprintf(stderr, "%s\n", msg[n]);
322     ExitProgram(EXIT_FAILURE);
323 }
324
325 int
326 main(int argc, char *argv[])
327 {
328     int ch;
329     char buffer[80];
330     int underline;
331 #ifdef NCURSES_VERSION
332     bool a_option = FALSE;
333 #endif
334     bool c_option = FALSE;
335 #if HAVE_USE_DEFAULT_COLORS
336     bool d_option = FALSE;
337 #endif
338     bool i_option = FALSE;
339     bool p_option = FALSE;
340
341     setlocale(LC_ALL, "");
342
343     while ((ch = getopt(argc, argv, "adcip")) != -1) {
344         switch (ch) {
345 #ifdef NCURSES_VERSION
346         case 'a':
347             a_option = TRUE;
348             break;
349 #endif
350         case 'c':
351             c_option = TRUE;
352             break;
353 #if HAVE_USE_DEFAULT_COLORS
354         case 'd':
355             d_option = TRUE;
356             break;
357 #endif
358         case 'i':
359             i_option = TRUE;
360             break;
361         case 'p':
362             p_option = TRUE;
363             break;
364         default:
365             usage();
366         }
367     }
368
369     printf("starting filter program using %s...\n",
370            i_option ? "initscr" : "newterm");
371     filter();
372     if (i_option) {
373         initscr();
374     } else {
375         if (newterm((char *) 0, stdout, stdin) == 0) {
376             fprintf(stderr, "cannot initialize terminal\n");
377             ExitProgram(EXIT_FAILURE);
378         }
379     }
380 #ifdef NCURSES_VERSION
381     if (a_option) {
382         cancel_altscreen();
383     }
384 #endif
385     cbreak();
386     keypad(stdscr, TRUE);
387
388     if (has_colors()) {
389         int background = COLOR_BLACK;
390         start_color();
391 #if HAVE_USE_DEFAULT_COLORS
392         if (d_option && (use_default_colors() != ERR))
393             background = -1;
394 #endif
395         init_pair(1, COLOR_CYAN, (short) background);
396         underline = COLOR_PAIR(1);
397     } else {
398         underline = A_UNDERLINE;
399     }
400
401     for (;;) {
402         int code = new_command(buffer, sizeof(buffer) - 1,
403                                underline, c_option, p_option);
404         if (code == ERR || *buffer == '\0')
405             break;
406         reset_shell_mode();
407         printf("\n");
408         fflush(stdout);
409         IGNORE_RC(system(buffer));
410         reset_prog_mode();
411         touchwin(stdscr);
412         erase();
413         refresh();
414     }
415     clear();
416     refresh();
417     endwin();
418     ExitProgram(EXIT_SUCCESS);
419 }
420 #else
421 int
422 main(void)
423 {
424     printf("This program requires the filter function\n");
425     ExitProgram(EXIT_FAILURE);
426 }
427 #endif /* HAVE_FILTER */