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