1 /* 2 * $Id: ui_getc.c,v 1.48 2010/01/18 10:24:06 tom Exp $ 3 * 4 * ui_getc.c - user interface glue for getc() 5 * 6 * Copyright 2001-2009,2010 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 * FIXME: this could be replaced by a select/poll on several file descriptors 123 */ 124 static int 125 dlg_getc_ready(DIALOG_CALLBACK * p) 126 { 127 fd_set read_fds; 128 int fd = fileno(p->input); 129 struct timeval test; 130 131 FD_ZERO(&read_fds); 132 FD_SET(fd, &read_fds); 133 134 test.tv_sec = 0; /* Seconds. */ 135 test.tv_usec = (isatty(fd) /* Microseconds. */ 136 ? (WTIMEOUT_VAL * 1000) 137 : 1); 138 return (select(fd + 1, &read_fds, (fd_set *) 0, (fd_set *) 0, &test) == 1) 139 && (FD_ISSET(fd, &read_fds)); 140 } 141 142 int 143 dlg_getc_callbacks(int ch, int fkey, int *result) 144 { 145 int code = FALSE; 146 DIALOG_CALLBACK *p, *q; 147 148 if ((p = dialog_state.getc_callbacks) != 0) { 149 do { 150 q = p->next; 151 if (dlg_getc_ready(p)) { 152 if (!(p->handle_getc(p, ch, fkey, result))) { 153 dlg_remove_callback(p); 154 } 155 } 156 } while ((p = q) != 0); 157 code = (dialog_state.getc_callbacks != 0); 158 } 159 return code; 160 } 161 162 static void 163 dlg_raise_window(WINDOW *win) 164 { 165 touchwin(win); 166 wmove(win, getcury(win), getcurx(win)); 167 wnoutrefresh(win); 168 doupdate(); 169 } 170 171 /* 172 * This is a work-around for the case where we actually need the wide-character 173 * code versus a byte stream. 174 */ 175 static int last_getc = ERR; 176 177 #ifdef USE_WIDE_CURSES 178 static char last_getc_bytes[80]; 179 static int have_last_getc; 180 static int used_last_getc; 181 #endif 182 183 int 184 dlg_last_getc(void) 185 { 186 #ifdef USE_WIDE_CURSES 187 if (used_last_getc != 1) 188 return ERR; /* not really an error... */ 189 #endif 190 return last_getc; 191 } 192 193 void 194 dlg_flush_getc(void) 195 { 196 last_getc = ERR; 197 #ifdef USE_WIDE_CURSES 198 have_last_getc = 0; 199 used_last_getc = 0; 200 #endif 201 } 202 203 /* 204 * Check if the stream has been unexpectedly closed, returning false in that 205 * case. 206 */ 207 static bool 208 valid_file(FILE *fp) 209 { 210 bool code = FALSE; 211 int fd = fileno(fp); 212 213 if (fd >= 0) { 214 long result = 0; 215 if ((result = fcntl(fd, F_GETFL, 0)) >= 0) { 216 code = TRUE; 217 } 218 } 219 return code; 220 } 221 222 /* 223 * Read a character from the given window. Handle repainting here (to simplify 224 * things in the calling application). Also, if input-callback(s) are set up, 225 * poll the corresponding files and handle the updates, e.g., for displaying a 226 * tailbox. 227 */ 228 int 229 dlg_getc(WINDOW *win, int *fkey) 230 { 231 WINDOW *save_win = win; 232 int ch = ERR; 233 int before_lookup; 234 int result; 235 bool done = FALSE; 236 bool literal = FALSE; 237 DIALOG_CALLBACK *p; 238 int interval = dialog_vars.timeout_secs; 239 time_t expired = time((time_t *) 0) + dialog_vars.timeout_secs; 240 time_t current; 241 242 if (dialog_state.getc_callbacks != 0) 243 wtimeout(win, WTIMEOUT_VAL); 244 else if (interval > 0) 245 wtimeout(win, interval); 246 247 while (!done) { 248 #ifdef USE_WIDE_CURSES 249 int code; 250 mbstate_t state; 251 wchar_t my_wchar; 252 wint_t my_wint; 253 254 /* 255 * We get a wide character, translate it to multibyte form to avoid 256 * having to change the rest of the code to use wide-characters. 257 */ 258 if (used_last_getc >= have_last_getc) { 259 used_last_getc = 0; 260 have_last_getc = 0; 261 ch = ERR; 262 *fkey = 0; 263 code = wget_wch(win, &my_wint); 264 my_wchar = (wchar_t) my_wint; 265 switch (code) { 266 case KEY_CODE_YES: 267 ch = *fkey = my_wchar; 268 last_getc = my_wchar; 269 break; 270 case OK: 271 memset(&state, 0, sizeof(state)); 272 have_last_getc = (int) wcrtomb(last_getc_bytes, my_wchar, &state); 273 if (have_last_getc < 0) { 274 have_last_getc = used_last_getc = 0; 275 last_getc_bytes[0] = (char) my_wchar; 276 } 277 ch = (int) CharOf(last_getc_bytes[used_last_getc++]); 278 last_getc = my_wchar; 279 break; 280 case ERR: 281 ch = ERR; 282 last_getc = ERR; 283 break; 284 default: 285 break; 286 } 287 } else { 288 ch = (int) CharOf(last_getc_bytes[used_last_getc++]); 289 } 290 #else 291 ch = wgetch(win); 292 last_getc = ch; 293 *fkey = (ch > KEY_MIN && ch < KEY_MAX); 294 #endif 295 if (literal) { 296 done = TRUE; 297 continue; 298 } 299 300 before_lookup = ch; 301 ch = dlg_lookup_key(win, ch, fkey); 302 dlg_trace_chr(ch, *fkey); 303 304 current = time((time_t *) 0); 305 306 switch (ch) { 307 case CHR_LITERAL: 308 if (!literal) { 309 literal = TRUE; 310 keypad(win, FALSE); 311 continue; 312 } 313 break; 314 case CHR_REPAINT: 315 (void) touchwin(win); 316 (void) wrefresh(curscr); 317 break; 318 case ERR: /* wtimeout() in effect; check for file I/O */ 319 if (interval > 0 320 && current >= expired) { 321 dlg_exiterr("timeout"); 322 } 323 if (dlg_getc_callbacks(ch, *fkey, &result)) { 324 dlg_raise_window(win); 325 } else { 326 done = (interval <= 0); 327 } 328 if (!valid_file(stdin) 329 || !valid_file(dialog_state.screen_output)) { 330 ch = ESC; 331 done = TRUE; 332 } 333 break; 334 case DLGK_FIELD_NEXT: 335 /* FALLTHRU */ 336 case TAB: 337 /* Handle tab as a special case for traversing between the nominal 338 * "current" window, and other windows having callbacks. If the 339 * nominal (control) window closes, we'll close the windows with 340 * callbacks. 341 */ 342 if (dialog_state.getc_callbacks != 0 && 343 before_lookup == TAB) { 344 if ((p = dialog_state.getc_redirect) != 0) { 345 p = p->next; 346 } else { 347 p = dialog_state.getc_callbacks; 348 } 349 if ((dialog_state.getc_redirect = p) != 0) { 350 win = p->win; 351 } else { 352 win = save_win; 353 } 354 dlg_raise_window(win); 355 break; 356 } 357 /* FALLTHRU */ 358 default: 359 #ifdef NO_LEAKS 360 if (before_lookup == DLG_CTRL('P')) { 361 /* for testing, ^P closes the connection */ 362 close(0); 363 close(1); 364 close(2); 365 break; 366 } 367 #endif 368 if ((p = dialog_state.getc_redirect) != 0) { 369 if (!(p->handle_getc(p, ch, *fkey, &result))) { 370 dlg_remove_callback(p); 371 dialog_state.getc_redirect = 0; 372 win = save_win; 373 } 374 } else { 375 done = TRUE; 376 } 377 break; 378 #ifdef HAVE_DLG_TRACE 379 case CHR_TRACE: 380 dlg_trace_win(win); 381 break; 382 #endif 383 } 384 } 385 if (literal) 386 keypad(win, TRUE); 387 return ch; 388 } 389 390 static void 391 finish_bg(int sig GCC_UNUSED) 392 { 393 end_dialog(); 394 dlg_exit(DLG_EXIT_ERROR); 395 } 396 397 /* 398 * If we have callbacks active, purge the list of all that are not marked 399 * to keep in the background. If any remain, run those in a background 400 * process. 401 */ 402 void 403 dlg_killall_bg(int *retval) 404 { 405 DIALOG_CALLBACK *cb; 406 int pid; 407 #ifdef HAVE_TYPE_UNIONWAIT 408 union wait wstatus; 409 #else 410 int wstatus; 411 #endif 412 413 if ((cb = dialog_state.getc_callbacks) != 0) { 414 while (cb != 0) { 415 if (cb->keep_bg) { 416 cb = cb->next; 417 } else { 418 dlg_remove_callback(cb); 419 cb = dialog_state.getc_callbacks; 420 } 421 } 422 if (dialog_state.getc_callbacks != 0) { 423 424 refresh(); 425 fflush(stdout); 426 fflush(stderr); 427 reset_shell_mode(); 428 if ((pid = fork()) != 0) { 429 _exit(pid > 0 ? DLG_EXIT_OK : DLG_EXIT_ERROR); 430 } else if (pid == 0) { /* child */ 431 if ((pid = fork()) != 0) { 432 /* 433 * Echo the process-id of the grandchild so a shell script 434 * can read that, and kill that process. We'll wait around 435 * until then. Our parent has already left, leaving us 436 * temporarily orphaned. 437 */ 438 if (pid > 0) { /* parent */ 439 fprintf(stderr, "%d\n", pid); 440 fflush(stderr); 441 } 442 /* wait for child */ 443 #ifdef HAVE_WAITPID 444 while (-1 == waitpid(pid, &wstatus, 0)) { 445 #ifdef EINTR 446 if (errno == EINTR) 447 continue; 448 #endif /* EINTR */ 449 #ifdef ERESTARTSYS 450 if (errno == ERESTARTSYS) 451 continue; 452 #endif /* ERESTARTSYS */ 453 break; 454 } 455 #else 456 while (wait(&wstatus) != pid) /* do nothing */ 457 ; 458 #endif 459 _exit(WEXITSTATUS(wstatus)); 460 } else if (pid == 0) { 461 if (!dialog_vars.cant_kill) 462 (void) signal(SIGHUP, finish_bg); 463 (void) signal(SIGINT, finish_bg); 464 (void) signal(SIGQUIT, finish_bg); 465 (void) signal(SIGSEGV, finish_bg); 466 while (dialog_state.getc_callbacks != 0) { 467 int fkey = 0; 468 dlg_getc_callbacks(ERR, fkey, retval); 469 napms(1000); 470 } 471 } 472 } 473 } 474 } 475 } 476