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
draw_bar(struct bar * b)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
update_barbox(struct dialog * d,struct bar * b,bool buttons)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
bsddialog_gauge(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int perc,int fd,const char * sep,const char * end)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
do_mixedgauge(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int mainperc,unsigned int nminibars,const char ** minilabels,int * minipercs,bool color)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
bsddialog_mixedgauge(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int mainperc,unsigned int nminibars,const char ** minilabels,int * minipercs)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
bsddialog_progressview(struct bsddialog_conf * conf,const char * text,int rows,int cols,struct bsddialog_progviewconf * pvconf,unsigned int nminibar,struct bsddialog_fileminibar * minibar)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
rangebox_redraw(struct dialog * d,struct bar * b,int * bigchange)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
bsddialog_rangebox(struct bsddialog_conf * conf,const char * text,int rows,int cols,int min,int max,int * value)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
pause_redraw(struct dialog * d,struct bar * b)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
bsddialog_pause(struct bsddialog_conf * conf,const char * text,int rows,int cols,unsigned int * seconds)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 }
666