]> CyberLeo.Net >> Repos - FreeBSD/stable/9.git/blob - lib/libdpv/dprompt.c
Similar to r274192: Enable dpv(1,3): Introduced [disabled] via r275047.
[FreeBSD/stable/9.git] / lib / libdpv / dprompt.c
1 /*-
2  * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29
30 #include <sys/types.h>
31
32 #define _BSD_SOURCE /* to get dprintf() prototype in stdio.h below */
33 #include <dialog.h>
34 #include <err.h>
35 #include <libutil.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <string_m.h>
41 #include <unistd.h>
42
43 #include "dialog_util.h"
44 #include "dialogrc.h"
45 #include "dprompt.h"
46 #include "dpv.h"
47 #include "dpv_private.h"
48
49 #define FLABEL_MAX 1024
50
51 static int fheight = 0; /* initialized by dprompt_init() */
52 static char dprompt[PROMPT_MAX + 1] = "";
53 static char *dprompt_pos = (char *)(0); /* treated numerically */
54
55 /* Display characteristics */
56 #define FM_DONE 0x01
57 #define FM_FAIL 0x02
58 #define FM_PEND 0x04
59 static uint8_t dprompt_free_mask;
60 static char *done = NULL;
61 static char *fail = NULL;
62 static char *pend = NULL;
63 int display_limit = DISPLAY_LIMIT_DEFAULT;      /* Max entries to show */
64 int label_size    = LABEL_SIZE_DEFAULT;         /* Max width for labels */
65 int pbar_size     = PBAR_SIZE_DEFAULT;          /* Mini-progressbar size */
66 static int gauge_percent = 0;
67 static int done_size, done_lsize, done_rsize;
68 static int fail_size, fail_lsize, fail_rsize;
69 static int mesg_size, mesg_lsize, mesg_rsize;
70 static int pend_size, pend_lsize, pend_rsize;
71 static int pct_lsize, pct_rsize;
72 static void *gauge = NULL;
73 #define SPIN_SIZE 4
74 static char spin[SPIN_SIZE + 1] = "/-\\|";
75 static char msg[PROMPT_MAX + 1];
76 static char *spin_cp = spin;
77
78 /* Function prototypes */
79 static char     spin_char(void);
80 static int      dprompt_add_files(struct dpv_file_node *file_list,
81                     struct dpv_file_node *curfile, int pct);
82
83 /*
84  * Returns a pointer to the current spin character in the spin string and
85  * advances the global position to the next character for the next call.
86  */
87 static char
88 spin_char(void)
89 {
90         char ch;
91
92         if (spin_cp == '\0')
93                 spin_cp = spin;
94         ch = *spin_cp;
95
96         /* Advance the spinner to the next char */
97         if (++spin_cp >= (spin + SPIN_SIZE))
98                 spin_cp = spin;
99
100         return (ch);
101 }
102
103 /*
104  * Initialize heights and widths based on various strings and environment
105  * variables (such as ENV_USE_COLOR).
106  */
107 void
108 dprompt_init(struct dpv_file_node *file_list)
109 {
110         uint8_t nls = 0;
111         int len;
112         int max_cols;
113         int max_rows;
114         int nthfile;
115         int numlines;
116         struct dpv_file_node *curfile;
117
118         /*
119          * Initialize dialog(3) `colors' support and draw backtitle
120          */
121         if (use_libdialog && !debug) {
122                 init_dialog(stdin, stdout);
123                 dialog_vars.colors = 1;
124                 if (backtitle != NULL) {
125                         dialog_vars.backtitle = (char *)backtitle;
126                         dlg_put_backtitle();
127                 }
128         }
129
130         /* Calculate width of dialog(3) or [X]dialog(1) --gauge box */
131         dwidth = label_size + pbar_size + 9;
132
133         /*
134          * Calculate height of dialog(3) or [X]dialog(1) --gauge box
135          */
136         dheight = 5;
137         max_rows = dialog_maxrows();
138         /* adjust max_rows for backtitle and/or dialog(3) statusLine */
139         if (backtitle != NULL)
140                 max_rows -= use_shadow ? 3 : 2;
141         if (use_libdialog && use_shadow)
142                 max_rows -= 2;
143         /* add lines for `-p text' */
144         numlines = dialog_prompt_numlines(pprompt, 0);
145         if (debug)
146                 warnx("`-p text' is %i line%s long", numlines,
147                     numlines == 1 ? "" : "s");
148         dheight += numlines;
149         /* adjust dheight for various implementations */
150         if (use_dialog) {
151                 dheight -= dialog_prompt_nlstate(pprompt);
152                 nls = dialog_prompt_nlstate(pprompt);
153         } else if (use_xdialog) {
154                 if (pprompt == NULL || *pprompt == '\0')
155                         dheight++;
156         } else if (use_libdialog) {
157                 if (pprompt != NULL && *pprompt != '\0')
158                         dheight--;
159         }
160         /* limit the number of display items (necessary per dialog(1,3)) */
161         if (display_limit == 0 || display_limit > DPV_DISPLAY_LIMIT)
162                 display_limit = DPV_DISPLAY_LIMIT;
163         /* verify fheight will fit (stop if we hit 1) */
164         for (; display_limit > 0; display_limit--) {
165                 nthfile = numlines = 0;
166                 fheight = (int)dpv_nfiles > display_limit ?
167                     (unsigned int)display_limit : dpv_nfiles;
168                 for (curfile = file_list; curfile != NULL;
169                     curfile = curfile->next) {
170                         nthfile++;
171                         numlines += dialog_prompt_numlines(curfile->name, nls);
172                         if ((nthfile % display_limit) == 0) {
173                                 if (numlines > fheight)
174                                         fheight = numlines;
175                                 numlines = nthfile = 0;
176                         }
177                 }
178                 if (numlines > fheight)
179                         fheight = numlines;
180                 if ((dheight + fheight +
181                     (int)dialog_prompt_numlines(aprompt, use_dialog) -
182                     (use_dialog ? (int)dialog_prompt_nlstate(aprompt) : 0))
183                     <= max_rows)
184                         break;  
185         }
186         /* don't show any items if we run the risk of hitting a blank set */
187         if ((max_rows - (use_shadow ? 5 : 4)) >= fheight)
188                 dheight += fheight;
189         else
190                 fheight = 0;
191         /* add lines for `-a text' */
192         numlines = dialog_prompt_numlines(aprompt, use_dialog);
193         if (debug)
194                 warnx("`-a text' is %i line%s long", numlines,
195                     numlines == 1 ? "" : "s");
196         dheight += numlines;
197
198         /* If using Xdialog(1), adjust accordingly (based on testing) */
199         if (use_xdialog)
200                 dheight += dheight / 4;
201
202         /* For wide mode, long prefix (`pprompt') or append (`aprompt')
203          * strings will bump width */
204         if (wide) {
205                 len = (int)dialog_prompt_longestline(pprompt, 0); /* !nls */
206                 if ((len + 4) > dwidth)
207                         dwidth = len + 4;
208                 len = (int)dialog_prompt_longestline(aprompt, 1); /* nls */
209                 if ((len + 4) > dwidth)
210                         dwidth = len + 4;
211         }
212
213         /* Enforce width constraints to maximum values */
214         max_cols = dialog_maxcols();
215         if (max_cols > 0 && dwidth > max_cols)
216                 dwidth = max_cols;
217
218         /* Optimize widths to sane values*/
219         if (pbar_size > dwidth - 9) {
220                 pbar_size = dwidth - 9;
221                 label_size = 0;
222                 /* -9 = "|  - [" ... "] |" */
223         }
224         if (pbar_size < 0)
225                 label_size = dwidth - 8;
226                 /* -8 = "|  " ... " -  |" */
227         else if (label_size > (dwidth - pbar_size - 9) || wide)
228                 label_size = no_labels ? 0 : dwidth - pbar_size - 9;
229                 /* -9 = "| " ... " - [" ... "] |" */
230
231         /* Hide labels if requested */
232         if (no_labels)
233                 label_size = 0;
234
235         /* Touch up the height (now that we know dwidth) */
236         dheight += dialog_prompt_wrappedlines(pprompt, dwidth - 4, 0);
237         dheight += dialog_prompt_wrappedlines(aprompt, dwidth - 4, 1);
238
239         if (debug)
240                 warnx("dheight = %i dwidth = %i fheight = %i",
241                     dheight, dwidth, fheight);
242
243         /* Calculate left/right portions of % */
244         pct_lsize = (pbar_size - 4) / 2; /* -4 == printf("%-3s%%", pct) */
245         pct_rsize = pct_lsize;
246         /* If not evenly divisible by 2, increment the right-side */
247         if ((pct_rsize + pct_rsize + 4) != pbar_size)
248                 pct_rsize++;
249
250         /* Initialize "Done" text */
251         if (done == NULL && (done = msg_done) == NULL) {
252                 if ((done = getenv(ENV_MSG_DONE)) != NULL)
253                         done_size = strlen(done);
254                 else {
255                         done_size = strlen(DPV_DONE_DEFAULT);
256                         if ((done = malloc(done_size + 1)) == NULL)
257                                 errx(EXIT_FAILURE, "Out of memory?!");
258                         dprompt_free_mask |= FM_DONE;
259                         snprintf(done, done_size + 1, DPV_DONE_DEFAULT);
260                 }
261         }
262         if (pbar_size < done_size) {
263                 done_lsize = done_rsize = 0;
264                 *(done + pbar_size) = '\0';
265                 done_size = pbar_size;
266         } else {
267                 /* Calculate left/right portions for mini-progressbar */
268                 done_lsize = (pbar_size - done_size) / 2;
269                 done_rsize = done_lsize;
270                 /* If not evenly divisible by 2, increment the right-side */
271                 if ((done_rsize + done_size + done_lsize) != pbar_size)
272                         done_rsize++;
273         }
274
275         /* Initialize "Fail" text */
276         if (fail == NULL && (fail = msg_fail) == NULL) {
277                 if ((fail = getenv(ENV_MSG_FAIL)) != NULL)
278                         fail_size = strlen(fail);
279                 else {
280                         fail_size = strlen(DPV_FAIL_DEFAULT);
281                         if ((fail = malloc(fail_size + 1)) == NULL)
282                                 errx(EXIT_FAILURE, "Out of memory?!");
283                         dprompt_free_mask |= FM_FAIL;
284                         snprintf(fail, fail_size + 1, DPV_FAIL_DEFAULT);
285                 }
286         }
287         if (pbar_size < fail_size) {
288                 fail_lsize = fail_rsize = 0;
289                 *(fail + pbar_size) = '\0';
290                 fail_size = pbar_size;
291         } else {
292                 /* Calculate left/right portions for mini-progressbar */
293                 fail_lsize = (pbar_size - fail_size) / 2;
294                 fail_rsize = fail_lsize;
295                 /* If not evenly divisible by 2, increment the right-side */
296                 if ((fail_rsize + fail_size + fail_lsize) != pbar_size)
297                         fail_rsize++;
298         }
299
300         /* Initialize "Pending" text */
301         if (pend == NULL && (pend = msg_pending) == NULL) {
302                 if ((pend = getenv(ENV_MSG_PENDING)) != NULL)
303                         pend_size = strlen(pend);
304                 else {
305                         pend_size = strlen(DPV_PENDING_DEFAULT);
306                         if ((pend = malloc(pend_size + 1)) == NULL)
307                                 errx(EXIT_FAILURE, "Out of memory?!");
308                         dprompt_free_mask |= FM_PEND;
309                         snprintf(pend, pend_size + 1, DPV_PENDING_DEFAULT);
310                 }
311         }
312         if (pbar_size < pend_size) {
313                 pend_lsize = pend_rsize = 0;
314                 *(pend + pbar_size) = '\0';
315                 pend_size = pbar_size;
316         } else {
317                 /* Calculate left/right portions for mini-progressbar */
318                 pend_lsize = (pbar_size - pend_size) / 2;
319                 pend_rsize = pend_lsize;
320                 /* If not evenly divisible by 2, increment the right-side */
321                 if ((pend_rsize + pend_lsize + pend_size) != pbar_size)
322                         pend_rsize++;
323         }
324
325         if (debug)
326                 warnx("label_size = %i pbar_size = %i", label_size, pbar_size);
327
328         dprompt_clear();
329 }
330
331 /*
332  * Clear the [X]dialog(1) `--gauge' prompt buffer.
333  */
334 void
335 dprompt_clear(void)
336 {
337
338         *dprompt = '\0';
339         dprompt_pos = dprompt;
340 }
341
342 /*
343  * Append to the [X]dialog(1) `--gauge' prompt buffer. Syntax is like printf(3)
344  * and returns the number of bytes appended to the buffer.
345  */
346 int
347 dprompt_add(const char *format, ...)
348 {
349         int len;
350         va_list ap;
351
352         if (dprompt_pos >= (dprompt + PROMPT_MAX))
353                 return (0);
354
355         va_start(ap, format);
356         len = vsnprintf(dprompt_pos, (size_t)(PROMPT_MAX -
357             (dprompt_pos - dprompt)), format, ap);
358         va_end(ap);
359         if (len == -1)
360                 errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow",
361                     __func__);
362
363         if ((dprompt_pos + len) < (dprompt + PROMPT_MAX))
364                 dprompt_pos += len;
365         else
366                 dprompt_pos = dprompt + PROMPT_MAX;
367
368         return (len);
369 }
370
371 /*
372  * Append active files to the [X]dialog(1) `--gauge' prompt buffer. Syntax
373  * requires a pointer to the head of the dpv_file_node linked-list. Returns the
374  * number of files processed successfully.
375  */
376 static int
377 dprompt_add_files(struct dpv_file_node *file_list,
378     struct dpv_file_node *curfile, int pct)
379 {
380         char c;
381         char bold_code = 'b'; /* default: enabled */
382         char color_code = '4'; /* default: blue */
383         uint8_t after_curfile = curfile != NULL ? FALSE : TRUE;
384         uint8_t nls = 0;
385         char *cp;
386         char *lastline;
387         char *name;
388         const char *bg_code;
389         const char *estext;
390         const char *format;
391         enum dprompt_state dstate;
392         int estext_lsize;
393         int estext_rsize;
394         int estext_size;
395         int flabel_size;
396         int hlen;
397         int lsize;
398         int nlines = 0;
399         int nthfile = 0;
400         int pwidth;
401         int rsize;
402         struct dpv_file_node *fp;
403         char flabel[FLABEL_MAX + 1];
404         char human[32];
405         char pbar[pbar_size + 16]; /* +15 for optional color */
406         char pbar_cap[sizeof(pbar)];
407         char pbar_fill[sizeof(pbar)];
408
409
410         /* Override color defaults with that of main progress bar */
411         if (use_colors || use_shadow) { /* NB: shadow enables color */
412                 color_code = gauge_color[0];
413                 /* NB: str[1] aka bg is unused */
414                 bold_code = gauge_color[2];
415         }
416
417         /*
418          * Create mini-progressbar for current file (if applicable)
419          */
420         *pbar = '\0';
421         if (pbar_size >= 0 && pct >= 0 && curfile != NULL &&
422             (curfile->length >= 0 || dialog_test)) {
423                 snprintf(pbar, pbar_size + 1, "%*s%3u%%%*s", pct_lsize, "",
424                     pct, pct_rsize, "");
425                 if (use_color) {
426                         /* Calculate the fill-width of progressbar */
427                         pwidth = pct * pbar_size / 100;
428                         /* Round up based on one-tenth of a percent */
429                         if ((pct * pbar_size % 100) > 50)
430                                 pwidth++;
431
432                         /*
433                          * Make two copies of pbar. Make one represent the fill
434                          * and the other the remainder (cap). We'll insert the
435                          * ANSI delimiter in between.
436                          */
437                         *pbar_fill = '\0';
438                         *pbar_cap = '\0';
439                         strncat(pbar_fill, (const char *)(pbar), dwidth);
440                         *(pbar_fill + pwidth) = '\0';
441                         strncat(pbar_cap, (const char *)(pbar+pwidth), dwidth);
442
443                         /* Finalize the mini [color] progressbar */
444                         snprintf(pbar, sizeof(pbar),
445                             "\\Z%c\\Zr\\Z%c%s%s%s\\Zn", bold_code, color_code,
446                             pbar_fill, "\\ZR", pbar_cap);
447                 }
448         }
449
450         for (fp = file_list; fp != NULL; fp = fp->next) {
451                 flabel_size = label_size;
452                 name = fp->name;
453                 nthfile++;
454
455                 /*
456                  * Support multiline filenames (where the filename is taken as
457                  * the last line and the text leading up to the last line can
458                  * be used as (for example) a heading/separator between files.
459                  */
460                 if (use_dialog)
461                         nls = dialog_prompt_nlstate(pprompt);
462                 nlines += dialog_prompt_numlines(name, nls);
463                 lastline = dialog_prompt_lastline(name, 1);
464                 if (name != lastline) {
465                         c = *lastline;
466                         *lastline = '\0';
467                         dprompt_add("%s", name);
468                         *lastline = c;
469                         name = lastline;
470                 }
471
472                 /* Support color codes (for dialog(1,3)) in file names */
473                 if ((use_dialog || use_libdialog) && use_color) {
474                         cp = name;
475                         while (*cp != '\0') {
476                                 if (*cp == '\\' && *(cp + 1) != '\0' &&
477                                     *(++cp) == 'Z' && *(cp + 1) != '\0') {
478                                         cp++;
479                                         flabel_size += 3;
480                                 }
481                                 cp++;
482                         }
483                         if (flabel_size > FLABEL_MAX)
484                                 flabel_size = FLABEL_MAX;
485                 }
486
487                 /* If no mini-progressbar, increase label width */
488                 if (pbar_size < 0 && flabel_size <= FLABEL_MAX - 2 &&
489                     no_labels == FALSE)
490                         flabel_size += 2;
491
492                 /* If name is too long, add an ellipsis */
493                 if (snprintf(flabel, flabel_size + 1, "%s", name) >
494                     flabel_size) sprintf(flabel + flabel_size - 3, "...");
495
496                 /*
497                  * Append the label (processing the current file differently)
498                  */
499                 if (fp == curfile && pct < 100) {
500                         /*
501                          * Add an ellipsis to current file name if it will fit.
502                          * There may be an ellipsis already from truncating the
503                          * label (in which case, we already have one).
504                          */
505                         cp = flabel + strlen(flabel);
506                         if (cp < (flabel + flabel_size))
507                                 snprintf(cp, flabel_size -
508                                     (cp - flabel) + 1, "...");
509
510                         /* Append label (with spinner and optional color) */
511                         dprompt_add("%s%-*s%s %c", use_color ? "\\Zb" : "",
512                             flabel_size, flabel, use_color ? "\\Zn" : "",
513                             spin_char());
514                 } else
515                         dprompt_add("%-*s%s %s", flabel_size,
516                             flabel, use_color ? "\\Zn" : "", " ");
517
518                 /*
519                  * Append pbar/status (processing the current file differently)
520                  */
521                 dstate = DPROMPT_NONE;
522                 if (fp->msg != NULL)
523                         dstate = DPROMPT_CUSTOM_MSG;
524                 else if (pbar_size < 0)
525                         dstate = DPROMPT_NONE;
526                 else if (pbar_size < 4)
527                         dstate = DPROMPT_MINIMAL;
528                 else if (after_curfile)
529                         dstate = DPROMPT_PENDING;
530                 else if (fp == curfile) {
531                         if (*pbar == '\0') {
532                                 if (fp->length < 0)
533                                         dstate = DPROMPT_DETAILS;
534                                 else if (fp->status == DPV_STATUS_RUNNING)
535                                         dstate = DPROMPT_DETAILS;
536                                 else
537                                         dstate = DPROMPT_END_STATE;
538                         }
539                         else if (dialog_test) /* status/length ignored */
540                                 dstate = pct < 100 ?
541                                     DPROMPT_PBAR : DPROMPT_END_STATE;
542                         else if (fp->status == DPV_STATUS_RUNNING)
543                                 dstate = fp->length < 0 ?
544                                     DPROMPT_DETAILS : DPROMPT_PBAR;
545                         else /* not running */
546                                 dstate = fp->length < 0 ?
547                                     DPROMPT_DETAILS : DPROMPT_END_STATE;
548                 } else { /* before curfile */
549                         if (dialog_test)
550                                 dstate = DPROMPT_END_STATE;
551                         else
552                                 dstate = fp->length < 0 ?
553                                     DPROMPT_DETAILS : DPROMPT_END_STATE;
554                 }
555                 format = use_color ?
556                     " [\\Z%c%s%-*s%s%-*s\\Zn]\\n" :
557                     " [%-*s%s%-*s]\\n";
558                 if (fp->status == DPV_STATUS_FAILED) {
559                         bg_code = "\\Zr\\Z1"; /* Red */
560                         estext_lsize = fail_lsize;
561                         estext_rsize = fail_rsize;
562                         estext_size = fail_size;
563                         estext = fail;
564                 } else { /* e.g., DPV_STATUS_DONE */
565                         bg_code = "\\Zr\\Z2"; /* Green */
566                         estext_lsize = done_lsize;
567                         estext_rsize = done_rsize;
568                         estext_size = done_size;
569                         estext = done;
570                 }
571                 switch (dstate) {
572                 case DPROMPT_PENDING: /* Future file(s) */
573                         dprompt_add(" [%-*s%s%-*s]\\n",
574                             pend_lsize, "", pend, pend_rsize, "");
575                         break;
576                 case DPROMPT_PBAR: /* Current file */
577                         dprompt_add(" [%s]\\n", pbar);
578                         break;
579                 case DPROMPT_END_STATE: /* Past/Current file(s) */
580                         if (use_color)
581                                 dprompt_add(format, bold_code, bg_code,
582                                     estext_lsize, "", estext,
583                                     estext_rsize, "");
584                         else
585                                 dprompt_add(format,
586                                     estext_lsize, "", estext,
587                                     estext_rsize, "");
588                         break;
589                 case DPROMPT_DETAILS: /* Past/Current file(s) */
590                         humanize_number(human, pbar_size + 2, fp->read, "",
591                             HN_AUTOSCALE, HN_NOSPACE | HN_DIVISOR_1000);
592
593                         /* Calculate center alignment */
594                         hlen = (int)strlen(human);
595                         lsize = (pbar_size - hlen) / 2;
596                         rsize = lsize;
597                         if ((lsize+hlen+rsize) != pbar_size)
598                                 rsize++;
599
600                         if (use_color)
601                                 dprompt_add(format, bold_code, bg_code,
602                                     lsize, "", human, rsize, "");
603                         else
604                                 dprompt_add(format,
605                                     lsize, "", human, rsize, "");
606                         break;
607                 case DPROMPT_CUSTOM_MSG: /* File-specific message override */
608                         snprintf(msg, PROMPT_MAX + 1, "%s", fp->msg);
609                         if (pbar_size < (mesg_size = strlen(msg))) {
610                                 mesg_lsize = mesg_rsize = 0;
611                                 *(msg + pbar_size) = '\0';
612                                 mesg_size = pbar_size;
613                         } else {
614                                 mesg_lsize = (pbar_size - mesg_size) / 2;
615                                 mesg_rsize = mesg_lsize;
616                                 if ((mesg_rsize + mesg_size + mesg_lsize)
617                                     != pbar_size)
618                                         mesg_rsize++;
619                         }
620                         if (use_color)
621                                 dprompt_add(format, bold_code, bg_code,
622                                     mesg_lsize, "", msg, mesg_rsize, "");
623                         else
624                                 dprompt_add(format, mesg_lsize, "", msg,
625                                     mesg_rsize, "");
626                         break;
627                 case DPROMPT_MINIMAL: /* Short progress bar, minimal room */
628                         if (use_color)
629                                 dprompt_add(format, bold_code, bg_code,
630                                     pbar_size, "", "", 0, "");
631                         else
632                                 dprompt_add(format, pbar_size, "", "", 0, "");
633                         break;
634                 case DPROMPT_NONE: /* pbar_size < 0 */
635                         /* FALLTHROUGH */
636                 default:
637                         dprompt_add(" \\n");
638                         /*
639                          * NB: Leading space required for the case when
640                          * spin_char() returns a single backslash [\] which
641                          * without the space, changes the meaning of `\n'
642                          */
643                 }
644
645                 /* Stop building if we've hit the internal limit */
646                 if (nthfile >= display_limit)
647                         break;
648
649                 /* If this is the current file, all others are pending */
650                 if (fp == curfile)
651                         after_curfile = TRUE;
652         }
653
654         /*
655          * Since we cannot change the height/width of the [X]dialog(1) widget
656          * after spawn, to make things look nice let's pad the height so that
657          * the `-a text' always appears in the same spot.
658          *
659          * NOTE: fheight is calculated in dprompt_init(). It represents the
660          * maximum height required to display the set of items (broken up into
661          * pieces of display_limit chunks) whose names contain the most
662          * newlines for any given set.
663          */
664         while (nlines < fheight) {
665                 dprompt_add("\n");
666                 nlines++;
667         }
668
669         return (nthfile);
670 }
671
672 /*
673  * Process the dpv_file_node linked-list of named files, re-generating the
674  * [X]dialog(1) `--gauge' prompt text for the current state of transfers.
675  */
676 void
677 dprompt_recreate(struct dpv_file_node *file_list,
678     struct dpv_file_node *curfile, int pct)
679 {
680         size_t len;
681
682         /*
683          * Re-Build the prompt text
684          */
685         dprompt_clear();
686         if (display_limit > 0)
687                 dprompt_add_files(file_list, curfile, pct);
688
689         /* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
690         if (use_xdialog) {
691                 /* Replace `\n' with `\n\\n\n' in dprompt */
692                 len = strlen(dprompt);
693                 len += strcount(dprompt, "\\n") * 5; /* +5 chars per count */
694                 if (len > PROMPT_MAX)
695                         errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow "
696                             "(%zu > %i)", __func__, len, PROMPT_MAX);
697                 if (replaceall(dprompt, "\\n", "\n\\n\n") < 0)
698                         err(EXIT_FAILURE, "%s: replaceall()", __func__);
699         }
700         else if (use_libdialog)
701                 strexpandnl(dprompt);
702 }
703
704 /*
705  * Print the [X]dialog(1) `--gauge' prompt text to a buffer.
706  */
707 int
708 dprompt_sprint(char * restrict str, const char *prefix, const char *append)
709 {
710
711         return (snprintf(str, PROMPT_MAX, "%s%s%s%s", use_color ? "\\Zn" : "",
712             prefix ? prefix : "", dprompt, append ? append : ""));
713 }
714
715 /*
716  * Print the [X]dialog(1) `--gauge' prompt text to file descriptor fd (could
717  * be STDOUT_FILENO or a pipe(2) file descriptor to actual [X]dialog(1)).
718  */
719 void
720 dprompt_dprint(int fd, const char *prefix, const char *append, int overall)
721 {
722         int percent = gauge_percent;
723
724         if (overall >= 0 && overall <= 100)
725                 gauge_percent = percent = overall;
726         dprintf(fd, "XXX\n%s%s%s%s\nXXX\n%i\n", use_color ? "\\Zn" : "",
727             prefix ? prefix : "", dprompt, append ? append : "", percent);
728         fsync(fd);
729 }
730
731 /*
732  * Print the dialog(3) `gauge' prompt text using libdialog.
733  */
734 void
735 dprompt_libprint(const char *prefix, const char *append, int overall)
736 {
737         int percent = gauge_percent;
738         char buf[DPV_PPROMPT_MAX + DPV_APROMPT_MAX + DPV_DISPLAY_LIMIT * 1024];
739
740         dprompt_sprint(buf, prefix, append);
741
742         if (overall >= 0 && overall <= 100)
743                 gauge_percent = percent = overall;
744         gauge = dlg_reallocate_gauge(gauge, title == NULL ? "" : title,
745             buf, dheight, dwidth, percent);
746         dlg_update_gauge(gauge, percent);
747 }
748
749 /*
750  * Free allocated items initialized by dprompt_init()
751  */
752 void
753 dprompt_free(void)
754 {
755         if ((dprompt_free_mask & FM_DONE) != 0) {
756                 dprompt_free_mask ^= FM_DONE;
757                 free(done);
758                 done = NULL;
759         }
760         if ((dprompt_free_mask & FM_FAIL) != 0) {
761                 dprompt_free_mask ^= FM_FAIL;
762                 free(fail);
763                 fail = NULL;
764         }
765         if ((dprompt_free_mask & FM_PEND) != 0) {
766                 dprompt_free_mask ^= FM_PEND;
767                 free(pend);
768                 pend = NULL;
769         }
770 }