xref: /freebsd/contrib/dialog/ui_getc.c (revision c697fb7f)
1 /*
2  *  $Id: ui_getc.c,v 1.70 2018/06/14 00:05:05 tom Exp $
3  *
4  *  ui_getc.c - user interface glue for getc()
5  *
6  *  Copyright 2001-2013,2018	Thomas E. Dickey
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License, version 2.1
10  *  as published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this program; if not, write to
19  *	Free Software Foundation, Inc.
20  *	51 Franklin St., Fifth Floor
21  *	Boston, MA 02110, USA.
22  */
23 
24 #include <dialog.h>
25 #include <dlg_keys.h>
26 
27 #ifdef NEED_WCHAR_H
28 #include <wchar.h>
29 #endif
30 
31 #if TIME_WITH_SYS_TIME
32 # include <sys/time.h>
33 # include <time.h>
34 #else
35 # if HAVE_SYS_TIME_H
36 #  include <sys/time.h>
37 # else
38 #  include <time.h>
39 # endif
40 #endif
41 
42 #ifdef HAVE_SYS_WAIT_H
43 #include <sys/wait.h>
44 #endif
45 
46 #ifdef __QNX__
47 #include <sys/select.h>
48 #endif
49 
50 #ifndef WEXITSTATUS
51 # ifdef HAVE_TYPE_UNIONWAIT
52 #  define	WEXITSTATUS(status)	(status.w_retcode)
53 # else
54 #  define	WEXITSTATUS(status)	(((status) & 0xff00) >> 8)
55 # endif
56 #endif
57 
58 #ifndef WTERMSIG
59 # ifdef HAVE_TYPE_UNIONWAIT
60 #  define	WTERMSIG(status)	(status.w_termsig)
61 # else
62 #  define	WTERMSIG(status)	((status) & 0x7f)
63 # endif
64 #endif
65 
66 void
67 dlg_add_callback(DIALOG_CALLBACK * p)
68 {
69     p->next = dialog_state.getc_callbacks;
70     dialog_state.getc_callbacks = p;
71     wtimeout(p->win, WTIMEOUT_VAL);
72 }
73 
74 /*
75  * Like dlg_add_callback(), but providing for cleanup of caller's associated
76  * state.
77  */
78 void
79 dlg_add_callback_ref(DIALOG_CALLBACK ** p, DIALOG_FREEBACK freeback)
80 {
81     (*p)->caller = p;
82     (*p)->freeback = freeback;
83     dlg_add_callback(*p);
84 }
85 
86 void
87 dlg_remove_callback(DIALOG_CALLBACK * p)
88 {
89     DIALOG_CALLBACK *q;
90 
91     if (p->input != 0) {
92 	FILE *input = p->input;
93 	fclose(input);
94 	if (p->input == dialog_state.pipe_input)
95 	    dialog_state.pipe_input = 0;
96 	/* more than one callback can have the same input */
97 	for (q = dialog_state.getc_callbacks; q != 0; q = q->next) {
98 	    if (q->input == input) {
99 		q->input = 0;
100 	    }
101 	}
102     }
103 
104     if (!(p->keep_win))
105 	dlg_del_window(p->win);
106     if ((q = dialog_state.getc_callbacks) == p) {
107 	dialog_state.getc_callbacks = p->next;
108     } else {
109 	while (q != 0) {
110 	    if (q->next == p) {
111 		q->next = p->next;
112 		break;
113 	    }
114 	    q = q->next;
115 	}
116     }
117 
118     /* handle dlg_add_callback_ref cleanup */
119     if (p->freeback != 0)
120 	p->freeback(p);
121     if (p->caller != 0)
122 	*(p->caller) = 0;
123 
124     free(p);
125 }
126 
127 /*
128  * A select() might find more than one input ready for service.  Handle them
129  * all.
130  */
131 static bool
132 handle_inputs(WINDOW *win)
133 {
134     bool result = FALSE;
135     DIALOG_CALLBACK *p;
136     DIALOG_CALLBACK *q;
137     int cur_y, cur_x;
138     int state = ERR;
139 
140     getyx(win, cur_y, cur_x);
141     for (p = dialog_state.getc_callbacks, q = 0; p != 0; p = q) {
142 	q = p->next;
143 	if ((p->handle_input != 0) && p->input_ready) {
144 	    p->input_ready = FALSE;
145 	    if (state == ERR) {
146 		state = curs_set(0);
147 	    }
148 	    if (p->handle_input(p)) {
149 		result = TRUE;
150 	    }
151 	}
152     }
153     if (result) {
154 	(void) wmove(win, cur_y, cur_x);	/* Restore cursor position */
155 	wrefresh(win);
156     }
157     if (state != ERR)
158 	curs_set(state);
159     return result;
160 }
161 
162 static bool
163 may_handle_inputs(void)
164 {
165     bool result = FALSE;
166 
167     DIALOG_CALLBACK *p;
168 
169     for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
170 	if (p->input != 0) {
171 	    result = TRUE;
172 	    break;
173 	}
174     }
175 
176     return result;
177 }
178 
179 /*
180  * Check any any inputs registered via callbacks, to see if there is any input
181  * available.  If there is, return a file-descriptor which should be read.
182  * Otherwise, return -1.
183  */
184 static int
185 check_inputs(void)
186 {
187     DIALOG_CALLBACK *p;
188     fd_set read_fds;
189     struct timeval test;
190     int last_fd = -1;
191     int fd;
192     int found;
193     int result = -1;
194 
195     if ((p = dialog_state.getc_callbacks) != 0) {
196 	FD_ZERO(&read_fds);
197 
198 	while (p != 0) {
199 	    p->input_ready = FALSE;
200 	    if (p->input != 0 && (fd = fileno(p->input)) >= 0) {
201 		FD_SET(fd, &read_fds);
202 		if (last_fd < fd)
203 		    last_fd = fd;
204 	    }
205 	    p = p->next;
206 	}
207 
208 	test.tv_sec = 0;
209 	test.tv_usec = WTIMEOUT_VAL * 1000;
210 	found = select(last_fd + 1, &read_fds,
211 		       (fd_set *) 0,
212 		       (fd_set *) 0,
213 		       &test);
214 
215 	if (found > 0) {
216 	    for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
217 		if (p->input != 0
218 		    && (fd = fileno(p->input)) >= 0
219 		    && FD_ISSET(fd, &read_fds)) {
220 		    p->input_ready = TRUE;
221 		    result = fd;
222 		}
223 	    }
224 	}
225     }
226 
227     return result;
228 }
229 
230 int
231 dlg_getc_callbacks(int ch, int fkey, int *result)
232 {
233     int code = FALSE;
234     DIALOG_CALLBACK *p, *q;
235 
236     if ((p = dialog_state.getc_callbacks) != 0) {
237 	if (check_inputs() >= 0) {
238 	    do {
239 		q = p->next;
240 		if (p->input_ready) {
241 		    if (!(p->handle_getc(p, ch, fkey, result))) {
242 			dlg_remove_callback(p);
243 		    }
244 		}
245 	    } while ((p = q) != 0);
246 	}
247 	code = (dialog_state.getc_callbacks != 0);
248     }
249     return code;
250 }
251 
252 static void
253 dlg_raise_window(WINDOW *win)
254 {
255     touchwin(win);
256     wmove(win, getcury(win), getcurx(win));
257     wnoutrefresh(win);
258     doupdate();
259 }
260 
261 /*
262  * This is a work-around for the case where we actually need the wide-character
263  * code versus a byte stream.
264  */
265 static int last_getc = ERR;
266 
267 #ifdef USE_WIDE_CURSES
268 static char last_getc_bytes[80];
269 static int have_last_getc;
270 static int used_last_getc;
271 #endif
272 
273 int
274 dlg_last_getc(void)
275 {
276 #ifdef USE_WIDE_CURSES
277     if (used_last_getc != 1)
278 	return ERR;		/* not really an error... */
279 #endif
280     return last_getc;
281 }
282 
283 void
284 dlg_flush_getc(void)
285 {
286     last_getc = ERR;
287 #ifdef USE_WIDE_CURSES
288     have_last_getc = 0;
289     used_last_getc = 0;
290 #endif
291 }
292 
293 /*
294  * Report the last key entered by the user.  The 'mode' parameter controls
295  * the way it is separated from other results:
296  * -2 (no separator)
297  * -1 (separator after the key name)
298  * 0 (separator is optionally before the key name)
299  * 1 (same as -1)
300  */
301 void
302 dlg_add_last_key(int mode)
303 {
304     if (dialog_vars.last_key) {
305 	if (mode >= 0) {
306 	    if (mode > 0) {
307 		dlg_add_last_key(-1);
308 	    } else {
309 		if (dlg_need_separator())
310 		    dlg_add_separator();
311 		dlg_add_last_key(-2);
312 	    }
313 	} else {
314 	    char temp[80];
315 	    sprintf(temp, "%d", last_getc);
316 	    dlg_add_string(temp);
317 	    if (mode == -1)
318 		dlg_add_separator();
319 	}
320     }
321 }
322 
323 /*
324  * Check if the stream has been unexpectedly closed, returning false in that
325  * case.
326  */
327 static bool
328 valid_file(FILE *fp)
329 {
330     bool code = FALSE;
331     int fd = fileno(fp);
332 
333     if (fd >= 0) {
334 	if (fcntl(fd, F_GETFL, 0) >= 0) {
335 	    code = TRUE;
336 	}
337     }
338     return code;
339 }
340 
341 static int
342 really_getch(WINDOW *win, int *fkey)
343 {
344     int ch;
345 #ifdef USE_WIDE_CURSES
346     int code;
347     mbstate_t state;
348     wchar_t my_wchar;
349     wint_t my_wint;
350 
351     /*
352      * We get a wide character, translate it to multibyte form to avoid
353      * having to change the rest of the code to use wide-characters.
354      */
355     if (used_last_getc >= have_last_getc) {
356 	used_last_getc = 0;
357 	have_last_getc = 0;
358 	ch = ERR;
359 	*fkey = 0;
360 	code = wget_wch(win, &my_wint);
361 	my_wchar = (wchar_t) my_wint;
362 	switch (code) {
363 	case KEY_CODE_YES:
364 	    ch = *fkey = my_wchar;
365 	    last_getc = my_wchar;
366 	    break;
367 	case OK:
368 	    memset(&state, 0, sizeof(state));
369 	    have_last_getc = (int) wcrtomb(last_getc_bytes, my_wchar, &state);
370 	    if (have_last_getc < 0) {
371 		have_last_getc = used_last_getc = 0;
372 		last_getc_bytes[0] = (char) my_wchar;
373 	    }
374 	    ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
375 	    last_getc = my_wchar;
376 	    break;
377 	case ERR:
378 	    ch = ERR;
379 	    last_getc = ERR;
380 	    break;
381 	default:
382 	    break;
383 	}
384     } else {
385 	ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
386     }
387 #else
388     ch = wgetch(win);
389     last_getc = ch;
390     *fkey = (ch > KEY_MIN && ch < KEY_MAX);
391 #endif
392     return ch;
393 }
394 
395 static DIALOG_CALLBACK *
396 next_callback(DIALOG_CALLBACK * p)
397 {
398     if ((p = dialog_state.getc_redirect) != 0) {
399 	p = p->next;
400     } else {
401 	p = dialog_state.getc_callbacks;
402     }
403     return p;
404 }
405 
406 static DIALOG_CALLBACK *
407 prev_callback(DIALOG_CALLBACK * p)
408 {
409     DIALOG_CALLBACK *q;
410 
411     if ((p = dialog_state.getc_redirect) != 0) {
412 	if (p == dialog_state.getc_callbacks) {
413 	    for (p = dialog_state.getc_callbacks; p->next != 0; p = p->next) ;
414 	} else {
415 	    for (q = dialog_state.getc_callbacks; q->next != p; q = q->next) ;
416 	    p = q;
417 	}
418     } else {
419 	p = dialog_state.getc_callbacks;
420     }
421     return p;
422 }
423 
424 #define isBeforeChr(chr) ((chr) == before_chr && !before_fkey)
425 #define isBeforeFkey(chr) ((chr) == before_chr && before_fkey)
426 
427 /*
428  * Read a character from the given window.  Handle repainting here (to simplify
429  * things in the calling application).  Also, if input-callback(s) are set up,
430  * poll the corresponding files and handle the updates, e.g., for displaying a
431  * tailbox.
432  */
433 int
434 dlg_getc(WINDOW *win, int *fkey)
435 {
436     WINDOW *save_win = win;
437     int ch = ERR;
438     int before_chr;
439     int before_fkey;
440     int result;
441     bool done = FALSE;
442     bool literal = FALSE;
443     DIALOG_CALLBACK *p = 0;
444     int interval = (dialog_vars.timeout_secs * 1000);
445     time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs;
446     time_t current;
447 
448     if (may_handle_inputs())
449 	wtimeout(win, WTIMEOUT_VAL);
450     else if (interval > 0)
451 	wtimeout(win, interval);
452 
453     while (!done) {
454 	bool handle_others = FALSE;
455 
456 	/*
457 	 * If there was no pending file-input, check the keyboard.
458 	 */
459 	ch = really_getch(win, fkey);
460 	if (literal) {
461 	    done = TRUE;
462 	    continue;
463 	}
464 
465 	before_chr = ch;
466 	before_fkey = *fkey;
467 
468 	ch = dlg_lookup_key(win, ch, fkey);
469 	dlg_trace_chr(ch, *fkey);
470 
471 	current = time((time_t *) 0);
472 
473 	/*
474 	 * If we acquired a fkey value, then it is one of dialog's builtin
475 	 * codes such as DLGK_HELPFILE.
476 	 */
477 	if (!*fkey || *fkey != before_fkey) {
478 	    switch (ch) {
479 	    case CHR_LITERAL:
480 		literal = TRUE;
481 		keypad(win, FALSE);
482 		continue;
483 	    case CHR_REPAINT:
484 		(void) touchwin(win);
485 		(void) wrefresh(curscr);
486 		break;
487 	    case ERR:		/* wtimeout() in effect; check for file I/O */
488 		if (interval > 0
489 		    && current >= expired) {
490 		    DLG_TRACE(("# dlg_getc: timeout expired\n"));
491 		    ch = ESC;
492 		    done = TRUE;
493 		} else if (!valid_file(stdin)
494 			   || !valid_file(dialog_state.screen_output)) {
495 		    DLG_TRACE(("# dlg_getc: input or output is invalid\n"));
496 		    ch = ESC;
497 		    done = TRUE;
498 		} else if (check_inputs()) {
499 		    if (handle_inputs(win))
500 			dlg_raise_window(win);
501 		    else
502 			done = TRUE;
503 		} else {
504 		    done = (interval <= 0);
505 		}
506 		break;
507 	    case DLGK_HELPFILE:
508 		if (dialog_vars.help_file) {
509 		    int yold, xold;
510 		    getyx(win, yold, xold);
511 		    dialog_helpfile("HELP", dialog_vars.help_file, 0, 0);
512 		    dlg_raise_window(win);
513 		    wmove(win, yold, xold);
514 		}
515 		continue;
516 	    case DLGK_FIELD_PREV:
517 		/* FALLTHRU */
518 	    case KEY_BTAB:
519 		/* FALLTHRU */
520 	    case DLGK_FIELD_NEXT:
521 		/* FALLTHRU */
522 	    case TAB:
523 		/* Handle tab/backtab as a special case for traversing between
524 		 * the nominal "current" window, and other windows having
525 		 * callbacks.  If the nominal (control) window closes, we'll
526 		 * close the windows with callbacks.
527 		 */
528 		if (dialog_state.getc_callbacks != 0 &&
529 		    (isBeforeChr(TAB) ||
530 		     isBeforeFkey(KEY_BTAB))) {
531 		    p = (isBeforeChr(TAB)
532 			 ? next_callback(p)
533 			 : prev_callback(p));
534 		    if ((dialog_state.getc_redirect = p) != 0) {
535 			win = p->win;
536 		    } else {
537 			win = save_win;
538 		    }
539 		    dlg_raise_window(win);
540 		    break;
541 		}
542 		/* FALLTHRU */
543 	    default:
544 #ifdef NO_LEAKS
545 		if (isBeforeChr(DLG_CTRL('P'))) {
546 		    /* for testing, ^P closes the connection */
547 		    close(0);
548 		    close(1);
549 		    close(2);
550 		    break;
551 		}
552 #endif
553 		handle_others = TRUE;
554 		break;
555 #ifdef HAVE_DLG_TRACE
556 	    case CHR_TRACE:
557 		dlg_trace_win(win);
558 		break;
559 #endif
560 	    }
561 	} else {
562 	    handle_others = TRUE;
563 	}
564 
565 	if (handle_others) {
566 	    if ((p = dialog_state.getc_redirect) != 0) {
567 		if (!(p->handle_getc(p, ch, *fkey, &result))) {
568 		    done = (p->win == save_win) && (!p->keep_win);
569 		    dlg_remove_callback(p);
570 		    dialog_state.getc_redirect = 0;
571 		    win = save_win;
572 		}
573 	    } else {
574 		done = TRUE;
575 	    }
576 	}
577     }
578     if (literal)
579 	keypad(win, TRUE);
580     return ch;
581 }
582 
583 static void
584 finish_bg(int sig GCC_UNUSED)
585 {
586     end_dialog();
587     dlg_exit(DLG_EXIT_ERROR);
588 }
589 
590 /*
591  * If we have callbacks active, purge the list of all that are not marked
592  * to keep in the background.  If any remain, run those in a background
593  * process.
594  */
595 void
596 dlg_killall_bg(int *retval)
597 {
598     DIALOG_CALLBACK *cb;
599     int pid;
600 #ifdef HAVE_TYPE_UNIONWAIT
601     union wait wstatus;
602 #else
603     int wstatus;
604 #endif
605 
606     if ((cb = dialog_state.getc_callbacks) != 0) {
607 	while (cb != 0) {
608 	    if (cb->keep_bg) {
609 		cb = cb->next;
610 	    } else {
611 		dlg_remove_callback(cb);
612 		cb = dialog_state.getc_callbacks;
613 	    }
614 	}
615 	if (dialog_state.getc_callbacks != 0) {
616 
617 	    refresh();
618 	    fflush(stdout);
619 	    fflush(stderr);
620 	    reset_shell_mode();
621 	    if ((pid = fork()) != 0) {
622 		_exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR);
623 	    } else if (pid == 0) {	/* child */
624 		if ((pid = fork()) != 0) {
625 		    /*
626 		     * Echo the process-id of the grandchild so a shell script
627 		     * can read that, and kill that process.  We'll wait around
628 		     * until then.  Our parent has already left, leaving us
629 		     * temporarily orphaned.
630 		     */
631 		    if (pid > 0) {	/* parent */
632 			fprintf(stderr, "%d\n", pid);
633 			fflush(stderr);
634 		    }
635 		    /* wait for child */
636 #ifdef HAVE_WAITPID
637 		    while (-1 == waitpid(pid, &wstatus, 0)) {
638 #ifdef EINTR
639 			if (errno == EINTR)
640 			    continue;
641 #endif /* EINTR */
642 #ifdef ERESTARTSYS
643 			if (errno == ERESTARTSYS)
644 			    continue;
645 #endif /* ERESTARTSYS */
646 			break;
647 		    }
648 #else
649 		    while (wait(&wstatus) != pid)	/* do nothing */
650 			;
651 #endif
652 		    _exit(WEXITSTATUS(wstatus));
653 		} else if (pid == 0) {
654 		    if (!dialog_vars.cant_kill)
655 			(void) signal(SIGHUP, finish_bg);
656 		    (void) signal(SIGINT, finish_bg);
657 		    (void) signal(SIGQUIT, finish_bg);
658 		    (void) signal(SIGSEGV, finish_bg);
659 		    while (dialog_state.getc_callbacks != 0) {
660 			int fkey = 0;
661 			dlg_getc_callbacks(ERR, fkey, retval);
662 			napms(1000);
663 		    }
664 		}
665 	    }
666 	}
667     }
668 }
669