1 /* 2 * $Id: ui_getc.c,v 1.70 2018/06/14 00:05:05 tom Exp $ 3 * 4 * ui_getc.c - user interface glue for getc() 5 * 6 * Copyright 2001-2013,2018 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 FILE *input = p->input; 93 fclose(input); 94 if (p->input == dialog_state.pipe_input) 95 dialog_state.pipe_input = 0; 96 /* more than one callback can have the same input */ 97 for (q = dialog_state.getc_callbacks; q != 0; q = q->next) { 98 if (q->input == input) { 99 q->input = 0; 100 } 101 } 102 } 103 104 if (!(p->keep_win)) 105 dlg_del_window(p->win); 106 if ((q = dialog_state.getc_callbacks) == p) { 107 dialog_state.getc_callbacks = p->next; 108 } else { 109 while (q != 0) { 110 if (q->next == p) { 111 q->next = p->next; 112 break; 113 } 114 q = q->next; 115 } 116 } 117 118 /* handle dlg_add_callback_ref cleanup */ 119 if (p->freeback != 0) 120 p->freeback(p); 121 if (p->caller != 0) 122 *(p->caller) = 0; 123 124 free(p); 125 } 126 127 /* 128 * A select() might find more than one input ready for service. Handle them 129 * all. 130 */ 131 static bool 132 handle_inputs(WINDOW *win) 133 { 134 bool result = FALSE; 135 DIALOG_CALLBACK *p; 136 DIALOG_CALLBACK *q; 137 int cur_y, cur_x; 138 int state = ERR; 139 140 getyx(win, cur_y, cur_x); 141 for (p = dialog_state.getc_callbacks, q = 0; p != 0; p = q) { 142 q = p->next; 143 if ((p->handle_input != 0) && p->input_ready) { 144 p->input_ready = FALSE; 145 if (state == ERR) { 146 state = curs_set(0); 147 } 148 if (p->handle_input(p)) { 149 result = TRUE; 150 } 151 } 152 } 153 if (result) { 154 (void) wmove(win, cur_y, cur_x); /* Restore cursor position */ 155 wrefresh(win); 156 } 157 if (state != ERR) 158 curs_set(state); 159 return result; 160 } 161 162 static bool 163 may_handle_inputs(void) 164 { 165 bool result = FALSE; 166 167 DIALOG_CALLBACK *p; 168 169 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) { 170 if (p->input != 0) { 171 result = TRUE; 172 break; 173 } 174 } 175 176 return result; 177 } 178 179 /* 180 * Check any any inputs registered via callbacks, to see if there is any input 181 * available. If there is, return a file-descriptor which should be read. 182 * Otherwise, return -1. 183 */ 184 static int 185 check_inputs(void) 186 { 187 DIALOG_CALLBACK *p; 188 fd_set read_fds; 189 struct timeval test; 190 int last_fd = -1; 191 int fd; 192 int found; 193 int result = -1; 194 195 if ((p = dialog_state.getc_callbacks) != 0) { 196 FD_ZERO(&read_fds); 197 198 while (p != 0) { 199 p->input_ready = FALSE; 200 if (p->input != 0 && (fd = fileno(p->input)) >= 0) { 201 FD_SET(fd, &read_fds); 202 if (last_fd < fd) 203 last_fd = fd; 204 } 205 p = p->next; 206 } 207 208 test.tv_sec = 0; 209 test.tv_usec = WTIMEOUT_VAL * 1000; 210 found = select(last_fd + 1, &read_fds, 211 (fd_set *) 0, 212 (fd_set *) 0, 213 &test); 214 215 if (found > 0) { 216 for (p = dialog_state.getc_callbacks; p != 0; p = p->next) { 217 if (p->input != 0 218 && (fd = fileno(p->input)) >= 0 219 && FD_ISSET(fd, &read_fds)) { 220 p->input_ready = TRUE; 221 result = fd; 222 } 223 } 224 } 225 } 226 227 return result; 228 } 229 230 int 231 dlg_getc_callbacks(int ch, int fkey, int *result) 232 { 233 int code = FALSE; 234 DIALOG_CALLBACK *p, *q; 235 236 if ((p = dialog_state.getc_callbacks) != 0) { 237 if (check_inputs() >= 0) { 238 do { 239 q = p->next; 240 if (p->input_ready) { 241 if (!(p->handle_getc(p, ch, fkey, result))) { 242 dlg_remove_callback(p); 243 } 244 } 245 } while ((p = q) != 0); 246 } 247 code = (dialog_state.getc_callbacks != 0); 248 } 249 return code; 250 } 251 252 static void 253 dlg_raise_window(WINDOW *win) 254 { 255 touchwin(win); 256 wmove(win, getcury(win), getcurx(win)); 257 wnoutrefresh(win); 258 doupdate(); 259 } 260 261 /* 262 * This is a work-around for the case where we actually need the wide-character 263 * code versus a byte stream. 264 */ 265 static int last_getc = ERR; 266 267 #ifdef USE_WIDE_CURSES 268 static char last_getc_bytes[80]; 269 static int have_last_getc; 270 static int used_last_getc; 271 #endif 272 273 int 274 dlg_last_getc(void) 275 { 276 #ifdef USE_WIDE_CURSES 277 if (used_last_getc != 1) 278 return ERR; /* not really an error... */ 279 #endif 280 return last_getc; 281 } 282 283 void 284 dlg_flush_getc(void) 285 { 286 last_getc = ERR; 287 #ifdef USE_WIDE_CURSES 288 have_last_getc = 0; 289 used_last_getc = 0; 290 #endif 291 } 292 293 /* 294 * Report the last key entered by the user. The 'mode' parameter controls 295 * the way it is separated from other results: 296 * -2 (no separator) 297 * -1 (separator after the key name) 298 * 0 (separator is optionally before the key name) 299 * 1 (same as -1) 300 */ 301 void 302 dlg_add_last_key(int mode) 303 { 304 if (dialog_vars.last_key) { 305 if (mode >= 0) { 306 if (mode > 0) { 307 dlg_add_last_key(-1); 308 } else { 309 if (dlg_need_separator()) 310 dlg_add_separator(); 311 dlg_add_last_key(-2); 312 } 313 } else { 314 char temp[80]; 315 sprintf(temp, "%d", last_getc); 316 dlg_add_string(temp); 317 if (mode == -1) 318 dlg_add_separator(); 319 } 320 } 321 } 322 323 /* 324 * Check if the stream has been unexpectedly closed, returning false in that 325 * case. 326 */ 327 static bool 328 valid_file(FILE *fp) 329 { 330 bool code = FALSE; 331 int fd = fileno(fp); 332 333 if (fd >= 0) { 334 if (fcntl(fd, F_GETFL, 0) >= 0) { 335 code = TRUE; 336 } 337 } 338 return code; 339 } 340 341 static int 342 really_getch(WINDOW *win, int *fkey) 343 { 344 int ch; 345 #ifdef USE_WIDE_CURSES 346 int code; 347 mbstate_t state; 348 wchar_t my_wchar; 349 wint_t my_wint; 350 351 /* 352 * We get a wide character, translate it to multibyte form to avoid 353 * having to change the rest of the code to use wide-characters. 354 */ 355 if (used_last_getc >= have_last_getc) { 356 used_last_getc = 0; 357 have_last_getc = 0; 358 ch = ERR; 359 *fkey = 0; 360 code = wget_wch(win, &my_wint); 361 my_wchar = (wchar_t) my_wint; 362 switch (code) { 363 case KEY_CODE_YES: 364 ch = *fkey = my_wchar; 365 last_getc = my_wchar; 366 break; 367 case OK: 368 memset(&state, 0, sizeof(state)); 369 have_last_getc = (int) wcrtomb(last_getc_bytes, my_wchar, &state); 370 if (have_last_getc < 0) { 371 have_last_getc = used_last_getc = 0; 372 last_getc_bytes[0] = (char) my_wchar; 373 } 374 ch = (int) CharOf(last_getc_bytes[used_last_getc++]); 375 last_getc = my_wchar; 376 break; 377 case ERR: 378 ch = ERR; 379 last_getc = ERR; 380 break; 381 default: 382 break; 383 } 384 } else { 385 ch = (int) CharOf(last_getc_bytes[used_last_getc++]); 386 } 387 #else 388 ch = wgetch(win); 389 last_getc = ch; 390 *fkey = (ch > KEY_MIN && ch < KEY_MAX); 391 #endif 392 return ch; 393 } 394 395 static DIALOG_CALLBACK * 396 next_callback(DIALOG_CALLBACK * p) 397 { 398 if ((p = dialog_state.getc_redirect) != 0) { 399 p = p->next; 400 } else { 401 p = dialog_state.getc_callbacks; 402 } 403 return p; 404 } 405 406 static DIALOG_CALLBACK * 407 prev_callback(DIALOG_CALLBACK * p) 408 { 409 DIALOG_CALLBACK *q; 410 411 if ((p = dialog_state.getc_redirect) != 0) { 412 if (p == dialog_state.getc_callbacks) { 413 for (p = dialog_state.getc_callbacks; p->next != 0; p = p->next) ; 414 } else { 415 for (q = dialog_state.getc_callbacks; q->next != p; q = q->next) ; 416 p = q; 417 } 418 } else { 419 p = dialog_state.getc_callbacks; 420 } 421 return p; 422 } 423 424 #define isBeforeChr(chr) ((chr) == before_chr && !before_fkey) 425 #define isBeforeFkey(chr) ((chr) == before_chr && before_fkey) 426 427 /* 428 * Read a character from the given window. Handle repainting here (to simplify 429 * things in the calling application). Also, if input-callback(s) are set up, 430 * poll the corresponding files and handle the updates, e.g., for displaying a 431 * tailbox. 432 */ 433 int 434 dlg_getc(WINDOW *win, int *fkey) 435 { 436 WINDOW *save_win = win; 437 int ch = ERR; 438 int before_chr; 439 int before_fkey; 440 int result; 441 bool done = FALSE; 442 bool literal = FALSE; 443 DIALOG_CALLBACK *p = 0; 444 int interval = (dialog_vars.timeout_secs * 1000); 445 time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs; 446 time_t current; 447 448 if (may_handle_inputs()) 449 wtimeout(win, WTIMEOUT_VAL); 450 else if (interval > 0) 451 wtimeout(win, interval); 452 453 while (!done) { 454 bool handle_others = FALSE; 455 456 /* 457 * If there was no pending file-input, check the keyboard. 458 */ 459 ch = really_getch(win, fkey); 460 if (literal) { 461 done = TRUE; 462 continue; 463 } 464 465 before_chr = ch; 466 before_fkey = *fkey; 467 468 ch = dlg_lookup_key(win, ch, fkey); 469 dlg_trace_chr(ch, *fkey); 470 471 current = time((time_t *) 0); 472 473 /* 474 * If we acquired a fkey value, then it is one of dialog's builtin 475 * codes such as DLGK_HELPFILE. 476 */ 477 if (!*fkey || *fkey != before_fkey) { 478 switch (ch) { 479 case CHR_LITERAL: 480 literal = TRUE; 481 keypad(win, FALSE); 482 continue; 483 case CHR_REPAINT: 484 (void) touchwin(win); 485 (void) wrefresh(curscr); 486 break; 487 case ERR: /* wtimeout() in effect; check for file I/O */ 488 if (interval > 0 489 && current >= expired) { 490 DLG_TRACE(("# dlg_getc: timeout expired\n")); 491 ch = ESC; 492 done = TRUE; 493 } else if (!valid_file(stdin) 494 || !valid_file(dialog_state.screen_output)) { 495 DLG_TRACE(("# dlg_getc: input or output is invalid\n")); 496 ch = ESC; 497 done = TRUE; 498 } else if (check_inputs()) { 499 if (handle_inputs(win)) 500 dlg_raise_window(win); 501 else 502 done = TRUE; 503 } else { 504 done = (interval <= 0); 505 } 506 break; 507 case DLGK_HELPFILE: 508 if (dialog_vars.help_file) { 509 int yold, xold; 510 getyx(win, yold, xold); 511 dialog_helpfile("HELP", dialog_vars.help_file, 0, 0); 512 dlg_raise_window(win); 513 wmove(win, yold, xold); 514 } 515 continue; 516 case DLGK_FIELD_PREV: 517 /* FALLTHRU */ 518 case KEY_BTAB: 519 /* FALLTHRU */ 520 case DLGK_FIELD_NEXT: 521 /* FALLTHRU */ 522 case TAB: 523 /* Handle tab/backtab as a special case for traversing between 524 * the nominal "current" window, and other windows having 525 * callbacks. If the nominal (control) window closes, we'll 526 * close the windows with callbacks. 527 */ 528 if (dialog_state.getc_callbacks != 0 && 529 (isBeforeChr(TAB) || 530 isBeforeFkey(KEY_BTAB))) { 531 p = (isBeforeChr(TAB) 532 ? next_callback(p) 533 : prev_callback(p)); 534 if ((dialog_state.getc_redirect = p) != 0) { 535 win = p->win; 536 } else { 537 win = save_win; 538 } 539 dlg_raise_window(win); 540 break; 541 } 542 /* FALLTHRU */ 543 default: 544 #ifdef NO_LEAKS 545 if (isBeforeChr(DLG_CTRL('P'))) { 546 /* for testing, ^P closes the connection */ 547 close(0); 548 close(1); 549 close(2); 550 break; 551 } 552 #endif 553 handle_others = TRUE; 554 break; 555 #ifdef HAVE_DLG_TRACE 556 case CHR_TRACE: 557 dlg_trace_win(win); 558 break; 559 #endif 560 } 561 } else { 562 handle_others = TRUE; 563 } 564 565 if (handle_others) { 566 if ((p = dialog_state.getc_redirect) != 0) { 567 if (!(p->handle_getc(p, ch, *fkey, &result))) { 568 done = (p->win == save_win) && (!p->keep_win); 569 dlg_remove_callback(p); 570 dialog_state.getc_redirect = 0; 571 win = save_win; 572 } 573 } else { 574 done = TRUE; 575 } 576 } 577 } 578 if (literal) 579 keypad(win, TRUE); 580 return ch; 581 } 582 583 static void 584 finish_bg(int sig GCC_UNUSED) 585 { 586 end_dialog(); 587 dlg_exit(DLG_EXIT_ERROR); 588 } 589 590 /* 591 * If we have callbacks active, purge the list of all that are not marked 592 * to keep in the background. If any remain, run those in a background 593 * process. 594 */ 595 void 596 dlg_killall_bg(int *retval) 597 { 598 DIALOG_CALLBACK *cb; 599 int pid; 600 #ifdef HAVE_TYPE_UNIONWAIT 601 union wait wstatus; 602 #else 603 int wstatus; 604 #endif 605 606 if ((cb = dialog_state.getc_callbacks) != 0) { 607 while (cb != 0) { 608 if (cb->keep_bg) { 609 cb = cb->next; 610 } else { 611 dlg_remove_callback(cb); 612 cb = dialog_state.getc_callbacks; 613 } 614 } 615 if (dialog_state.getc_callbacks != 0) { 616 617 refresh(); 618 fflush(stdout); 619 fflush(stderr); 620 reset_shell_mode(); 621 if ((pid = fork()) != 0) { 622 _exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR); 623 } else if (pid == 0) { /* child */ 624 if ((pid = fork()) != 0) { 625 /* 626 * Echo the process-id of the grandchild so a shell script 627 * can read that, and kill that process. We'll wait around 628 * until then. Our parent has already left, leaving us 629 * temporarily orphaned. 630 */ 631 if (pid > 0) { /* parent */ 632 fprintf(stderr, "%d\n", pid); 633 fflush(stderr); 634 } 635 /* wait for child */ 636 #ifdef HAVE_WAITPID 637 while (-1 == waitpid(pid, &wstatus, 0)) { 638 #ifdef EINTR 639 if (errno == EINTR) 640 continue; 641 #endif /* EINTR */ 642 #ifdef ERESTARTSYS 643 if (errno == ERESTARTSYS) 644 continue; 645 #endif /* ERESTARTSYS */ 646 break; 647 } 648 #else 649 while (wait(&wstatus) != pid) /* do nothing */ 650 ; 651 #endif 652 _exit(WEXITSTATUS(wstatus)); 653 } else if (pid == 0) { 654 if (!dialog_vars.cant_kill) 655 (void) signal(SIGHUP, finish_bg); 656 (void) signal(SIGINT, finish_bg); 657 (void) signal(SIGQUIT, finish_bg); 658 (void) signal(SIGSEGV, finish_bg); 659 while (dialog_state.getc_callbacks != 0) { 660 int fkey = 0; 661 dlg_getc_callbacks(ERR, fkey, retval); 662 napms(1000); 663 } 664 } 665 } 666 } 667 } 668 } 669