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