Merge branch 'vendor/TCSH'
[dragonfly.git] / contrib / dialog / ui_getc.c
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 }