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