freebsd-nq/contrib/dialog/ui_getc.c
Nathan Whitehorn 1971864966 Revert r241818 that updated dialog to 20120706. This turns out to horribly
break mixed form dialogs in conjunction with the FreeBSD termcap, making
the bsdinstall partition editor Add dialog, among other things, completely
nonfunctional. This restores dialog 20110707.
2012-12-30 04:22:34 +00:00

633 lines
13 KiB
C

/*
* $Id: ui_getc.c,v 1.63 2011/07/07 22:05:58 tom Exp $
*
* ui_getc.c - user interface glue for getc()
*
* Copyright 2001-2010,2011 Thomas E. Dickey
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License, version 2.1
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to
* Free Software Foundation, Inc.
* 51 Franklin St., Fifth Floor
* Boston, MA 02110, USA.
*/
#include <dialog.h>
#include <dlg_keys.h>
#ifdef NEED_WCHAR_H
#include <wchar.h>
#endif
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef __QNX__
#include <sys/select.h>
#endif
#ifndef WEXITSTATUS
# ifdef HAVE_TYPE_UNIONWAIT
# define WEXITSTATUS(status) (status.w_retcode)
# else
# define WEXITSTATUS(status) (((status) & 0xff00) >> 8)
# endif
#endif
#ifndef WTERMSIG
# ifdef HAVE_TYPE_UNIONWAIT
# define WTERMSIG(status) (status.w_termsig)
# else
# define WTERMSIG(status) ((status) & 0x7f)
# endif
#endif
void
dlg_add_callback(DIALOG_CALLBACK * p)
{
p->next = dialog_state.getc_callbacks;
dialog_state.getc_callbacks = p;
wtimeout(p->win, WTIMEOUT_VAL);
}
/*
* Like dlg_add_callback(), but providing for cleanup of caller's associated
* state.
*/
void
dlg_add_callback_ref(DIALOG_CALLBACK ** p, DIALOG_FREEBACK freeback)
{
(*p)->caller = p;
(*p)->freeback = freeback;
dlg_add_callback(*p);
}
void
dlg_remove_callback(DIALOG_CALLBACK * p)
{
DIALOG_CALLBACK *q;
if (p->input != 0) {
fclose(p->input);
if (p->input == dialog_state.pipe_input)
dialog_state.pipe_input = 0;
p->input = 0;
}
if (!(p->keep_win))
dlg_del_window(p->win);
if ((q = dialog_state.getc_callbacks) == p) {
dialog_state.getc_callbacks = p->next;
} else {
while (q != 0) {
if (q->next == p) {
q->next = p->next;
break;
}
q = q->next;
}
}
/* handle dlg_add_callback_ref cleanup */
if (p->freeback != 0)
p->freeback(p);
if (p->caller != 0)
*(p->caller) = 0;
free(p);
}
/*
* A select() might find more than one input ready for service. Handle them
* all.
*/
static bool
handle_inputs(WINDOW *win)
{
bool result = FALSE;
DIALOG_CALLBACK *p;
DIALOG_CALLBACK *q;
int cur_y, cur_x;
int state = ERR;
getyx(win, cur_y, cur_x);
for (p = dialog_state.getc_callbacks, q = 0; p != 0; p = q) {
q = p->next;
if ((p->handle_input != 0) && p->input_ready) {
p->input_ready = FALSE;
if (state == ERR) {
state = curs_set(0);
}
if (p->handle_input(p)) {
result = TRUE;
}
}
}
if (result) {
(void) wmove(win, cur_y, cur_x); /* Restore cursor position */
wrefresh(win);
curs_set(state);
}
return result;
}
static bool
may_handle_inputs(void)
{
bool result = FALSE;
DIALOG_CALLBACK *p;
for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
if (p->input != 0) {
result = TRUE;
break;
}
}
return result;
}
/*
* Check any any inputs registered via callbacks, to see if there is any input
* available. If there is, return a file-descriptor which should be read.
* Otherwise, return -1.
*/
static int
check_inputs(void)
{
DIALOG_CALLBACK *p;
fd_set read_fds;
struct timeval test;
int last_fd = -1;
int fd;
int found;
int result = -1;
if ((p = dialog_state.getc_callbacks) != 0) {
FD_ZERO(&read_fds);
while (p != 0) {
p->input_ready = FALSE;
if (p->input != 0 && (fd = fileno(p->input)) >= 0) {
FD_SET(fd, &read_fds);
if (last_fd < fd)
last_fd = fd;
}
p = p->next;
}
test.tv_sec = 0;
test.tv_usec = WTIMEOUT_VAL * 1000;
found = select(last_fd + 1, &read_fds,
(fd_set *) 0,
(fd_set *) 0,
&test);
if (found > 0) {
for (p = dialog_state.getc_callbacks; p != 0; p = p->next) {
if (p->input != 0
&& (fd = fileno(p->input)) >= 0
&& FD_ISSET(fd, &read_fds)) {
p->input_ready = TRUE;
result = fd;
}
}
}
}
return result;
}
int
dlg_getc_callbacks(int ch, int fkey, int *result)
{
int code = FALSE;
DIALOG_CALLBACK *p, *q;
if ((p = dialog_state.getc_callbacks) != 0) {
if (check_inputs() >= 0) {
do {
q = p->next;
if (p->input_ready) {
if (!(p->handle_getc(p, ch, fkey, result))) {
dlg_remove_callback(p);
}
}
} while ((p = q) != 0);
}
code = (dialog_state.getc_callbacks != 0);
}
return code;
}
static void
dlg_raise_window(WINDOW *win)
{
touchwin(win);
wmove(win, getcury(win), getcurx(win));
wnoutrefresh(win);
doupdate();
}
/*
* This is a work-around for the case where we actually need the wide-character
* code versus a byte stream.
*/
static int last_getc = ERR;
#ifdef USE_WIDE_CURSES
static char last_getc_bytes[80];
static int have_last_getc;
static int used_last_getc;
#endif
int
dlg_last_getc(void)
{
#ifdef USE_WIDE_CURSES
if (used_last_getc != 1)
return ERR; /* not really an error... */
#endif
return last_getc;
}
void
dlg_flush_getc(void)
{
last_getc = ERR;
#ifdef USE_WIDE_CURSES
have_last_getc = 0;
used_last_getc = 0;
#endif
}
/*
* Check if the stream has been unexpectedly closed, returning false in that
* case.
*/
static bool
valid_file(FILE *fp)
{
bool code = FALSE;
int fd = fileno(fp);
if (fd >= 0) {
long result = 0;
if ((result = fcntl(fd, F_GETFL, 0)) >= 0) {
code = TRUE;
}
}
return code;
}
static int
really_getch(WINDOW *win, int *fkey)
{
int ch;
#ifdef USE_WIDE_CURSES
int code;
mbstate_t state;
wchar_t my_wchar;
wint_t my_wint;
/*
* We get a wide character, translate it to multibyte form to avoid
* having to change the rest of the code to use wide-characters.
*/
if (used_last_getc >= have_last_getc) {
used_last_getc = 0;
have_last_getc = 0;
ch = ERR;
*fkey = 0;
code = wget_wch(win, &my_wint);
my_wchar = (wchar_t) my_wint;
switch (code) {
case KEY_CODE_YES:
ch = *fkey = my_wchar;
last_getc = my_wchar;
break;
case OK:
memset(&state, 0, sizeof(state));
have_last_getc = (int) wcrtomb(last_getc_bytes, my_wchar, &state);
if (have_last_getc < 0) {
have_last_getc = used_last_getc = 0;
last_getc_bytes[0] = (char) my_wchar;
}
ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
last_getc = my_wchar;
break;
case ERR:
ch = ERR;
last_getc = ERR;
break;
default:
break;
}
} else {
ch = (int) CharOf(last_getc_bytes[used_last_getc++]);
}
#else
ch = wgetch(win);
last_getc = ch;
*fkey = (ch > KEY_MIN && ch < KEY_MAX);
#endif
return ch;
}
static DIALOG_CALLBACK *
next_callback(DIALOG_CALLBACK * p)
{
if ((p = dialog_state.getc_redirect) != 0) {
p = p->next;
} else {
p = dialog_state.getc_callbacks;
}
return p;
}
static DIALOG_CALLBACK *
prev_callback(DIALOG_CALLBACK * p)
{
DIALOG_CALLBACK *q;
if ((p = dialog_state.getc_redirect) != 0) {
if (p == dialog_state.getc_callbacks) {
for (p = dialog_state.getc_callbacks; p->next != 0; p = p->next) ;
} else {
for (q = dialog_state.getc_callbacks; q->next != p; q = q->next) ;
p = q;
}
} else {
p = dialog_state.getc_callbacks;
}
return p;
}
#define isBeforeChr(chr) ((chr) == before_chr && !before_fkey)
#define isBeforeFkey(chr) ((chr) == before_chr && before_fkey)
/*
* Read a character from the given window. Handle repainting here (to simplify
* things in the calling application). Also, if input-callback(s) are set up,
* poll the corresponding files and handle the updates, e.g., for displaying a
* tailbox.
*/
int
dlg_getc(WINDOW *win, int *fkey)
{
WINDOW *save_win = win;
int ch = ERR;
int before_chr;
int before_fkey;
int result;
bool done = FALSE;
bool literal = FALSE;
DIALOG_CALLBACK *p = 0;
int interval = (dialog_vars.timeout_secs * 1000);
time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs;
time_t current;
if (may_handle_inputs())
wtimeout(win, WTIMEOUT_VAL);
else if (interval > 0)
wtimeout(win, interval);
while (!done) {
bool handle_others = FALSE;
/*
* If there was no pending file-input, check the keyboard.
*/
ch = really_getch(win, fkey);
if (literal) {
done = TRUE;
continue;
}
before_chr = ch;
before_fkey = *fkey;
ch = dlg_lookup_key(win, ch, fkey);
dlg_trace_chr(ch, *fkey);
current = time((time_t *) 0);
/*
* If we acquired a fkey value, then it is one of dialog's builtin
* codes such as DLGK_HELPFILE.
*/
if (!*fkey || *fkey != before_fkey) {
switch (ch) {
case CHR_LITERAL:
if (!literal) {
literal = TRUE;
keypad(win, FALSE);
continue;
}
break;
case CHR_REPAINT:
(void) touchwin(win);
(void) wrefresh(curscr);
break;
case ERR: /* wtimeout() in effect; check for file I/O */
if (interval > 0
&& current >= expired) {
dlg_exiterr("timeout");
}
if (!valid_file(stdin)
|| !valid_file(dialog_state.screen_output)) {
ch = ESC;
done = TRUE;
} else if (check_inputs()) {
if (handle_inputs(win))
dlg_raise_window(win);
else
done = TRUE;
} else {
done = (interval <= 0);
}
break;
case DLGK_HELPFILE:
if (dialog_vars.help_file) {
int yold, xold;
getyx(win, yold, xold);
dialog_helpfile("HELP", dialog_vars.help_file, 0, 0);
dlg_raise_window(win);
wmove(win, yold, xold);
}
continue;
case DLGK_FIELD_PREV:
/* FALLTHRU */
case KEY_BTAB:
/* FALLTHRU */
case DLGK_FIELD_NEXT:
/* FALLTHRU */
case TAB:
/* Handle tab/backtab as a special case for traversing between
* the nominal "current" window, and other windows having
* callbacks. If the nominal (control) window closes, we'll
* close the windows with callbacks.
*/
if (dialog_state.getc_callbacks != 0 &&
(isBeforeChr(TAB) ||
isBeforeFkey(KEY_BTAB))) {
p = (isBeforeChr(TAB)
? next_callback(p)
: prev_callback(p));
if ((dialog_state.getc_redirect = p) != 0) {
win = p->win;
} else {
win = save_win;
}
dlg_raise_window(win);
break;
}
/* FALLTHRU */
default:
#ifdef NO_LEAKS
if (isBeforeChr(DLG_CTRL('P'))) {
/* for testing, ^P closes the connection */
close(0);
close(1);
close(2);
break;
}
#endif
handle_others = TRUE;
break;
#ifdef HAVE_DLG_TRACE
case CHR_TRACE:
dlg_trace_win(win);
break;
#endif
}
} else {
handle_others = TRUE;
}
if (handle_others) {
if ((p = dialog_state.getc_redirect) != 0) {
if (!(p->handle_getc(p, ch, *fkey, &result))) {
dlg_remove_callback(p);
dialog_state.getc_redirect = 0;
win = save_win;
}
} else {
done = TRUE;
}
}
}
if (literal)
keypad(win, TRUE);
return ch;
}
static void
finish_bg(int sig GCC_UNUSED)
{
end_dialog();
dlg_exit(DLG_EXIT_ERROR);
}
/*
* If we have callbacks active, purge the list of all that are not marked
* to keep in the background. If any remain, run those in a background
* process.
*/
void
dlg_killall_bg(int *retval)
{
DIALOG_CALLBACK *cb;
int pid;
#ifdef HAVE_TYPE_UNIONWAIT
union wait wstatus;
#else
int wstatus;
#endif
if ((cb = dialog_state.getc_callbacks) != 0) {
while (cb != 0) {
if (cb->keep_bg) {
cb = cb->next;
} else {
dlg_remove_callback(cb);
cb = dialog_state.getc_callbacks;
}
}
if (dialog_state.getc_callbacks != 0) {
refresh();
fflush(stdout);
fflush(stderr);
reset_shell_mode();
if ((pid = fork()) != 0) {
_exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR);
} else if (pid == 0) { /* child */
if ((pid = fork()) != 0) {
/*
* Echo the process-id of the grandchild so a shell script
* can read that, and kill that process. We'll wait around
* until then. Our parent has already left, leaving us
* temporarily orphaned.
*/
if (pid > 0) { /* parent */
fprintf(stderr, "%d\n", pid);
fflush(stderr);
}
/* wait for child */
#ifdef HAVE_WAITPID
while (-1 == waitpid(pid, &wstatus, 0)) {
#ifdef EINTR
if (errno == EINTR)
continue;
#endif /* EINTR */
#ifdef ERESTARTSYS
if (errno == ERESTARTSYS)
continue;
#endif /* ERESTARTSYS */
break;
}
#else
while (wait(&wstatus) != pid) /* do nothing */
;
#endif
_exit(WEXITSTATUS(wstatus));
} else if (pid == 0) {
if (!dialog_vars.cant_kill)
(void) signal(SIGHUP, finish_bg);
(void) signal(SIGINT, finish_bg);
(void) signal(SIGQUIT, finish_bg);
(void) signal(SIGSEGV, finish_bg);
while (dialog_state.getc_callbacks != 0) {
int fkey = 0;
dlg_getc_callbacks(ERR, fkey, retval);
napms(1000);
}
}
}
}
}
}