]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bsddialog/lib/barbox.c
zfs: merge openzfs/zfs@a0b2a93c4
[FreeBSD/FreeBSD.git] / contrib / bsddialog / lib / barbox.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2023 Alfonso Sabato Siciliano
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 #include <curses.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <unistd.h>
33
34 #include "bsddialog.h"
35 #include "bsddialog_progressview.h"
36 #include "bsddialog_theme.h"
37 #include "lib_util.h"
38
39 #define BARPADDING   2  /* widget border | BARPADDING | box bar */
40 #define BOXBORDERS   2
41 #define MIN_WBAR     15
42 #define MIN_WBOX     (BARPADDING + BOXBORDERS + MIN_WBAR + BARPADDING)
43 #define MIN_WMGBAR   18
44 #define MIN_WMGBOX   (BARPADDING + BOXBORDERS + MIN_WMGBAR + BARPADDING)
45 #define HBOX         3
46 #define WBOX(d)      ((d)->w - BORDERS - BARPADDING - BARPADDING)
47 #define WBAR(d)      (WBOX(d) - BOXBORDERS)
48
49 bool bsddialog_interruptprogview;
50 bool bsddialog_abortprogview;
51 long long int bsddialog_total_progview;
52
53 static const char states[12][14] = {
54         "  Succeeded  ", /* -1  */
55         "   Failed    ", /* -2  */
56         "   Passed    ", /* -3  */
57         "  Completed  ", /* -4  */
58         "   Checked   ", /* -5  */
59         "    Done     ", /* -6  */
60         "   Skipped   ", /* -7  */
61         " In Progress ", /* -8  */
62         "(blank)      ", /* -9  */
63         "     N/A     ", /* -10 */
64         "   Pending   ", /* -11 */
65         "   UNKNOWN   ", /* < -11, no API */
66 };
67
68 struct bar {
69         bool toupdate;
70         WINDOW *win;
71         int y;           /* bar y in win */
72         int x;           /* bar x in win */
73         int w;           /* width in win */
74         int perc;        /* barlen = (w * perc) / 100 */
75         const char* fmt; /* format for label */
76         int label;       /* rangebox and pause perc!=label */
77 };
78
79 static void draw_bar(struct bar *b)
80 {
81         int barlen, xlabel;
82         chtype ch;
83         char label[128];
84
85         barlen = b->perc > 0 ? (b->perc * b->w) / 100 : 0;
86
87         ch = ' ' | t.bar.f_color;
88         mvwhline(b->win, b->y, b->x, ch, barlen);
89         ch = ' ' | t.bar.color;
90         mvwhline(b->win, b->y, b->x + barlen, ch, b->w - barlen);
91
92         sprintf(label, b->fmt, b->label);
93         xlabel = b->x + b->w/2 - (int)strlen(label)/2; /* 1-byte-char string */
94         wattron(b->win, t.bar.color);   /* x+barlen < xlabel */
95         mvwaddstr(b->win, b->y, xlabel, label);
96         wattroff(b->win, t.bar.color);
97         wattron(b->win, t.bar.f_color); /* x+barlen >= xlabel */
98         mvwaddnstr(b->win, b->y, xlabel, label, MAX((b->x+barlen) - xlabel, 0));
99         wattroff(b->win, t.bar.f_color);
100
101         if (b->toupdate)
102                 wnoutrefresh(b->win);
103         b->toupdate = false;
104 }
105
106 static void update_barbox(struct dialog *d, struct bar *b, bool buttons)
107 {
108         int y;
109
110         y = d->y + d->h - BORDER - HBOX;
111         if (buttons)
112                 y -= HBUTTONS;
113         update_box(d->conf, b->win, y, d->x + BORDER + BARPADDING, HBOX,
114             WBOX(d), RAISED);
115 }
116
117 int
118 bsddialog_gauge(struct bsddialog_conf *conf, const char *text, int rows,
119     int cols, unsigned int perc, int fd, const char *sep, const char *end)
120 {
121         bool mainloop;
122         int fd2;
123         FILE *input;
124         char inputbuf[2048], ntext[2048], *pntext;
125         struct bar b;
126         struct dialog d;
127
128         if (prepare_dialog(conf, text, rows, cols, &d) != 0)
129                 return (BSDDIALOG_ERROR);
130         if ((b.win = newwin(1, 1, 1, 1)) == NULL)
131                 RETURN_ERROR("Cannot build WINDOW bar");
132         b.y = b.x = 1;
133         b.fmt = "%3d%%";
134
135         input = NULL;
136         if (fd >= 0) {
137                 CHECK_PTR(sep);
138                 CHECK_PTR(end);
139
140                 fd2 = dup(fd);
141                 if ((input = fdopen(fd2, "r")) == NULL)
142                         RETURN_FMTERROR("Cannot build FILE* from fd %d", fd);
143         }
144
145         perc = MIN(perc, 100);
146         mainloop = true;
147         while (mainloop) {
148                 if (d.built) {
149                         hide_dialog(&d);
150                         refresh(); /* Important for decreasing screen */
151                 }
152                 if (dialog_size_position(&d, HBOX, MIN_WBOX, NULL) != 0)
153                         return (BSDDIALOG_ERROR);
154                 if (draw_dialog(&d))
155                         return (BSDDIALOG_ERROR);
156                 if (d.built)
157                         refresh(); /* fix grey lines expanding screen */
158                 TEXTPAD(&d, HBOX);
159                 update_barbox(&d, &b, false);
160                 b.w = WBAR(&d);
161                 b.perc = b.label = perc;
162                 b.toupdate = true;
163                 draw_bar(&b);
164                 doupdate();
165                 if (input == NULL) /* that is fd < 0 */
166                         break;
167
168                 while (true) {
169                         fscanf(input, "%s", inputbuf);
170                         if (strcmp(inputbuf, end) == 0) {
171                                 mainloop = false;
172                                 break;
173                         }
174                         if (strcmp(inputbuf, sep) == 0)
175                                 break;
176                 }
177                 if (mainloop == false)
178                         break;
179                 fscanf(input, "%d", &perc);
180                 perc = MIN(perc, 100);
181                 pntext = &ntext[0];
182                 ntext[0] = '\0';
183                 while (true) {
184                         fscanf(input, "%s", inputbuf);
185                         if (strcmp(inputbuf, end) == 0) {
186                                 mainloop = false;
187                                 break;
188                         }
189                         if (strcmp(inputbuf, sep) == 0)
190                                 break;
191                         strcpy(pntext, inputbuf);
192                         pntext += strlen(inputbuf); /* end string, no strlen */
193                         pntext[0] = ' ';
194                         pntext++;
195                 }
196                 pntext[0] = '\0';
197                 d.text = ntext;
198         }
199
200         if (input != NULL)
201                 fclose(input);
202         delwin(b.win);
203         end_dialog(&d);
204
205         return (BSDDIALOG_OK);
206 }
207
208 /* Mixedgauge */
209 static int
210 do_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows, int cols,
211     unsigned int mainperc, unsigned int nminibars, const char **minilabels,
212     int *minipercs, bool color)
213 {
214         int i, miniperc, max_minibarlen;
215         int ystext, htext;
216         int minicolor, red, green;
217         struct bar b;
218         struct dialog d;
219
220         CHECK_ARRAY(nminibars, minilabels);
221         CHECK_ARRAY(nminibars, minipercs);
222
223         red   = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_RED,  BSDDIALOG_BOLD);
224         green = bsddialog_color(BSDDIALOG_WHITE,BSDDIALOG_GREEN,BSDDIALOG_BOLD);
225
226         max_minibarlen = 0;
227         for (i = 0; i < (int)nminibars; i++)
228                 max_minibarlen = MAX(max_minibarlen,
229                     (int)strcols(CHECK_STR(minilabels[i])));
230         max_minibarlen += 3 + 16; /* seps + [...] */
231         max_minibarlen = MAX(max_minibarlen, MIN_WMGBOX); /* mainbar */
232
233         if (prepare_dialog(conf, text, rows, cols, &d) != 0)
234                 return (BSDDIALOG_ERROR);
235         if (dialog_size_position(&d, nminibars + HBOX, max_minibarlen,
236             &htext) != 0)
237                 return (BSDDIALOG_ERROR);
238         if (draw_dialog(&d) != 0)
239                 return (BSDDIALOG_ERROR);
240
241         /* mini bars */
242         b.win = d.widget;
243         b.x = 1 + d.w - 2 - 15;
244         b.w = 13;
245         b.fmt = "%3d%%";
246         b.toupdate = false;
247         for (i = 0; i < (int)nminibars; i++) {
248                 miniperc = minipercs[i];
249                 /* label */
250                 if (color && miniperc >= 0)
251                         wattron(d.widget, A_BOLD);
252                 mvwaddstr(d.widget, i+1, 2, CHECK_STR(minilabels[i]));
253                 if (color && miniperc >= 0)
254                         wattroff(d.widget, A_BOLD);
255                 /* perc */
256                 if (miniperc == BSDDIALOG_MG_BLANK)
257                         continue;
258                 mvwaddstr(d.widget, i+1, d.w-2-15, "[             ]");
259                 if (miniperc >= 0) {
260                         b.y = i + 1;
261                         b.perc = b.label = MIN(miniperc, 100);
262                         draw_bar(&b);
263                 } else { /* miniperc < 0 */
264                         if (miniperc < BSDDIALOG_MG_PENDING)
265                                 miniperc = -12; /* UNKNOWN */
266                         minicolor = t.dialog.color;
267                         if (color && miniperc == BSDDIALOG_MG_FAILED)
268                                 minicolor = red;
269                         else if (color && miniperc == BSDDIALOG_MG_DONE)
270                                 minicolor = green;
271                         wattron(d.widget, minicolor);
272                         miniperc = abs(miniperc + 1);
273                         mvwaddstr(d.widget, i+1, 1+d.w-2-15, states[miniperc]);
274                         wattroff(d.widget, minicolor);
275                 }
276         }
277         wnoutrefresh(d.widget);
278
279         /* text */
280         ystext = MAX(d.h - BORDERS - htext - HBOX, (int)nminibars);
281         rtextpad(&d, 0, 0, ystext, HBOX);
282
283         /* main bar */
284         if ((b.win = newwin(1, 1, 1, 1)) == NULL)
285                 RETURN_ERROR("Cannot build WINDOW bar");
286         update_barbox(&d, &b, false);
287         wattron(b.win, t.bar.color);
288         mvwaddstr(b.win, 0, 2, "Overall Progress");
289         wattroff(b.win, t.bar.color);
290
291         b.y = b.x = 1;
292         b.w = WBAR(&d);
293         b.fmt = "%3d%%";
294         b.perc = b.label = MIN(mainperc, 100);
295         b.toupdate = true;
296         draw_bar(&b);
297
298         doupdate();
299         /* getch(); to test with "alternate mode" */
300
301         delwin(b.win);
302         end_dialog(&d);
303
304         return (BSDDIALOG_OK);
305 }
306
307 int
308 bsddialog_mixedgauge(struct bsddialog_conf *conf, const char *text, int rows,
309     int cols, unsigned int mainperc, unsigned int nminibars,
310     const char **minilabels, int *minipercs)
311 {
312         int retval;
313
314         retval = do_mixedgauge(conf, text, rows, cols, mainperc, nminibars,
315             minilabels, minipercs, false);
316
317         return (retval);
318 }
319
320 int
321 bsddialog_progressview (struct bsddialog_conf *conf, const char *text, int rows,
322     int cols, struct bsddialog_progviewconf *pvconf, unsigned int nminibar,
323     struct bsddialog_fileminibar *minibar)
324 {
325         bool update;
326         int perc, retval, *minipercs;
327         unsigned int i, mainperc, totaltodo;
328         float readforsec;
329         const char **minilabels;
330         time_t tstart, told, tnew, refresh;
331
332         if ((minilabels = calloc(nminibar, sizeof(char*))) == NULL)
333                 RETURN_ERROR("Cannot allocate memory for minilabels");
334         if ((minipercs = calloc(nminibar, sizeof(int))) == NULL)
335                 RETURN_ERROR("Cannot allocate memory for minipercs");
336
337         totaltodo = 0;
338         for (i = 0; i < nminibar; i++) {
339                 totaltodo += minibar[i].size;
340                 minilabels[i] = minibar[i].label;
341                 minipercs[i] = minibar[i].status;
342         }
343
344         refresh = pvconf->refresh == 0 ? 0 : pvconf->refresh - 1;
345         retval = BSDDIALOG_OK;
346         i = 0;
347         update = true;
348         time(&told);
349         tstart = told;
350         while (!(bsddialog_interruptprogview || bsddialog_abortprogview)) {
351                 if (bsddialog_total_progview == 0 || totaltodo == 0)
352                         mainperc = 0;
353                 else
354                         mainperc = (bsddialog_total_progview * 100) / totaltodo;
355
356                 time(&tnew);
357                 if (update || tnew > told + refresh) {
358                         retval = do_mixedgauge(conf, text, rows, cols, mainperc,
359                             nminibar, minilabels, minipercs, true);
360                         if (retval == BSDDIALOG_ERROR)
361                                 return (BSDDIALOG_ERROR);
362
363                         move(SCREENLINES - 1, 2);
364                         clrtoeol();
365                         readforsec = ((tnew - tstart) == 0) ? 0 :
366                             bsddialog_total_progview / (float)(tnew - tstart);
367                         printw(pvconf->fmtbottomstr, bsddialog_total_progview,
368                             readforsec);
369                         refresh();
370
371                         time(&told);
372                         update = false;
373                 }
374
375                 if (i >= nminibar)
376                         break;
377                 if (minibar[i].status == BSDDIALOG_MG_FAILED)
378                         break;
379
380                 perc = pvconf->callback(&minibar[i]);
381
382                 if (minibar[i].status == BSDDIALOG_MG_DONE) { /*||perc >= 100)*/
383                         minipercs[i] = BSDDIALOG_MG_DONE;
384                         update = true;
385                         i++;
386                 } else if (minibar[i].status == BSDDIALOG_MG_FAILED ||
387                     perc < 0) {
388                         minipercs[i] = BSDDIALOG_MG_FAILED;
389                         update = true;
390                 } else /* perc >= 0 */
391                         minipercs[i] = perc;
392         }
393
394         free(minilabels);
395         free(minipercs);
396         return (retval);
397 }
398
399 static int rangebox_redraw(struct dialog *d, struct bar *b, int *bigchange)
400 {
401         if (d->built) {
402                 hide_dialog(d);
403                 refresh(); /* Important for decreasing screen */
404         }
405         if (dialog_size_position(d, HBOX, MIN_WBOX, NULL) != 0)
406                 return (BSDDIALOG_ERROR);
407         if (draw_dialog(d) != 0)
408                 return (BSDDIALOG_ERROR);
409         if (d->built)
410                 refresh(); /* Important to fix grey lines expanding screen */
411         TEXTPAD(d, HBOX + HBUTTONS);
412
413         b->w = WBAR(d);
414         *bigchange = MAX(1, b->w  / 10);
415         update_barbox(d, b, true);
416         b->toupdate = true;
417
418         return (0);
419 }
420
421 int
422 bsddialog_rangebox(struct bsddialog_conf *conf, const char *text, int rows,
423     int cols, int min, int max, int *value)
424 {
425         bool loop;
426         int currvalue, retval, bigchange, positions;
427         wint_t input;
428         struct bar b;
429         struct dialog d;
430
431         CHECK_PTR(value);
432         if (min >= max)
433                 RETURN_FMTERROR("min (%d) >= max (%d)", min, max);
434         if (*value < min)
435                 RETURN_FMTERROR("value (%d) < min (%d)", *value, min);
436         if (*value > max)
437                 RETURN_FMTERROR("value (%d) > max (%d)", *value, max);
438
439         currvalue = *value;
440         positions = max - min + 1;
441
442         if (prepare_dialog(conf, text, rows, cols, &d) != 0)
443                 return (BSDDIALOG_ERROR);
444         set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
445         if ((b.win = newwin(1, 1, 1, 1)) == NULL)
446                 RETURN_ERROR("Cannot build WINDOW bar");
447         b.y = b.x = 1;
448         b.fmt = "%d";
449         if (rangebox_redraw(&d, &b, &bigchange) != 0)
450                 return (BSDDIALOG_ERROR);
451
452         loop = true;
453         while (loop) {
454                 if (b.toupdate) {
455                         b.perc = ((float)(currvalue - min)*100) / (positions-1);
456                         b.label = currvalue;
457                         draw_bar(&b);
458                 }
459                 doupdate();
460                 if (get_wch(&input) == ERR)
461                         continue;
462                 switch(input) {
463                 case KEY_ENTER:
464                 case 10: /* Enter */
465                         retval = BUTTONVALUE(d.bs);
466                         loop = false;
467                         break;
468                 case 27: /* Esc */
469                         if (conf->key.enable_esc) {
470                                 retval = BSDDIALOG_ESC;
471                                 loop = false;
472                         }
473                         break;
474                 case '\t': /* TAB */
475                 case KEY_RIGHT:
476                         d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons;
477                         DRAW_BUTTONS(d);
478                         break;
479                 case KEY_LEFT:
480                         d.bs.curr--;
481                         if (d.bs.curr < 0)
482                                  d.bs.curr = d.bs.nbuttons - 1;
483                         DRAW_BUTTONS(d);
484                         break;
485                 case KEY_HOME:
486                         currvalue = max;
487                         b.toupdate = true;
488                         break;
489                 case KEY_END:
490                         currvalue = min;
491                         b.toupdate = true;
492                         break;
493                 case KEY_NPAGE:
494                         currvalue -= bigchange;
495                         if (currvalue < min)
496                                 currvalue = min;
497                         b.toupdate = true;
498                         break;
499                 case KEY_PPAGE:
500                         currvalue += bigchange;
501                         if (currvalue > max)
502                                 currvalue = max;
503                         b.toupdate = true;
504                         break;
505                 case KEY_UP:
506                         if (currvalue < max) {
507                                 currvalue++;
508                                 b.toupdate = true;
509                         }
510                         break;
511                 case KEY_DOWN:
512                         if (currvalue > min) {
513                                 currvalue--;
514                                 b.toupdate = true;
515                         }
516                         break;
517                 case KEY_F(1):
518                         if (conf->key.f1_file == NULL &&
519                             conf->key.f1_message == NULL)
520                                 break;
521                         if (f1help_dialog(conf) != 0)
522                                 return (BSDDIALOG_ERROR);
523                         if (rangebox_redraw(&d, &b, &bigchange) != 0)
524                                 return (BSDDIALOG_ERROR);
525                         break;
526                 case KEY_RESIZE:
527                         if (rangebox_redraw(&d, &b, &bigchange) != 0)
528                                 return (BSDDIALOG_ERROR);
529                         break;
530                 default:
531                         if (shortcut_buttons(input, &d.bs)) {
532                                 DRAW_BUTTONS(d);
533                                 doupdate();
534                                 retval = BUTTONVALUE(d.bs);
535                                 loop = false;
536                         }
537                 }
538         }
539
540         *value = currvalue;
541
542         delwin(b.win);
543         end_dialog(&d);
544
545         return (retval);
546 }
547
548 static int pause_redraw(struct dialog *d, struct bar *b)
549 {
550         if (d->built) {
551                 hide_dialog(d);
552                 refresh(); /* Important for decreasing screen */
553         }
554         if (dialog_size_position(d, HBOX, MIN_WBOX, NULL) != 0)
555                 return (BSDDIALOG_ERROR);
556         if (draw_dialog(d) != 0)
557                 return (BSDDIALOG_ERROR);
558         if (d->built)
559                 refresh(); /* Important to fix grey lines expanding screen */
560         TEXTPAD(d, HBOX + HBUTTONS);
561
562         b->w = WBAR(d);
563         update_barbox(d, b, true);
564         b->toupdate = true;
565
566         return (0);
567 }
568
569 int
570 bsddialog_pause(struct bsddialog_conf *conf, const char *text, int rows,
571     int cols, unsigned int *seconds)
572 {
573         bool loop;
574         int retval, tout;
575         wint_t input;
576         struct bar b;
577         struct dialog d;
578
579         CHECK_PTR(seconds);
580         if (prepare_dialog(conf, text, rows, cols, &d) != 0)
581                 return (BSDDIALOG_ERROR);
582         set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
583         if ((b.win = newwin(1, 1, 1, 1)) == NULL)
584                 RETURN_ERROR("Cannot build WINDOW bar");
585         b.y = b.x = 1;
586         b.fmt = "%d";
587         if (pause_redraw(&d, &b) != 0)
588                 return (BSDDIALOG_ERROR);
589
590         tout = *seconds;
591         nodelay(stdscr, TRUE);
592         timeout(1000);
593         loop = true;
594         while (loop) {
595                 if (b.toupdate) {
596                         b.perc = (float)tout * 100 / *seconds;
597                         b.label = tout;
598                         draw_bar(&b);
599                 }
600                 doupdate();
601                 if (get_wch(&input) == ERR) { /* timeout */
602                         tout--;
603                         if (tout < 0) {
604                                 retval = BSDDIALOG_TIMEOUT;
605                                 break;
606                         }
607                         else {
608                                 b.toupdate = true;
609                                 continue;
610                         }
611                 }
612                 switch(input) {
613                 case KEY_ENTER:
614                 case 10: /* Enter */
615                         retval = BUTTONVALUE(d.bs);
616                         loop = false;
617                         break;
618                 case 27: /* Esc */
619                         if (conf->key.enable_esc) {
620                                 retval = BSDDIALOG_ESC;
621                                 loop = false;
622                         }
623                         break;
624                 case '\t': /* TAB */
625                 case KEY_RIGHT:
626                         d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons;
627                         DRAW_BUTTONS(d);
628                         break;
629                 case KEY_LEFT:
630                         d.bs.curr--;
631                         if (d.bs.curr < 0)
632                                  d.bs.curr = d.bs.nbuttons - 1;
633                         DRAW_BUTTONS(d);
634                         break;
635                 case KEY_F(1):
636                         if (conf->key.f1_file == NULL &&
637                             conf->key.f1_message == NULL)
638                                 break;
639                         if (f1help_dialog(conf) != 0)
640                                 return (BSDDIALOG_ERROR);
641                         if (pause_redraw(&d, &b) != 0)
642                                 return (BSDDIALOG_ERROR);
643                         break;
644                 case KEY_RESIZE:
645                         if (pause_redraw(&d, &b) != 0)
646                                 return (BSDDIALOG_ERROR);
647                         break;
648                 default:
649                         if (shortcut_buttons(input, &d.bs)) {
650                                 DRAW_BUTTONS(d);
651                                 doupdate();
652                                 retval = BUTTONVALUE(d.bs);
653                                 loop = false;
654                         }
655                 }
656         }
657         nodelay(stdscr, FALSE);
658
659         *seconds = MAX(tout, 0);
660
661         delwin(b.win);
662         end_dialog(&d);
663
664         return (retval);
665 }