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