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