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