xref: /freebsd/contrib/dialog/ui_getc.c (revision 884a2a699669ec61e2366e3e358342dbc94be24a)
1 /*
2  *  $Id: ui_getc.c,v 1.59 2011/02/28 10:56:15 tom Exp $
3  *
4  *  ui_getc.c - user interface glue for getc()
5  *
6  *  Copyright 2001-2010,2011	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  * A select() might find more than one input ready for service.  Handle them
123  * all.
124  */
125 static bool
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
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
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
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
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
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
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  * Check if the stream has been unexpectedly closed, returning false in that
288  * case.
289  */
290 static bool
291 valid_file(FILE *fp)
292 {
293     bool code = FALSE;
294     int fd = fileno(fp);
295 
296     if (fd >= 0) {
297 	long result = 0;
298 	if ((result = fcntl(fd, F_GETFL, 0)) >= 0) {
299 	    code = TRUE;
300 	}
301     }
302     return code;
303 }
304 
305 static int
306 really_getch(WINDOW *win, int *fkey)
307 {
308     int ch;
309 #ifdef USE_WIDE_CURSES
310     int code;
311     mbstate_t state;
312     wchar_t my_wchar;
313     wint_t my_wint;
314 
315     /*
316      * We get a wide character, translate it to multibyte form to avoid
317      * having to change the rest of the code to use wide-characters.
318      */
319     if (used_last_getc >= have_last_getc) {
320 	used_last_getc = 0;
321 	have_last_getc = 0;
322 	ch = ERR;
323 	*fkey = 0;
324 	code = wget_wch(win, &my_wint);
325 	my_wchar = (wchar_t) my_wint;
326 	switch (code) {
327 	case KEY_CODE_YES:
328 	    ch = *fkey = my_wchar;
329 	    last_getc = my_wchar;
330 	    break;
331 	case OK:
332 	    memset(&state, 0, sizeof(state));
333 	    have_last_getc = (int) wcrtomb(last_getc_bytes, my_wchar, &state);
334 	    if (have_last_getc < 0) {
335 		have_last_getc = used_last_getc = 0;
336 		last_getc_bytes[0] = (char) my_wchar;
337 	    }
338 	    ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
339 	    last_getc = my_wchar;
340 	    break;
341 	case ERR:
342 	    ch = ERR;
343 	    last_getc = ERR;
344 	    break;
345 	default:
346 	    break;
347 	}
348     } else {
349 	ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
350     }
351 #else
352     ch = wgetch(win);
353     last_getc = ch;
354     *fkey = (ch > KEY_MIN && ch < KEY_MAX);
355 #endif
356     return ch;
357 }
358 
359 static DIALOG_CALLBACK *
360 next_callback(DIALOG_CALLBACK * p)
361 {
362     if ((p = dialog_state.getc_redirect) != 0) {
363 	p = p->next;
364     } else {
365 	p = dialog_state.getc_callbacks;
366     }
367     return p;
368 }
369 
370 static DIALOG_CALLBACK *
371 prev_callback(DIALOG_CALLBACK * p)
372 {
373     DIALOG_CALLBACK *q;
374 
375     if ((p = dialog_state.getc_redirect) != 0) {
376 	if (p == dialog_state.getc_callbacks) {
377 	    for (p = dialog_state.getc_callbacks; p->next != 0; p = p->next) ;
378 	} else {
379 	    for (q = dialog_state.getc_callbacks; q->next != p; q = q->next) ;
380 	    p = q;
381 	}
382     } else {
383 	p = dialog_state.getc_callbacks;
384     }
385     return p;
386 }
387 
388 /*
389  * Read a character from the given window.  Handle repainting here (to simplify
390  * things in the calling application).  Also, if input-callback(s) are set up,
391  * poll the corresponding files and handle the updates, e.g., for displaying a
392  * tailbox.
393  */
394 int
395 dlg_getc(WINDOW *win, int *fkey)
396 {
397     WINDOW *save_win = win;
398     int ch = ERR;
399     int before_lookup;
400     int result;
401     bool done = FALSE;
402     bool literal = FALSE;
403     DIALOG_CALLBACK *p = 0;
404     int interval = (dialog_vars.timeout_secs * 1000);
405     time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs;
406     time_t current;
407 
408     if (may_handle_inputs())
409 	wtimeout(win, WTIMEOUT_VAL);
410     else if (interval > 0)
411 	wtimeout(win, interval);
412 
413     while (!done) {
414 	/*
415 	 * If there was no pending file-input, check the keyboard.
416 	 */
417 	ch = really_getch(win, fkey);
418 	if (literal) {
419 	    done = TRUE;
420 	    continue;
421 	}
422 
423 	before_lookup = ch;
424 	ch = dlg_lookup_key(win, ch, fkey);
425 	dlg_trace_chr(ch, *fkey);
426 
427 	current = time((time_t *) 0);
428 
429 	switch (ch) {
430 	case CHR_LITERAL:
431 	    if (!literal) {
432 		literal = TRUE;
433 		keypad(win, FALSE);
434 		continue;
435 	    }
436 	    break;
437 	case CHR_REPAINT:
438 	    (void) touchwin(win);
439 	    (void) wrefresh(curscr);
440 	    break;
441 	case ERR:		/* wtimeout() in effect; check for file I/O */
442 	    if (interval > 0
443 		&& current >= expired) {
444 		dlg_exiterr("timeout");
445 	    }
446 	    if (!valid_file(stdin)
447 		|| !valid_file(dialog_state.screen_output)) {
448 		ch = ESC;
449 		done = TRUE;
450 	    } else if (check_inputs()) {
451 		if (handle_inputs(win))
452 		    dlg_raise_window(win);
453 		else
454 		    done = TRUE;
455 	    } else {
456 		done = (interval <= 0);
457 	    }
458 	    break;
459 	case DLGK_FIELD_PREV:
460 	    /* FALLTHRU */
461 	case KEY_BTAB:
462 	    /* FALLTHRU */
463 	case DLGK_FIELD_NEXT:
464 	    /* FALLTHRU */
465 	case TAB:
466 	    /* Handle tab/backtab as a special case for traversing between the
467 	     * nominal "current" window, and other windows having callbacks.
468 	     * If the nominal (control) window closes, we'll close the windows
469 	     * with callbacks.
470 	     */
471 	    if (dialog_state.getc_callbacks != 0 &&
472 		(before_lookup == TAB ||
473 		 before_lookup == KEY_BTAB)) {
474 		if (before_lookup == TAB)
475 		    p = next_callback(p);
476 		else
477 		    p = prev_callback(p);
478 		if ((dialog_state.getc_redirect = p) != 0) {
479 		    win = p->win;
480 		} else {
481 		    win = save_win;
482 		}
483 		dlg_raise_window(win);
484 		break;
485 	    }
486 	    /* FALLTHRU */
487 	default:
488 #ifdef NO_LEAKS
489 	    if (before_lookup == DLG_CTRL('P')) {
490 		/* for testing, ^P closes the connection */
491 		close(0);
492 		close(1);
493 		close(2);
494 		break;
495 	    }
496 #endif
497 	    if ((p = dialog_state.getc_redirect) != 0) {
498 		if (!(p->handle_getc(p, ch, *fkey, &result))) {
499 		    dlg_remove_callback(p);
500 		    dialog_state.getc_redirect = 0;
501 		    win = save_win;
502 		}
503 	    } else {
504 		done = TRUE;
505 	    }
506 	    break;
507 #ifdef HAVE_DLG_TRACE
508 	case CHR_TRACE:
509 	    dlg_trace_win(win);
510 	    break;
511 #endif
512 	}
513     }
514     if (literal)
515 	keypad(win, TRUE);
516     return ch;
517 }
518 
519 static void
520 finish_bg(int sig GCC_UNUSED)
521 {
522     end_dialog();
523     dlg_exit(DLG_EXIT_ERROR);
524 }
525 
526 /*
527  * If we have callbacks active, purge the list of all that are not marked
528  * to keep in the background.  If any remain, run those in a background
529  * process.
530  */
531 void
532 dlg_killall_bg(int *retval)
533 {
534     DIALOG_CALLBACK *cb;
535     int pid;
536 #ifdef HAVE_TYPE_UNIONWAIT
537     union wait wstatus;
538 #else
539     int wstatus;
540 #endif
541 
542     if ((cb = dialog_state.getc_callbacks) != 0) {
543 	while (cb != 0) {
544 	    if (cb->keep_bg) {
545 		cb = cb->next;
546 	    } else {
547 		dlg_remove_callback(cb);
548 		cb = dialog_state.getc_callbacks;
549 	    }
550 	}
551 	if (dialog_state.getc_callbacks != 0) {
552 
553 	    refresh();
554 	    fflush(stdout);
555 	    fflush(stderr);
556 	    reset_shell_mode();
557 	    if ((pid = fork()) != 0) {
558 		_exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR);
559 	    } else if (pid == 0) {	/* child */
560 		if ((pid = fork()) != 0) {
561 		    /*
562 		     * Echo the process-id of the grandchild so a shell script
563 		     * can read that, and kill that process.  We'll wait around
564 		     * until then.  Our parent has already left, leaving us
565 		     * temporarily orphaned.
566 		     */
567 		    if (pid > 0) {	/* parent */
568 			fprintf(stderr, "%d\n", pid);
569 			fflush(stderr);
570 		    }
571 		    /* wait for child */
572 #ifdef HAVE_WAITPID
573 		    while (-1 == waitpid(pid, &wstatus, 0)) {
574 #ifdef EINTR
575 			if (errno == EINTR)
576 			    continue;
577 #endif /* EINTR */
578 #ifdef ERESTARTSYS
579 			if (errno == ERESTARTSYS)
580 			    continue;
581 #endif /* ERESTARTSYS */
582 			break;
583 		    }
584 #else
585 		    while (wait(&wstatus) != pid)	/* do nothing */
586 			;
587 #endif
588 		    _exit(WEXITSTATUS(wstatus));
589 		} else if (pid == 0) {
590 		    if (!dialog_vars.cant_kill)
591 			(void) signal(SIGHUP, finish_bg);
592 		    (void) signal(SIGINT, finish_bg);
593 		    (void) signal(SIGQUIT, finish_bg);
594 		    (void) signal(SIGSEGV, finish_bg);
595 		    while (dialog_state.getc_callbacks != 0) {
596 			int fkey = 0;
597 			dlg_getc_callbacks(ERR, fkey, retval);
598 			napms(1000);
599 		    }
600 		}
601 	    }
602 	}
603     }
604 }
605