]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/dialog/ui_getc.c
nvi: import version 2.2.1
[FreeBSD/FreeBSD.git] / contrib / dialog / ui_getc.c
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 }