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