2 * $Id: ui_getc.c,v 1.75 2020/03/27 21:49:03 tom Exp $
4 * ui_getc.c - user interface glue for getc()
6 * Copyright 2001-2019,2020 Thomas E. Dickey
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.
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.
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.
31 #if TIME_WITH_SYS_TIME
32 # include <sys/time.h>
36 # include <sys/time.h>
42 #ifdef HAVE_SYS_WAIT_H
47 #include <sys/select.h>
51 # ifdef HAVE_TYPE_UNIONWAIT
52 # define WEXITSTATUS(status) (status.w_retcode)
54 # define WEXITSTATUS(status) (((status) & 0xff00) >> 8)
59 # ifdef HAVE_TYPE_UNIONWAIT
60 # define WTERMSIG(status) (status.w_termsig)
62 # define WTERMSIG(status) ((status) & 0x7f)
67 dlg_add_callback(DIALOG_CALLBACK * p)
69 p->next = dialog_state.getc_callbacks;
70 dialog_state.getc_callbacks = p;
71 dlg_set_timeout(p->win, TRUE);
75 * Like dlg_add_callback(), but providing for cleanup of caller's associated
79 dlg_add_callback_ref(DIALOG_CALLBACK ** p, DIALOG_FREEBACK freeback)
82 (*p)->freeback = freeback;
87 dlg_remove_callback(DIALOG_CALLBACK * p)
92 FILE *input = p->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) {
105 dlg_del_window(p->win);
106 if ((q = dialog_state.getc_callbacks) == p) {
107 dialog_state.getc_callbacks = p->next;
118 /* handle dlg_add_callback_ref cleanup */
119 if (p->freeback != 0)
128 * A select() might find more than one input ready for service. Handle them
132 handle_inputs(WINDOW *win)
140 getyx(win, cur_y, cur_x);
141 for (p = dialog_state.getc_callbacks, q = 0; p != 0; p = q) {
143 if ((p->handle_input != 0) && p->input_ready) {
144 p->input_ready = FALSE;
148 if (p->handle_input(p)) {
154 (void) wmove(win, cur_y, cur_x); /* Restore cursor position */
163 may_handle_inputs(void)
169 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
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.
192 if ((p = dialog_state.getc_callbacks) != 0) {
201 p->input_ready = FALSE;
202 if (p->input != 0 && (fd = fileno(p->input)) >= 0) {
203 FD_SET(fd, &read_fds);
211 test.tv_usec = WTIMEOUT_VAL * 1000;
212 found = select(last_fd + 1, &read_fds,
218 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
220 && (fd = fileno(p->input)) >= 0
221 && FD_ISSET(fd, &read_fds)) {
222 p->input_ready = TRUE;
233 dlg_getc_callbacks(int ch, int fkey, int *result)
236 DIALOG_CALLBACK *p, *q;
238 if ((p = dialog_state.getc_callbacks) != 0) {
239 if (check_inputs() >= 0) {
242 if (p->input_ready) {
243 if (!(p->handle_getc(p, ch, fkey, result))) {
244 dlg_remove_callback(p);
247 } while ((p = q) != 0);
249 code = (dialog_state.getc_callbacks != 0);
255 dlg_raise_window(WINDOW *win)
258 wmove(win, getcury(win), getcurx(win));
264 * This is a work-around for the case where we actually need the wide-character
265 * code versus a byte stream.
267 static int last_getc = ERR;
269 #ifdef USE_WIDE_CURSES
270 static char last_getc_bytes[80];
271 static int have_last_getc;
272 static int used_last_getc;
278 #ifdef USE_WIDE_CURSES
279 if (used_last_getc != 1)
280 return ERR; /* not really an error... */
289 #ifdef USE_WIDE_CURSES
296 * Report the last key entered by the user. The 'mode' parameter controls
297 * the way it is separated from other results:
299 * -1 (separator after the key name)
300 * 0 (separator is optionally before the key name)
304 dlg_add_last_key(int mode)
306 if (dialog_vars.last_key) {
309 dlg_add_last_key(-1);
311 if (dlg_need_separator())
313 dlg_add_last_key(-2);
317 sprintf(temp, "%d", last_getc);
318 DLG_TRACE(("# dlg_add_last_key(%s)\n", temp));
319 dlg_add_string(temp);
327 * Check if the stream has been unexpectedly closed, returning false in that
337 if (fcntl(fd, F_GETFL, 0) >= 0) {
345 really_getch(WINDOW *win, int *fkey)
348 #ifdef USE_WIDE_CURSES
353 * We get a wide character, translate it to multibyte form to avoid
354 * having to change the rest of the code to use wide-characters.
356 if (used_last_getc >= have_last_getc) {
364 code = wget_wch(win, &my_wint);
365 my_wchar = (wchar_t) my_wint;
368 ch = *fkey = my_wchar;
369 last_getc = my_wchar;
372 memset(&state, 0, sizeof(state));
373 have_last_getc = (int) wcrtomb(last_getc_bytes, my_wchar, &state);
374 if (have_last_getc < 0) {
375 have_last_getc = used_last_getc = 0;
376 last_getc_bytes[0] = (char) my_wchar;
378 ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
379 last_getc = my_wchar;
389 ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
394 *fkey = (ch > KEY_MIN && ch < KEY_MAX);
399 static DIALOG_CALLBACK *
400 next_callback(DIALOG_CALLBACK * p)
402 if ((p = dialog_state.getc_redirect) != 0) {
405 p = dialog_state.getc_callbacks;
410 static DIALOG_CALLBACK *
411 prev_callback(DIALOG_CALLBACK * p)
415 if ((p = dialog_state.getc_redirect) != 0) {
416 if (p == dialog_state.getc_callbacks) {
417 for (p = dialog_state.getc_callbacks; p->next != 0; p = p->next) ;
419 for (q = dialog_state.getc_callbacks; q->next != p; q = q->next) ;
423 p = dialog_state.getc_callbacks;
428 #define isBeforeChr(chr) ((chr) == before_chr && !before_fkey)
429 #define isBeforeFkey(chr) ((chr) == before_chr && before_fkey)
432 * Read a character from the given window. Handle repainting here (to simplify
433 * things in the calling application). Also, if input-callback(s) are set up,
434 * poll the corresponding files and handle the updates, e.g., for displaying a
438 dlg_getc(WINDOW *win, int *fkey)
440 WINDOW *save_win = win;
446 bool literal = FALSE;
447 DIALOG_CALLBACK *p = 0;
448 int interval = dlg_set_timeout(win, may_handle_inputs());
449 time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs;
453 bool handle_others = FALSE;
456 * If there was no pending file-input, check the keyboard.
458 ch = really_getch(win, fkey);
467 ch = dlg_lookup_key(win, ch, fkey);
468 dlg_trace_chr(ch, *fkey);
470 current = time((time_t *) 0);
473 * If we acquired a fkey value, then it is one of dialog's builtin
474 * codes such as DLGK_HELPFILE.
476 if (!*fkey || *fkey != before_fkey) {
483 (void) touchwin(win);
484 (void) wrefresh(curscr);
486 case ERR: /* wtimeout() in effect; check for file I/O */
488 && current >= expired) {
489 DLG_TRACE(("# dlg_getc: timeout expired\n"));
492 } else if (!valid_file(stdin)
493 || !valid_file(dialog_state.screen_output)) {
494 DLG_TRACE(("# dlg_getc: input or output is invalid\n"));
497 } else if (check_inputs()) {
498 if (handle_inputs(win))
499 dlg_raise_window(win);
503 done = (interval <= 0);
507 if (dialog_vars.help_file) {
509 getyx(win, yold, xold);
510 dialog_helpfile("HELP", dialog_vars.help_file, 0, 0);
511 dlg_raise_window(win);
512 wmove(win, yold, xold);
515 case DLGK_FIELD_PREV:
519 case DLGK_FIELD_NEXT:
522 /* Handle tab/backtab as a special case for traversing between
523 * the nominal "current" window, and other windows having
524 * callbacks. If the nominal (control) window closes, we'll
525 * close the windows with callbacks.
527 if (dialog_state.getc_callbacks != 0 &&
529 isBeforeFkey(KEY_BTAB))) {
530 p = (isBeforeChr(TAB)
533 if ((dialog_state.getc_redirect = p) != 0) {
538 dlg_raise_window(win);
544 if (isBeforeChr(DLG_CTRL('P'))) {
545 /* for testing, ^P closes the connection */
552 handle_others = TRUE;
554 #ifdef HAVE_DLG_TRACE
561 handle_others = TRUE;
565 if ((p = dialog_state.getc_redirect) != 0) {
566 if (!(p->handle_getc(p, ch, *fkey, &result))) {
567 done = (p->win == save_win) && (!p->keep_win);
568 dlg_remove_callback(p);
569 dialog_state.getc_redirect = 0;
583 finish_bg(int sig GCC_UNUSED)
586 dlg_exit(DLG_EXIT_ERROR);
590 * If we have callbacks active, purge the list of all that are not marked
591 * to keep in the background. If any remain, run those in a background
595 dlg_killall_bg(int *retval)
598 #ifdef HAVE_TYPE_UNIONWAIT
604 if ((cb = dialog_state.getc_callbacks) != 0) {
609 dlg_remove_callback(cb);
610 cb = dialog_state.getc_callbacks;
613 if (dialog_state.getc_callbacks != 0) {
620 if ((pid = fork()) != 0) {
621 _exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR);
622 } else { /* child, pid==0 */
623 if ((pid = fork()) != 0) {
625 * Echo the process-id of the grandchild so a shell script
626 * can read that, and kill that process. We'll wait around
627 * until then. Our parent has already left, leaving us
628 * temporarily orphaned.
630 if (pid > 0) { /* parent */
631 fprintf(stderr, "%d\n", pid);
636 while (-1 == waitpid(pid, &wstatus, 0)) {
642 if (errno == ERESTARTSYS)
644 #endif /* ERESTARTSYS */
648 while (wait(&wstatus) != pid) /* do nothing */
651 _exit(WEXITSTATUS(wstatus));
652 } else { /* child, pid==0 */
653 if (!dialog_vars.cant_kill)
654 (void) signal(SIGHUP, finish_bg);
655 (void) signal(SIGINT, finish_bg);
656 (void) signal(SIGQUIT, finish_bg);
657 (void) signal(SIGSEGV, finish_bg);
658 while (dialog_state.getc_callbacks != 0) {
660 dlg_getc_callbacks(ERR, fkey, retval);