ui_getc.c revision 3d4e8889889e5e36302454225999f7e146d3219c
1/*
2 *  $Id: ui_getc.c,v 1.48 2010/01/18 10:24:06 tom Exp $
3 *
4 * ui_getc.c - user interface glue for getc()
5 *
6 * Copyright 2001-2009,2010 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 * FIXME: this could be replaced by a select/poll on several file descriptors
123 */
124static int
125dlg_getc_ready(DIALOG_CALLBACK * p)
126{
127    fd_set read_fds;
128    int fd = fileno(p->input);
129    struct timeval test;
130
131    FD_ZERO(&read_fds);
132    FD_SET(fd, &read_fds);
133
134    test.tv_sec = 0;		/* Seconds.  */
135    test.tv_usec = (isatty(fd)	/* Microseconds.  */
136		    ? (WTIMEOUT_VAL * 1000)
137		    : 1);
138    return (select(fd + 1, &read_fds, (fd_set *) 0, (fd_set *) 0, &test) == 1)
139	&& (FD_ISSET(fd, &read_fds));
140}
141
142int
143dlg_getc_callbacks(int ch, int fkey, int *result)
144{
145    int code = FALSE;
146    DIALOG_CALLBACK *p, *q;
147
148    if ((p = dialog_state.getc_callbacks) != 0) {
149	do {
150	    q = p->next;
151	    if (dlg_getc_ready(p)) {
152		if (!(p->handle_getc(p, ch, fkey, result))) {
153		    dlg_remove_callback(p);
154		}
155	    }
156	} while ((p = q) != 0);
157	code = (dialog_state.getc_callbacks != 0);
158    }
159    return code;
160}
161
162static void
163dlg_raise_window(WINDOW *win)
164{
165    touchwin(win);
166    wmove(win, getcury(win), getcurx(win));
167    wnoutrefresh(win);
168    doupdate();
169}
170
171/*
172 * This is a work-around for the case where we actually need the wide-character
173 * code versus a byte stream.
174 */
175static int last_getc = ERR;
176
177#ifdef USE_WIDE_CURSES
178static char last_getc_bytes[80];
179static int have_last_getc;
180static int used_last_getc;
181#endif
182
183int
184dlg_last_getc(void)
185{
186#ifdef USE_WIDE_CURSES
187    if (used_last_getc != 1)
188	return ERR;		/* not really an error... */
189#endif
190    return last_getc;
191}
192
193void
194dlg_flush_getc(void)
195{
196    last_getc = ERR;
197#ifdef USE_WIDE_CURSES
198    have_last_getc = 0;
199    used_last_getc = 0;
200#endif
201}
202
203/*
204 * Check if the stream has been unexpectedly closed, returning false in that
205 * case.
206 */
207static bool
208valid_file(FILE *fp)
209{
210    bool code = FALSE;
211    int fd = fileno(fp);
212
213    if (fd >= 0) {
214	long result = 0;
215	if ((result = fcntl(fd, F_GETFL, 0)) >= 0) {
216	    code = TRUE;
217	}
218    }
219    return code;
220}
221
222/*
223 * Read a character from the given window.  Handle repainting here (to simplify
224 * things in the calling application).  Also, if input-callback(s) are set up,
225 * poll the corresponding files and handle the updates, e.g., for displaying a
226 * tailbox.
227 */
228int
229dlg_getc(WINDOW *win, int *fkey)
230{
231    WINDOW *save_win = win;
232    int ch = ERR;
233    int before_lookup;
234    int result;
235    bool done = FALSE;
236    bool literal = FALSE;
237    DIALOG_CALLBACK *p;
238    int interval = dialog_vars.timeout_secs;
239    time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs;
240    time_t current;
241
242    if (dialog_state.getc_callbacks != 0)
243	wtimeout(win, WTIMEOUT_VAL);
244    else if (interval > 0)
245	wtimeout(win, interval);
246
247    while (!done) {
248#ifdef USE_WIDE_CURSES
249	int code;
250	mbstate_t state;
251	wchar_t my_wchar;
252	wint_t my_wint;
253
254	/*
255	 * We get a wide character, translate it to multibyte form to avoid
256	 * having to change the rest of the code to use wide-characters.
257	 */
258	if (used_last_getc >= have_last_getc) {
259	    used_last_getc = 0;
260	    have_last_getc = 0;
261	    ch = ERR;
262	    *fkey = 0;
263	    code = wget_wch(win, &my_wint);
264	    my_wchar = (wchar_t) my_wint;
265	    switch (code) {
266	    case KEY_CODE_YES:
267		ch = *fkey = my_wchar;
268		last_getc = my_wchar;
269		break;
270	    case OK:
271		memset(&state, 0, sizeof(state));
272		have_last_getc = (int) wcrtomb(last_getc_bytes, my_wchar, &state);
273		if (have_last_getc < 0) {
274		    have_last_getc = used_last_getc = 0;
275		    last_getc_bytes[0] = (char) my_wchar;
276		}
277		ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
278		last_getc = my_wchar;
279		break;
280	    case ERR:
281		ch = ERR;
282		last_getc = ERR;
283		break;
284	    default:
285		break;
286	    }
287	} else {
288	    ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
289	}
290#else
291	ch = wgetch(win);
292	last_getc = ch;
293	*fkey = (ch > KEY_MIN && ch < KEY_MAX);
294#endif
295	if (literal) {
296	    done = TRUE;
297	    continue;
298	}
299
300	before_lookup = ch;
301	ch = dlg_lookup_key(win, ch, fkey);
302	dlg_trace_chr(ch, *fkey);
303
304	current = time((time_t *) 0);
305
306	switch (ch) {
307	case CHR_LITERAL:
308	    if (!literal) {
309		literal = TRUE;
310		keypad(win, FALSE);
311		continue;
312	    }
313	    break;
314	case CHR_REPAINT:
315	    (void) touchwin(win);
316	    (void) wrefresh(curscr);
317	    break;
318	case ERR:		/* wtimeout() in effect; check for file I/O */
319	    if (interval > 0
320		&& current >= expired) {
321		dlg_exiterr("timeout");
322	    }
323	    if (dlg_getc_callbacks(ch, *fkey, &result)) {
324		dlg_raise_window(win);
325	    } else {
326		done = (interval <= 0);
327	    }
328	    if (!valid_file(stdin)
329		|| !valid_file(dialog_state.screen_output)) {
330		ch = ESC;
331		done = TRUE;
332	    }
333	    break;
334	case DLGK_FIELD_NEXT:
335	    /* FALLTHRU */
336	case TAB:
337	    /* Handle tab as a special case for traversing between the nominal
338	     * "current" window, and other windows having callbacks.  If the
339	     * nominal (control) window closes, we'll close the windows with
340	     * callbacks.
341	     */
342	    if (dialog_state.getc_callbacks != 0 &&
343		before_lookup == TAB) {
344		if ((p = dialog_state.getc_redirect) != 0) {
345		    p = p->next;
346		} else {
347		    p = dialog_state.getc_callbacks;
348		}
349		if ((dialog_state.getc_redirect = p) != 0) {
350		    win = p->win;
351		} else {
352		    win = save_win;
353		}
354		dlg_raise_window(win);
355		break;
356	    }
357	    /* FALLTHRU */
358	default:
359#ifdef NO_LEAKS
360	    if (before_lookup == DLG_CTRL('P')) {
361		/* for testing, ^P closes the connection */
362		close(0);
363		close(1);
364		close(2);
365		break;
366	    }
367#endif
368	    if ((p = dialog_state.getc_redirect) != 0) {
369		if (!(p->handle_getc(p, ch, *fkey, &result))) {
370		    dlg_remove_callback(p);
371		    dialog_state.getc_redirect = 0;
372		    win = save_win;
373		}
374	    } else {
375		done = TRUE;
376	    }
377	    break;
378#ifdef HAVE_DLG_TRACE
379	case CHR_TRACE:
380	    dlg_trace_win(win);
381	    break;
382#endif
383	}
384    }
385    if (literal)
386	keypad(win, TRUE);
387    return ch;
388}
389
390static void
391finish_bg(int sig GCC_UNUSED)
392{
393    end_dialog();
394    dlg_exit(DLG_EXIT_ERROR);
395}
396
397/*
398 * If we have callbacks active, purge the list of all that are not marked
399 * to keep in the background.  If any remain, run those in a background
400 * process.
401 */
402void
403dlg_killall_bg(int *retval)
404{
405    DIALOG_CALLBACK *cb;
406    int pid;
407#ifdef HAVE_TYPE_UNIONWAIT
408    union wait wstatus;
409#else
410    int wstatus;
411#endif
412
413    if ((cb = dialog_state.getc_callbacks) != 0) {
414	while (cb != 0) {
415	    if (cb->keep_bg) {
416		cb = cb->next;
417	    } else {
418		dlg_remove_callback(cb);
419		cb = dialog_state.getc_callbacks;
420	    }
421	}
422	if (dialog_state.getc_callbacks != 0) {
423
424	    refresh();
425	    fflush(stdout);
426	    fflush(stderr);
427	    reset_shell_mode();
428	    if ((pid = fork()) != 0) {
429		_exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR);
430	    } else if (pid == 0) {	/* child */
431		if ((pid = fork()) != 0) {
432		    /*
433		     * Echo the process-id of the grandchild so a shell script
434		     * can read that, and kill that process.  We'll wait around
435		     * until then.  Our parent has already left, leaving us
436		     * temporarily orphaned.
437		     */
438		    if (pid > 0) {	/* parent */
439			fprintf(stderr, "%d\n", pid);
440			fflush(stderr);
441		    }
442		    /* wait for child */
443#ifdef HAVE_WAITPID
444		    while (-1 == waitpid(pid, &wstatus, 0)) {
445#ifdef EINTR
446			if (errno == EINTR)
447			    continue;
448#endif /* EINTR */
449#ifdef ERESTARTSYS
450			if (errno == ERESTARTSYS)
451			    continue;
452#endif /* ERESTARTSYS */
453			break;
454		    }
455#else
456		    while (wait(&wstatus) != pid)	/* do nothing */
457			;
458#endif
459		    _exit(WEXITSTATUS(wstatus));
460		} else if (pid == 0) {
461		    if (!dialog_vars.cant_kill)
462			(void) signal(SIGHUP, finish_bg);
463		    (void) signal(SIGINT, finish_bg);
464		    (void) signal(SIGQUIT, finish_bg);
465		    (void) signal(SIGSEGV, finish_bg);
466		    while (dialog_state.getc_callbacks != 0) {
467			int fkey = 0;
468			dlg_getc_callbacks(ERR, fkey, retval);
469			napms(1000);
470		    }
471		}
472	    }
473	}
474    }
475}
476