xref: /freebsd/contrib/dialog/ui_getc.c (revision aa0a1e58f0189b0fde359a8bda032887e72057fa)
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 
66 void
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
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
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  * FIXME: this could be replaced by a select/poll on several file descriptors
123  */
124 static int
125 dlg_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 
142 int
143 dlg_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 
162 static void
163 dlg_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  */
175 static int last_getc = ERR;
176 
177 #ifdef USE_WIDE_CURSES
178 static char last_getc_bytes[80];
179 static int have_last_getc;
180 static int used_last_getc;
181 #endif
182 
183 int
184 dlg_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 
193 void
194 dlg_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  */
207 static bool
208 valid_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  */
228 int
229 dlg_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 
390 static void
391 finish_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  */
402 void
403 dlg_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