ui_getc.c revision 251843
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
66void
67dlg_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 */
78void
79dlg_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
86void
87dlg_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 */
125static bool
126handle_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
155static bool
156may_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 */
177static int
178check_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
223int
224dlg_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
245static void
246dlg_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 */
258static int last_getc = ERR;
259
260#ifdef USE_WIDE_CURSES
261static char last_getc_bytes[80];
262static int have_last_getc;
263static int used_last_getc;
264#endif
265
266int
267dlg_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
276void
277dlg_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 */
294void
295dlg_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 */
320static bool
321valid_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
334static int
335really_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
388static DIALOG_CALLBACK *
389next_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
399static DIALOG_CALLBACK *
400prev_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 */
426int
427dlg_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
574static void
575finish_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 */
586void
587dlg_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