1 /*
2 * $Id: ui_getc.c,v 1.67 2013/03/24 23:53:19 tom Exp $
3 *
4 * ui_getc.c - user interface glue for getc()
5 *
6 * Copyright 2001-2012,2013 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 fclose(p->input);
93 if (p->input == dialog_state.pipe_input)
94 dialog_state.pipe_input = 0;
95 p->input = 0;
96 }
97
98 if (!(p->keep_win))
99 dlg_del_window(p->win);
100 if ((q = dialog_state.getc_callbacks) == p) {
101 dialog_state.getc_callbacks = p->next;
102 } else {
103 while (q != 0) {
104 if (q->next == p) {
105 q->next = p->next;
106 break;
107 }
108 q = q->next;
109 }
110 }
111
112 /* handle dlg_add_callback_ref cleanup */
113 if (p->freeback != 0)
114 p->freeback(p);
115 if (p->caller != 0)
116 *(p->caller) = 0;
117
118 free(p);
119 }
120
121 /*
122 * A select() might find more than one input ready for service. Handle them
123 * all.
124 */
125 static bool
handle_inputs(WINDOW * win)126 handle_inputs(WINDOW *win)
127 {
128 bool result = FALSE;
129 DIALOG_CALLBACK *p;
130 DIALOG_CALLBACK *q;
131 int cur_y, cur_x;
132 int state = ERR;
133
134 getyx(win, cur_y, cur_x);
135 for (p = dialog_state.getc_callbacks, q = 0; p != 0; p = q) {
136 q = p->next;
137 if ((p->handle_input != 0) && p->input_ready) {
138 p->input_ready = FALSE;
139 if (state == ERR) {
140 state = curs_set(0);
141 }
142 if (p->handle_input(p)) {
143 result = TRUE;
144 }
145 }
146 }
147 if (result) {
148 (void) wmove(win, cur_y, cur_x); /* Restore cursor position */
149 wrefresh(win);
150 curs_set(state);
151 }
152 return result;
153 }
154
155 static bool
may_handle_inputs(void)156 may_handle_inputs(void)
157 {
158 bool result = FALSE;
159
160 DIALOG_CALLBACK *p;
161
162 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
163 if (p->input != 0) {
164 result = TRUE;
165 break;
166 }
167 }
168
169 return result;
170 }
171
172 /*
173 * Check any any inputs registered via callbacks, to see if there is any input
174 * available. If there is, return a file-descriptor which should be read.
175 * Otherwise, return -1.
176 */
177 static int
check_inputs(void)178 check_inputs(void)
179 {
180 DIALOG_CALLBACK *p;
181 fd_set read_fds;
182 struct timeval test;
183 int last_fd = -1;
184 int fd;
185 int found;
186 int result = -1;
187
188 if ((p = dialog_state.getc_callbacks) != 0) {
189 FD_ZERO(&read_fds);
190
191 while (p != 0) {
192 p->input_ready = FALSE;
193 if (p->input != 0 && (fd = fileno(p->input)) >= 0) {
194 FD_SET(fd, &read_fds);
195 if (last_fd < fd)
196 last_fd = fd;
197 }
198 p = p->next;
199 }
200
201 test.tv_sec = 0;
202 test.tv_usec = WTIMEOUT_VAL * 1000;
203 found = select(last_fd + 1, &read_fds,
204 (fd_set *) 0,
205 (fd_set *) 0,
206 &test);
207
208 if (found > 0) {
209 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
210 if (p->input != 0
211 && (fd = fileno(p->input)) >= 0
212 && FD_ISSET(fd, &read_fds)) {
213 p->input_ready = TRUE;
214 result = fd;
215 }
216 }
217 }
218 }
219
220 return result;
221 }
222
223 int
dlg_getc_callbacks(int ch,int fkey,int * result)224 dlg_getc_callbacks(int ch, int fkey, int *result)
225 {
226 int code = FALSE;
227 DIALOG_CALLBACK *p, *q;
228
229 if ((p = dialog_state.getc_callbacks) != 0) {
230 if (check_inputs() >= 0) {
231 do {
232 q = p->next;
233 if (p->input_ready) {
234 if (!(p->handle_getc(p, ch, fkey, result))) {
235 dlg_remove_callback(p);
236 }
237 }
238 } while ((p = q) != 0);
239 }
240 code = (dialog_state.getc_callbacks != 0);
241 }
242 return code;
243 }
244
245 static void
dlg_raise_window(WINDOW * win)246 dlg_raise_window(WINDOW *win)
247 {
248 touchwin(win);
249 wmove(win, getcury(win), getcurx(win));
250 wnoutrefresh(win);
251 doupdate();
252 }
253
254 /*
255 * This is a work-around for the case where we actually need the wide-character
256 * code versus a byte stream.
257 */
258 static int last_getc = ERR;
259
260 #ifdef USE_WIDE_CURSES
261 static char last_getc_bytes[80];
262 static int have_last_getc;
263 static int used_last_getc;
264 #endif
265
266 int
dlg_last_getc(void)267 dlg_last_getc(void)
268 {
269 #ifdef USE_WIDE_CURSES
270 if (used_last_getc != 1)
271 return ERR; /* not really an error... */
272 #endif
273 return last_getc;
274 }
275
276 void
dlg_flush_getc(void)277 dlg_flush_getc(void)
278 {
279 last_getc = ERR;
280 #ifdef USE_WIDE_CURSES
281 have_last_getc = 0;
282 used_last_getc = 0;
283 #endif
284 }
285
286 /*
287 * Report the last key entered by the user. The 'mode' parameter controls
288 * the way it is separated from other results:
289 * -2 (no separator)
290 * -1 (separator after the key name)
291 * 0 (separator is optionally before the key name)
292 * 1 (same as -1)
293 */
294 void
dlg_add_last_key(int mode)295 dlg_add_last_key(int mode)
296 {
297 if (dialog_vars.last_key) {
298 if (mode >= 0) {
299 if (mode > 0) {
300 dlg_add_last_key(-1);
301 } else {
302 if (dlg_need_separator())
303 dlg_add_separator();
304 dlg_add_last_key(-2);
305 }
306 } else {
307 char temp[80];
308 sprintf(temp, "%d", last_getc);
309 dlg_add_string(temp);
310 if (mode == -1)
311 dlg_add_separator();
312 }
313 }
314 }
315
316 /*
317 * Check if the stream has been unexpectedly closed, returning false in that
318 * case.
319 */
320 static bool
valid_file(FILE * fp)321 valid_file(FILE *fp)
322 {
323 bool code = FALSE;
324 int fd = fileno(fp);
325
326 if (fd >= 0) {
327 if (fcntl(fd, F_GETFL, 0) >= 0) {
328 code = TRUE;
329 }
330 }
331 return code;
332 }
333
334 static int
really_getch(WINDOW * win,int * fkey)335 really_getch(WINDOW *win, int *fkey)
336 {
337 int ch;
338 #ifdef USE_WIDE_CURSES
339 int code;
340 mbstate_t state;
341 wchar_t my_wchar;
342 wint_t my_wint;
343
344 /*
345 * We get a wide character, translate it to multibyte form to avoid
346 * having to change the rest of the code to use wide-characters.
347 */
348 if (used_last_getc >= have_last_getc) {
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) CharOf(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) CharOf(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 = (dialog_vars.timeout_secs * 1000);
438 time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs;
439 time_t current;
440
441 if (may_handle_inputs())
442 wtimeout(win, WTIMEOUT_VAL);
443 else if (interval > 0)
444 wtimeout(win, interval);
445
446 while (!done) {
447 bool handle_others = FALSE;
448
449 /*
450 * If there was no pending file-input, check the keyboard.
451 */
452 ch = really_getch(win, fkey);
453 if (literal) {
454 done = TRUE;
455 continue;
456 }
457
458 before_chr = ch;
459 before_fkey = *fkey;
460
461 ch = dlg_lookup_key(win, ch, fkey);
462 dlg_trace_chr(ch, *fkey);
463
464 current = time((time_t *) 0);
465
466 /*
467 * If we acquired a fkey value, then it is one of dialog's builtin
468 * codes such as DLGK_HELPFILE.
469 */
470 if (!*fkey || *fkey != before_fkey) {
471 switch (ch) {
472 case CHR_LITERAL:
473 literal = TRUE;
474 keypad(win, FALSE);
475 continue;
476 case CHR_REPAINT:
477 (void) touchwin(win);
478 (void) wrefresh(curscr);
479 break;
480 case ERR: /* wtimeout() in effect; check for file I/O */
481 if (interval > 0
482 && current >= expired) {
483 dlg_exiterr("timeout");
484 }
485 if (!valid_file(stdin)
486 || !valid_file(dialog_state.screen_output)) {
487 ch = ESC;
488 done = TRUE;
489 } else if (check_inputs()) {
490 if (handle_inputs(win))
491 dlg_raise_window(win);
492 else
493 done = TRUE;
494 } else {
495 done = (interval <= 0);
496 }
497 break;
498 case DLGK_HELPFILE:
499 if (dialog_vars.help_file) {
500 int yold, xold;
501 getyx(win, yold, xold);
502 dialog_helpfile("HELP", dialog_vars.help_file, 0, 0);
503 dlg_raise_window(win);
504 wmove(win, yold, xold);
505 }
506 continue;
507 case DLGK_FIELD_PREV:
508 /* FALLTHRU */
509 case KEY_BTAB:
510 /* FALLTHRU */
511 case DLGK_FIELD_NEXT:
512 /* FALLTHRU */
513 case TAB:
514 /* Handle tab/backtab as a special case for traversing between
515 * the nominal "current" window, and other windows having
516 * callbacks. If the nominal (control) window closes, we'll
517 * close the windows with callbacks.
518 */
519 if (dialog_state.getc_callbacks != 0 &&
520 (isBeforeChr(TAB) ||
521 isBeforeFkey(KEY_BTAB))) {
522 p = (isBeforeChr(TAB)
523 ? next_callback(p)
524 : prev_callback(p));
525 if ((dialog_state.getc_redirect = p) != 0) {
526 win = p->win;
527 } else {
528 win = save_win;
529 }
530 dlg_raise_window(win);
531 break;
532 }
533 /* FALLTHRU */
534 default:
535 #ifdef NO_LEAKS
536 if (isBeforeChr(DLG_CTRL('P'))) {
537 /* for testing, ^P closes the connection */
538 close(0);
539 close(1);
540 close(2);
541 break;
542 }
543 #endif
544 handle_others = TRUE;
545 break;
546 #ifdef HAVE_DLG_TRACE
547 case CHR_TRACE:
548 dlg_trace_win(win);
549 break;
550 #endif
551 }
552 } else {
553 handle_others = TRUE;
554 }
555
556 if (handle_others) {
557 if ((p = dialog_state.getc_redirect) != 0) {
558 if (!(p->handle_getc(p, ch, *fkey, &result))) {
559 done = (p->win == save_win) && (!p->keep_win);
560 dlg_remove_callback(p);
561 dialog_state.getc_redirect = 0;
562 win = save_win;
563 }
564 } else {
565 done = TRUE;
566 }
567 }
568 }
569 if (literal)
570 keypad(win, TRUE);
571 return ch;
572 }
573
574 static void
finish_bg(int sig GCC_UNUSED)575 finish_bg(int sig GCC_UNUSED)
576 {
577 end_dialog();
578 dlg_exit(DLG_EXIT_ERROR);
579 }
580
581 /*
582 * If we have callbacks active, purge the list of all that are not marked
583 * to keep in the background. If any remain, run those in a background
584 * process.
585 */
586 void
dlg_killall_bg(int * retval)587 dlg_killall_bg(int *retval)
588 {
589 DIALOG_CALLBACK *cb;
590 int pid;
591 #ifdef HAVE_TYPE_UNIONWAIT
592 union wait wstatus;
593 #else
594 int wstatus;
595 #endif
596
597 if ((cb = dialog_state.getc_callbacks) != 0) {
598 while (cb != 0) {
599 if (cb->keep_bg) {
600 cb = cb->next;
601 } else {
602 dlg_remove_callback(cb);
603 cb = dialog_state.getc_callbacks;
604 }
605 }
606 if (dialog_state.getc_callbacks != 0) {
607
608 refresh();
609 fflush(stdout);
610 fflush(stderr);
611 reset_shell_mode();
612 if ((pid = fork()) != 0) {
613 _exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR);
614 } else if (pid == 0) { /* child */
615 if ((pid = fork()) != 0) {
616 /*
617 * Echo the process-id of the grandchild so a shell script
618 * can read that, and kill that process. We'll wait around
619 * until then. Our parent has already left, leaving us
620 * temporarily orphaned.
621 */
622 if (pid > 0) { /* parent */
623 fprintf(stderr, "%d\n", pid);
624 fflush(stderr);
625 }
626 /* wait for child */
627 #ifdef HAVE_WAITPID
628 while (-1 == waitpid(pid, &wstatus, 0)) {
629 #ifdef EINTR
630 if (errno == EINTR)
631 continue;
632 #endif /* EINTR */
633 #ifdef ERESTARTSYS
634 if (errno == ERESTARTSYS)
635 continue;
636 #endif /* ERESTARTSYS */
637 break;
638 }
639 #else
640 while (wait(&wstatus) != pid) /* do nothing */
641 ;
642 #endif
643 _exit(WEXITSTATUS(wstatus));
644 } else if (pid == 0) {
645 if (!dialog_vars.cant_kill)
646 (void) signal(SIGHUP, finish_bg);
647 (void) signal(SIGINT, finish_bg);
648 (void) signal(SIGQUIT, finish_bg);
649 (void) signal(SIGSEGV, finish_bg);
650 while (dialog_state.getc_callbacks != 0) {
651 int fkey = 0;
652 dlg_getc_callbacks(ERR, fkey, retval);
653 napms(1000);
654 }
655 }
656 }
657 }
658 }
659 }
660