2 * $Id: ui_getc.c,v 1.80 2020/11/25 01:08:30 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.
26 #include <dlg_internals.h>
32 #if TIME_WITH_SYS_TIME
33 # include <sys/time.h>
37 # include <sys/time.h>
43 #ifdef HAVE_SYS_WAIT_H
48 #include <sys/select.h>
52 # ifdef HAVE_TYPE_UNIONWAIT
53 # define WEXITSTATUS(status) (status.w_retcode)
55 # define WEXITSTATUS(status) (((status) & 0xff00) >> 8)
60 # ifdef HAVE_TYPE_UNIONWAIT
61 # define WTERMSIG(status) (status.w_termsig)
63 # define WTERMSIG(status) ((status) & 0x7f)
68 dlg_add_callback(DIALOG_CALLBACK * p)
70 p->next = dialog_state.getc_callbacks;
71 dialog_state.getc_callbacks = p;
72 dlg_set_timeout(p->win, TRUE);
76 * Like dlg_add_callback(), but providing for cleanup of caller's associated
80 dlg_add_callback_ref(DIALOG_CALLBACK ** p, DIALOG_FREEBACK freeback)
83 (*p)->freeback = freeback;
88 dlg_remove_callback(DIALOG_CALLBACK * p)
93 FILE *input = p->input;
95 if (p->input == dialog_state.pipe_input)
96 dialog_state.pipe_input = 0;
97 /* more than one callback can have the same input */
98 for (q = dialog_state.getc_callbacks; q != 0; q = q->next) {
99 if (q->input == input) {
106 dlg_del_window(p->win);
107 if ((q = dialog_state.getc_callbacks) == p) {
108 dialog_state.getc_callbacks = p->next;
119 /* handle dlg_add_callback_ref cleanup */
120 if (p->freeback != 0)
129 * A select() might find more than one input ready for service. Handle them
133 handle_inputs(WINDOW *win)
141 getyx(win, cur_y, cur_x);
142 for (p = dialog_state.getc_callbacks, q = 0; p != 0; p = q) {
144 if ((p->handle_input != 0) && p->input_ready) {
145 p->input_ready = FALSE;
149 if (p->handle_input(p)) {
154 if (result && _dlg_find_window(win)) {
155 (void) wmove(win, cur_y, cur_x); /* Restore cursor position */
166 may_handle_inputs(void)
172 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
183 * Check any any inputs registered via callbacks, to see if there is any input
184 * available. If there is, return a file-descriptor which should be read.
185 * Otherwise, return -1.
195 if ((p = dialog_state.getc_callbacks) != 0) {
204 p->input_ready = FALSE;
205 if (p->input != 0 && (fd = fileno(p->input)) >= 0) {
206 FD_SET(fd, &read_fds);
214 test.tv_usec = WTIMEOUT_VAL * 1000;
215 found = select(last_fd + 1, &read_fds,
221 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
223 && (fd = fileno(p->input)) >= 0
224 && FD_ISSET(fd, &read_fds)) {
225 p->input_ready = TRUE;
236 dlg_getc_callbacks(int ch, int fkey, int *result)
239 DIALOG_CALLBACK *p, *q;
241 if ((p = dialog_state.getc_callbacks) != 0) {
242 if (check_inputs() >= 0) {
245 if (p->input_ready) {
246 if (!(p->handle_getc(p, ch, fkey, result))) {
247 dlg_remove_callback(p);
250 } while ((p = q) != 0);
252 code = (dialog_state.getc_callbacks != 0);
258 dlg_raise_window(WINDOW *win)
260 if (_dlg_find_window(win)) {
262 wmove(win, getcury(win), getcurx(win));
269 * This is a work-around for the case where we actually need the wide-character
270 * code versus a byte stream.
272 static int last_getc = ERR;
274 #ifdef USE_WIDE_CURSES
275 static char last_getc_bytes[80];
276 static int have_last_getc;
277 static int used_last_getc;
283 #ifdef USE_WIDE_CURSES
284 if (used_last_getc != 1)
285 return ERR; /* not really an error... */
294 #ifdef USE_WIDE_CURSES
301 * Report the last key entered by the user. The 'mode' parameter controls
302 * the way it is separated from other results:
304 * -1 (separator after the key name)
305 * 0 (separator is optionally before the key name)
309 dlg_add_last_key(int mode)
311 if (dialog_vars.last_key) {
314 dlg_add_last_key(-1);
316 if (dlg_need_separator())
318 dlg_add_last_key(-2);
322 sprintf(temp, "%d", last_getc);
323 DLG_TRACE(("# dlg_add_last_key(%s)\n", temp));
324 dlg_add_string(temp);
332 * Check if the stream has been unexpectedly closed, returning false in that
342 if (fcntl(fd, F_GETFL, 0) >= 0) {
350 really_getch(WINDOW *win, int *fkey)
353 #ifdef USE_WIDE_CURSES
358 * We get a wide character, translate it to multibyte form to avoid
359 * having to change the rest of the code to use wide-characters.
361 if (used_last_getc >= have_last_getc) {
369 code = wget_wch(win, &my_wint);
370 my_wchar = (wchar_t) my_wint;
373 ch = *fkey = my_wchar;
374 last_getc = my_wchar;
377 memset(&state, 0, sizeof(state));
378 have_last_getc = (int) wcrtomb(last_getc_bytes, my_wchar, &state);
379 if (have_last_getc < 0) {
380 have_last_getc = used_last_getc = 0;
381 last_getc_bytes[0] = (char) my_wchar;
383 ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
384 last_getc = my_wchar;
394 ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
399 *fkey = (ch > KEY_MIN && ch < KEY_MAX);
404 static DIALOG_CALLBACK *
405 next_callback(DIALOG_CALLBACK * p)
407 if ((p = dialog_state.getc_redirect) != 0) {
410 p = dialog_state.getc_callbacks;
415 static DIALOG_CALLBACK *
416 prev_callback(DIALOG_CALLBACK * p)
420 if ((p = dialog_state.getc_redirect) != 0) {
421 if (p == dialog_state.getc_callbacks) {
422 for (p = dialog_state.getc_callbacks; p->next != 0; p = p->next) ;
424 for (q = dialog_state.getc_callbacks; q->next != p; q = q->next) ;
428 p = dialog_state.getc_callbacks;
433 #define isBeforeChr(chr) ((chr) == before_chr && !before_fkey)
434 #define isBeforeFkey(chr) ((chr) == before_chr && before_fkey)
437 * Read a character from the given window. Handle repainting here (to simplify
438 * things in the calling application). Also, if input-callback(s) are set up,
439 * poll the corresponding files and handle the updates, e.g., for displaying a
443 dlg_getc(WINDOW *win, int *fkey)
445 WINDOW *save_win = win;
451 bool literal = FALSE;
452 DIALOG_CALLBACK *p = 0;
453 int interval = dlg_set_timeout(win, may_handle_inputs());
454 time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs;
458 bool handle_others = FALSE;
460 if (_dlg_find_window(win) == NULL)
464 * If there was no pending file-input, check the keyboard.
466 ch = really_getch(win, fkey);
475 ch = dlg_lookup_key(win, ch, fkey);
476 dlg_trace_chr(ch, *fkey);
478 current = time((time_t *) 0);
481 * If we acquired a fkey value, then it is one of dialog's builtin
482 * codes such as DLGK_HELPFILE.
484 if (!*fkey || *fkey != before_fkey) {
491 if (_dlg_find_window(win)) {
492 (void) touchwin(win);
493 (void) wrefresh(curscr);
496 case ERR: /* wtimeout() in effect; check for file I/O */
498 && current >= expired) {
500 DLG_TRACE(("# dlg_getc: timeout expired\n"));
501 if (dlg_getenv_num("DIALOG_TIMEOUT", &status)) {
502 dlg_exiterr("timeout");
506 } else if (!valid_file(stdin)
507 || !valid_file(dialog_state.screen_output)) {
508 DLG_TRACE(("# dlg_getc: input or output is invalid\n"));
511 } else if (check_inputs()) {
512 if (_dlg_find_window(win) && handle_inputs(win))
513 dlg_raise_window(win);
517 done = (interval <= 0);
521 if (dialog_vars.help_file && _dlg_find_window(win)) {
523 getyx(win, yold, xold);
524 dialog_helpfile("HELP", dialog_vars.help_file, 0, 0);
525 dlg_raise_window(win);
526 wmove(win, yold, xold);
529 case DLGK_FIELD_PREV:
533 case DLGK_FIELD_NEXT:
536 /* Handle tab/backtab as a special case for traversing between
537 * the nominal "current" window, and other windows having
538 * callbacks. If the nominal (control) window closes, we'll
539 * close the windows with callbacks.
541 if (dialog_state.getc_callbacks != 0 &&
543 isBeforeFkey(KEY_BTAB))) {
544 p = (isBeforeChr(TAB)
547 if ((dialog_state.getc_redirect = p) != 0) {
552 dlg_raise_window(win);
558 if (isBeforeChr(DLG_CTRL('P'))) {
559 /* for testing, ^P closes the connection */
566 handle_others = TRUE;
568 #ifdef HAVE_DLG_TRACE
575 handle_others = TRUE;
579 if ((p = dialog_state.getc_redirect) != 0) {
580 if (!(p->handle_getc(p, ch, *fkey, &result))) {
581 done = (p->win == save_win) && (!p->keep_win);
582 dlg_remove_callback(p);
583 dialog_state.getc_redirect = 0;
591 if (literal && _dlg_find_window(win))
597 finish_bg(int sig GCC_UNUSED)
600 dlg_exit(DLG_EXIT_ERROR);
604 * If we have callbacks active, purge the list of all that are not marked
605 * to keep in the background. If any remain, run those in a background
609 dlg_killall_bg(int *retval)
612 #ifdef HAVE_TYPE_UNIONWAIT
618 if ((cb = dialog_state.getc_callbacks) != 0) {
623 dlg_remove_callback(cb);
624 cb = dialog_state.getc_callbacks;
627 if (dialog_state.getc_callbacks != 0) {
634 if ((pid = fork()) != 0) {
635 _exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR);
636 } else { /* child, pid==0 */
637 if ((pid = fork()) != 0) {
639 * Echo the process-id of the grandchild so a shell script
640 * can read that, and kill that process. We'll wait around
641 * until then. Our parent has already left, leaving us
642 * temporarily orphaned.
644 if (pid > 0) { /* parent */
645 fprintf(stderr, "%d\n", pid);
650 while (-1 == waitpid(pid, &wstatus, 0)) {
656 if (errno == ERESTARTSYS)
658 #endif /* ERESTARTSYS */
662 while (wait(&wstatus) != pid) /* do nothing */
665 _exit(WEXITSTATUS(wstatus));
666 } else { /* child, pid==0 */
667 if (!dialog_vars.cant_kill)
668 (void) signal(SIGHUP, finish_bg);
669 (void) signal(SIGINT, finish_bg);
670 (void) signal(SIGQUIT, finish_bg);
671 (void) signal(SIGSEGV, finish_bg);
672 while (dialog_state.getc_callbacks != 0) {
674 dlg_getc_callbacks(ERR, fkey, retval);