1 /*
2 * $Id: ui_getc.c,v 1.80 2020/11/25 01:08:30 tom Exp $
3 *
4 * ui_getc.c - user interface glue for getc()
5 *
6 * Copyright 2001-2019,2020 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 #include <dlg_internals.h>
27
28 #ifdef NEED_WCHAR_H
29 #include <wchar.h>
30 #endif
31
32 #if TIME_WITH_SYS_TIME
33 # include <sys/time.h>
34 # include <time.h>
35 #else
36 # if HAVE_SYS_TIME_H
37 # include <sys/time.h>
38 # else
39 # include <time.h>
40 # endif
41 #endif
42
43 #ifdef HAVE_SYS_WAIT_H
44 #include <sys/wait.h>
45 #endif
46
47 #ifdef __QNX__
48 #include <sys/select.h>
49 #endif
50
51 #ifndef WEXITSTATUS
52 # ifdef HAVE_TYPE_UNIONWAIT
53 # define WEXITSTATUS(status) (status.w_retcode)
54 # else
55 # define WEXITSTATUS(status) (((status) & 0xff00) >> 8)
56 # endif
57 #endif
58
59 #ifndef WTERMSIG
60 # ifdef HAVE_TYPE_UNIONWAIT
61 # define WTERMSIG(status) (status.w_termsig)
62 # else
63 # define WTERMSIG(status) ((status) & 0x7f)
64 # endif
65 #endif
66
67 void
dlg_add_callback(DIALOG_CALLBACK * p)68 dlg_add_callback(DIALOG_CALLBACK * p)
69 {
70 p->next = dialog_state.getc_callbacks;
71 dialog_state.getc_callbacks = p;
72 dlg_set_timeout(p->win, TRUE);
73 }
74
75 /*
76 * Like dlg_add_callback(), but providing for cleanup of caller's associated
77 * state.
78 */
79 void
dlg_add_callback_ref(DIALOG_CALLBACK ** p,DIALOG_FREEBACK freeback)80 dlg_add_callback_ref(DIALOG_CALLBACK ** p, DIALOG_FREEBACK freeback)
81 {
82 (*p)->caller = p;
83 (*p)->freeback = freeback;
84 dlg_add_callback(*p);
85 }
86
87 void
dlg_remove_callback(DIALOG_CALLBACK * p)88 dlg_remove_callback(DIALOG_CALLBACK * p)
89 {
90 DIALOG_CALLBACK *q;
91
92 if (p->input != 0) {
93 FILE *input = p->input;
94 fclose(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) {
100 q->input = 0;
101 }
102 }
103 }
104
105 if (!(p->keep_win))
106 dlg_del_window(p->win);
107 if ((q = dialog_state.getc_callbacks) == p) {
108 dialog_state.getc_callbacks = p->next;
109 } else {
110 while (q != 0) {
111 if (q->next == p) {
112 q->next = p->next;
113 break;
114 }
115 q = q->next;
116 }
117 }
118
119 /* handle dlg_add_callback_ref cleanup */
120 if (p->freeback != 0)
121 p->freeback(p);
122 if (p->caller != 0)
123 *(p->caller) = 0;
124
125 free(p);
126 }
127
128 /*
129 * A select() might find more than one input ready for service. Handle them
130 * all.
131 */
132 static bool
handle_inputs(WINDOW * win)133 handle_inputs(WINDOW *win)
134 {
135 bool result = FALSE;
136 DIALOG_CALLBACK *p;
137 DIALOG_CALLBACK *q;
138 int cur_y, cur_x;
139 int state = ERR;
140
141 getyx(win, cur_y, cur_x);
142 for (p = dialog_state.getc_callbacks, q = 0; p != 0; p = q) {
143 q = p->next;
144 if ((p->handle_input != 0) && p->input_ready) {
145 p->input_ready = FALSE;
146 if (state == ERR) {
147 state = curs_set(0);
148 }
149 if (p->handle_input(p)) {
150 result = TRUE;
151 }
152 }
153 }
154 if (result && _dlg_find_window(win)) {
155 (void) wmove(win, cur_y, cur_x); /* Restore cursor position */
156 wrefresh(win);
157 } else {
158 result = FALSE;
159 }
160 if (state != ERR)
161 curs_set(state);
162 return result;
163 }
164
165 static bool
may_handle_inputs(void)166 may_handle_inputs(void)
167 {
168 bool result = FALSE;
169
170 DIALOG_CALLBACK *p;
171
172 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
173 if (p->input != 0) {
174 result = TRUE;
175 break;
176 }
177 }
178
179 return result;
180 }
181
182 /*
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.
186 */
187 static int
check_inputs(void)188 check_inputs(void)
189 {
190 DIALOG_CALLBACK *p;
191 fd_set read_fds;
192 struct timeval test;
193 int result = -1;
194
195 if ((p = dialog_state.getc_callbacks) != 0) {
196 int last_fd = -1;
197 int found;
198 int fd;
199
200 FD_ZERO(&read_fds);
201
202 while (p != 0) {
203
204 p->input_ready = FALSE;
205 if (p->input != 0 && (fd = fileno(p->input)) >= 0) {
206 FD_SET(fd, &read_fds);
207 if (last_fd < fd)
208 last_fd = fd;
209 }
210 p = p->next;
211 }
212
213 test.tv_sec = 0;
214 test.tv_usec = WTIMEOUT_VAL * 1000;
215 found = select(last_fd + 1, &read_fds,
216 (fd_set *) 0,
217 (fd_set *) 0,
218 &test);
219
220 if (found > 0) {
221 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
222 if (p->input != 0
223 && (fd = fileno(p->input)) >= 0
224 && FD_ISSET(fd, &read_fds)) {
225 p->input_ready = TRUE;
226 result = fd;
227 }
228 }
229 }
230 }
231
232 return result;
233 }
234
235 int
dlg_getc_callbacks(int ch,int fkey,int * result)236 dlg_getc_callbacks(int ch, int fkey, int *result)
237 {
238 int code = FALSE;
239 DIALOG_CALLBACK *p, *q;
240
241 if ((p = dialog_state.getc_callbacks) != 0) {
242 if (check_inputs() >= 0) {
243 do {
244 q = p->next;
245 if (p->input_ready) {
246 if (!(p->handle_getc(p, ch, fkey, result))) {
247 dlg_remove_callback(p);
248 }
249 }
250 } while ((p = q) != 0);
251 }
252 code = (dialog_state.getc_callbacks != 0);
253 }
254 return code;
255 }
256
257 static void
dlg_raise_window(WINDOW * win)258 dlg_raise_window(WINDOW *win)
259 {
260 if (_dlg_find_window(win)) {
261 touchwin(win);
262 wmove(win, getcury(win), getcurx(win));
263 wnoutrefresh(win);
264 doupdate();
265 }
266 }
267
268 /*
269 * This is a work-around for the case where we actually need the wide-character
270 * code versus a byte stream.
271 */
272 static int last_getc = ERR;
273
274 #ifdef USE_WIDE_CURSES
275 static char last_getc_bytes[80];
276 static int have_last_getc;
277 static int used_last_getc;
278 #endif
279
280 int
dlg_last_getc(void)281 dlg_last_getc(void)
282 {
283 #ifdef USE_WIDE_CURSES
284 if (used_last_getc != 1)
285 return ERR; /* not really an error... */
286 #endif
287 return last_getc;
288 }
289
290 void
dlg_flush_getc(void)291 dlg_flush_getc(void)
292 {
293 last_getc = ERR;
294 #ifdef USE_WIDE_CURSES
295 have_last_getc = 0;
296 used_last_getc = 0;
297 #endif
298 }
299
300 /*
301 * Report the last key entered by the user. The 'mode' parameter controls
302 * the way it is separated from other results:
303 * -2 (no separator)
304 * -1 (separator after the key name)
305 * 0 (separator is optionally before the key name)
306 * 1 (same as -1)
307 */
308 void
dlg_add_last_key(int mode)309 dlg_add_last_key(int mode)
310 {
311 if (dialog_vars.last_key) {
312 if (mode >= 0) {
313 if (mode > 0) {
314 dlg_add_last_key(-1);
315 } else {
316 if (dlg_need_separator())
317 dlg_add_separator();
318 dlg_add_last_key(-2);
319 }
320 } else {
321 char temp[80];
322 sprintf(temp, "%d", last_getc);
323 DLG_TRACE(("# dlg_add_last_key(%s)\n", temp));
324 dlg_add_string(temp);
325 if (mode == -1)
326 dlg_add_separator();
327 }
328 }
329 }
330
331 /*
332 * Check if the stream has been unexpectedly closed, returning false in that
333 * case.
334 */
335 static bool
valid_file(FILE * fp)336 valid_file(FILE *fp)
337 {
338 bool code = FALSE;
339 int fd = fileno(fp);
340
341 if (fd >= 0) {
342 if (fcntl(fd, F_GETFL, 0) >= 0) {
343 code = TRUE;
344 }
345 }
346 return code;
347 }
348
349 static int
really_getch(WINDOW * win,int * fkey)350 really_getch(WINDOW *win, int *fkey)
351 {
352 int ch;
353 #ifdef USE_WIDE_CURSES
354 mbstate_t state;
355 wint_t my_wint;
356
357 /*
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.
360 */
361 if (used_last_getc >= have_last_getc) {
362 int code;
363 wchar_t my_wchar;
364
365 used_last_getc = 0;
366 have_last_getc = 0;
367 ch = ERR;
368 *fkey = 0;
369 code = wget_wch(win, &my_wint);
370 my_wchar = (wchar_t) my_wint;
371 switch (code) {
372 case KEY_CODE_YES:
373 ch = *fkey = my_wchar;
374 last_getc = my_wchar;
375 break;
376 case OK:
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;
382 }
383 ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
384 last_getc = my_wchar;
385 break;
386 case ERR:
387 ch = ERR;
388 last_getc = ERR;
389 break;
390 default:
391 break;
392 }
393 } else {
394 ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
395 }
396 #else
397 ch = wgetch(win);
398 last_getc = ch;
399 *fkey = (ch > KEY_MIN && ch < KEY_MAX);
400 #endif
401 return ch;
402 }
403
404 static DIALOG_CALLBACK *
next_callback(DIALOG_CALLBACK * p)405 next_callback(DIALOG_CALLBACK * p)
406 {
407 if ((p = dialog_state.getc_redirect) != 0) {
408 p = p->next;
409 } else {
410 p = dialog_state.getc_callbacks;
411 }
412 return p;
413 }
414
415 static DIALOG_CALLBACK *
prev_callback(DIALOG_CALLBACK * p)416 prev_callback(DIALOG_CALLBACK * p)
417 {
418 DIALOG_CALLBACK *q;
419
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) ;
423 } else {
424 for (q = dialog_state.getc_callbacks; q->next != p; q = q->next) ;
425 p = q;
426 }
427 } else {
428 p = dialog_state.getc_callbacks;
429 }
430 return p;
431 }
432
433 #define isBeforeChr(chr) ((chr) == before_chr && !before_fkey)
434 #define isBeforeFkey(chr) ((chr) == before_chr && before_fkey)
435
436 /*
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
440 * tailbox.
441 */
442 int
dlg_getc(WINDOW * win,int * fkey)443 dlg_getc(WINDOW *win, int *fkey)
444 {
445 WINDOW *save_win = win;
446 int ch = ERR;
447 int before_chr;
448 int before_fkey;
449 int result;
450 bool done = FALSE;
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;
455 time_t current;
456
457 while (!done) {
458 bool handle_others = FALSE;
459
460 if (_dlg_find_window(win) == NULL)
461 break;
462
463 /*
464 * If there was no pending file-input, check the keyboard.
465 */
466 ch = really_getch(win, fkey);
467 if (literal) {
468 done = TRUE;
469 continue;
470 }
471
472 before_chr = ch;
473 before_fkey = *fkey;
474
475 ch = dlg_lookup_key(win, ch, fkey);
476 dlg_trace_chr(ch, *fkey);
477
478 current = time((time_t *) 0);
479
480 /*
481 * If we acquired a fkey value, then it is one of dialog's builtin
482 * codes such as DLGK_HELPFILE.
483 */
484 if (!*fkey || *fkey != before_fkey) {
485 switch (ch) {
486 case CHR_LITERAL:
487 literal = TRUE;
488 keypad(win, FALSE);
489 continue;
490 case CHR_REPAINT:
491 if (_dlg_find_window(win)) {
492 (void) touchwin(win);
493 (void) wrefresh(curscr);
494 }
495 break;
496 case ERR: /* wtimeout() in effect; check for file I/O */
497 if (interval > 0
498 && current >= expired) {
499 int status;
500 DLG_TRACE(("# dlg_getc: timeout expired\n"));
501 if (dlg_getenv_num("DIALOG_TIMEOUT", &status)) {
502 dlg_exiterr("timeout");
503 }
504 ch = ESC;
505 done = TRUE;
506 } else if (!valid_file(stdin)
507 || !valid_file(dialog_state.screen_output)) {
508 DLG_TRACE(("# dlg_getc: input or output is invalid\n"));
509 ch = ESC;
510 done = TRUE;
511 } else if (check_inputs()) {
512 if (_dlg_find_window(win) && handle_inputs(win))
513 dlg_raise_window(win);
514 else
515 done = TRUE;
516 } else {
517 done = (interval <= 0);
518 }
519 break;
520 case DLGK_HELPFILE:
521 if (dialog_vars.help_file && _dlg_find_window(win)) {
522 int yold, xold;
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);
527 }
528 continue;
529 case DLGK_FIELD_PREV:
530 /* FALLTHRU */
531 case KEY_BTAB:
532 /* FALLTHRU */
533 case DLGK_FIELD_NEXT:
534 /* FALLTHRU */
535 case TAB:
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.
540 */
541 if (dialog_state.getc_callbacks != 0 &&
542 (isBeforeChr(TAB) ||
543 isBeforeFkey(KEY_BTAB))) {
544 p = (isBeforeChr(TAB)
545 ? next_callback(p)
546 : prev_callback(p));
547 if ((dialog_state.getc_redirect = p) != 0) {
548 win = p->win;
549 } else {
550 win = save_win;
551 }
552 dlg_raise_window(win);
553 break;
554 }
555 /* FALLTHRU */
556 default:
557 #ifdef NO_LEAKS
558 if (isBeforeChr(DLG_CTRL('P'))) {
559 /* for testing, ^P closes the connection */
560 close(0);
561 close(1);
562 close(2);
563 break;
564 }
565 #endif
566 handle_others = TRUE;
567 break;
568 #ifdef HAVE_DLG_TRACE
569 case CHR_TRACE:
570 dlg_trace_win(win);
571 break;
572 #endif
573 }
574 } else {
575 handle_others = TRUE;
576 }
577
578 if (handle_others) {
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;
584 win = save_win;
585 }
586 } else {
587 done = TRUE;
588 }
589 }
590 }
591 if (literal && _dlg_find_window(win))
592 keypad(win, TRUE);
593 return ch;
594 }
595
596 static void
finish_bg(int sig GCC_UNUSED)597 finish_bg(int sig GCC_UNUSED)
598 {
599 end_dialog();
600 dlg_exit(DLG_EXIT_ERROR);
601 }
602
603 /*
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
606 * process.
607 */
608 void
dlg_killall_bg(int * retval)609 dlg_killall_bg(int *retval)
610 {
611 DIALOG_CALLBACK *cb;
612 #ifdef HAVE_TYPE_UNIONWAIT
613 union wait wstatus;
614 #else
615 int wstatus;
616 #endif
617
618 if ((cb = dialog_state.getc_callbacks) != 0) {
619 while (cb != 0) {
620 if (cb->keep_bg) {
621 cb = cb->next;
622 } else {
623 dlg_remove_callback(cb);
624 cb = dialog_state.getc_callbacks;
625 }
626 }
627 if (dialog_state.getc_callbacks != 0) {
628 int pid;
629
630 refresh();
631 fflush(stdout);
632 fflush(stderr);
633 reset_shell_mode();
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) {
638 /*
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.
643 */
644 if (pid > 0) { /* parent */
645 fprintf(stderr, "%d\n", pid);
646 fflush(stderr);
647 }
648 /* wait for child */
649 #ifdef HAVE_WAITPID
650 while (-1 == waitpid(pid, &wstatus, 0)) {
651 #ifdef EINTR
652 if (errno == EINTR)
653 continue;
654 #endif /* EINTR */
655 #ifdef ERESTARTSYS
656 if (errno == ERESTARTSYS)
657 continue;
658 #endif /* ERESTARTSYS */
659 break;
660 }
661 #else
662 while (wait(&wstatus) != pid) /* do nothing */
663 ;
664 #endif
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) {
673 int fkey = 0;
674 dlg_getc_callbacks(ERR, fkey, retval);
675 napms(1000);
676 }
677 }
678 }
679 }
680 }
681 }
682