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