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