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