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
dlg_add_callback(DIALOG_CALLBACK * p)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
dlg_add_callback_ref(DIALOG_CALLBACK ** p,DIALOG_FREEBACK freeback)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
dlg_remove_callback(DIALOG_CALLBACK * p)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
handle_inputs(WINDOW * win)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
may_handle_inputs(void)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
check_inputs(void)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
dlg_getc_callbacks(int ch,int fkey,int * result)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
dlg_raise_window(WINDOW * win)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
dlg_last_getc(void)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
dlg_flush_getc(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
dlg_add_last_key(int mode)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
valid_file(FILE * fp)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
really_getch(WINDOW * win,int * fkey)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 *
next_callback(DIALOG_CALLBACK * p)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 *
prev_callback(DIALOG_CALLBACK * p)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
dlg_getc(WINDOW * win,int * fkey)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
finish_bg(int sig GCC_UNUSED)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
dlg_killall_bg(int * retval)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