dprompt.c revision 335406
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: stable/11/lib/libdpv/dprompt.c 335406 2018-06-20 05:45:41Z dteske $");
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
51static int fheight = 0; /* initialized by dprompt_init() */
52static char dprompt[PROMPT_MAX + 1] = "";
53static 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
59static uint8_t dprompt_free_mask;
60static char *done = NULL;
61static char *fail = NULL;
62static char *pend = NULL;
63int display_limit = DISPLAY_LIMIT_DEFAULT;	/* Max entries to show */
64int label_size    = LABEL_SIZE_DEFAULT;		/* Max width for labels */
65int pbar_size     = PBAR_SIZE_DEFAULT;		/* Mini-progressbar size */
66static int gauge_percent = 0;
67static int done_size, done_lsize, done_rsize;
68static int fail_size, fail_lsize, fail_rsize;
69static int mesg_size, mesg_lsize, mesg_rsize;
70static int pend_size, pend_lsize, pend_rsize;
71static int pct_lsize, pct_rsize;
72static void *gauge = NULL;
73#define SPIN_SIZE 4
74static char spin[SPIN_SIZE + 1] = "/-\\|";
75static char msg[PROMPT_MAX + 1];
76static char *spin_cp = spin;
77
78/* Function prototypes */
79static char	spin_char(void);
80static 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 */
87static char
88spin_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 */
107void
108dprompt_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 */
334void
335dprompt_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 */
346int
347dprompt_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 */
376static int
377dprompt_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 flabel_size;
395	int hlen;
396	int lsize;
397	int nlines = 0;
398	int nthfile = 0;
399	int pwidth;
400	int rsize;
401	struct dpv_file_node *fp;
402	char flabel[FLABEL_MAX + 1];
403	char human[32];
404	char pbar[pbar_size + 16]; /* +15 for optional color */
405	char pbar_cap[sizeof(pbar)];
406	char pbar_fill[sizeof(pbar)];
407
408
409	/* Override color defaults with that of main progress bar */
410	if (use_colors || use_shadow) { /* NB: shadow enables color */
411		color_code = gauge_color[0];
412		/* NB: str[1] aka bg is unused */
413		bold_code = gauge_color[2];
414	}
415
416	/*
417	 * Create mini-progressbar for current file (if applicable)
418	 */
419	*pbar = '\0';
420	if (pbar_size >= 0 && pct >= 0 && curfile != NULL &&
421	    (curfile->length >= 0 || dialog_test)) {
422		snprintf(pbar, pbar_size + 1, "%*s%3u%%%*s", pct_lsize, "",
423		    pct, pct_rsize, "");
424		if (use_color) {
425			/* Calculate the fill-width of progressbar */
426			pwidth = pct * pbar_size / 100;
427			/* Round up based on one-tenth of a percent */
428			if ((pct * pbar_size % 100) > 50)
429				pwidth++;
430
431			/*
432			 * Make two copies of pbar. Make one represent the fill
433			 * and the other the remainder (cap). We'll insert the
434			 * ANSI delimiter in between.
435			 */
436			*pbar_fill = '\0';
437			*pbar_cap = '\0';
438			strncat(pbar_fill, (const char *)(pbar), dwidth);
439			*(pbar_fill + pwidth) = '\0';
440			strncat(pbar_cap, (const char *)(pbar+pwidth), dwidth);
441
442			/* Finalize the mini [color] progressbar */
443			snprintf(pbar, sizeof(pbar),
444			    "\\Z%c\\Zr\\Z%c%s%s%s\\Zn", bold_code, color_code,
445			    pbar_fill, "\\ZR", pbar_cap);
446		}
447	}
448
449	for (fp = file_list; fp != NULL; fp = fp->next) {
450		flabel_size = label_size;
451		name = fp->name;
452		nthfile++;
453
454		/*
455		 * Support multiline filenames (where the filename is taken as
456		 * the last line and the text leading up to the last line can
457		 * be used as (for example) a heading/separator between files.
458		 */
459		if (use_dialog)
460			nls = dialog_prompt_nlstate(pprompt);
461		nlines += dialog_prompt_numlines(name, nls);
462		lastline = dialog_prompt_lastline(name, 1);
463		if (name != lastline) {
464			c = *lastline;
465			*lastline = '\0';
466			dprompt_add("%s", name);
467			*lastline = c;
468			name = lastline;
469		}
470
471		/* Support color codes (for dialog(1,3)) in file names */
472		if ((use_dialog || use_libdialog) && use_color) {
473			cp = name;
474			while (*cp != '\0') {
475				if (*cp == '\\' && *(cp + 1) != '\0' &&
476				    *(++cp) == 'Z' && *(cp + 1) != '\0') {
477					cp++;
478					flabel_size += 3;
479				}
480				cp++;
481			}
482			if (flabel_size > FLABEL_MAX)
483				flabel_size = FLABEL_MAX;
484		}
485
486		/* If no mini-progressbar, increase label width */
487		if (pbar_size < 0 && flabel_size <= FLABEL_MAX - 2 &&
488		    no_labels == FALSE)
489			flabel_size += 2;
490
491		/* If name is too long, add an ellipsis */
492		if (snprintf(flabel, flabel_size + 1, "%s", name) >
493		    flabel_size) sprintf(flabel + flabel_size - 3, "...");
494
495		/*
496		 * Append the label (processing the current file differently)
497		 */
498		if (fp == curfile && pct < 100) {
499			/*
500			 * Add an ellipsis to current file name if it will fit.
501			 * There may be an ellipsis already from truncating the
502			 * label (in which case, we already have one).
503			 */
504			cp = flabel + strlen(flabel);
505			if (cp < (flabel + flabel_size))
506				snprintf(cp, flabel_size -
507				    (cp - flabel) + 1, "...");
508
509			/* Append label (with spinner and optional color) */
510			dprompt_add("%s%-*s%s %c", use_color ? "\\Zb" : "",
511			    flabel_size, flabel, use_color ? "\\Zn" : "",
512			    spin_char());
513		} else
514			dprompt_add("%-*s%s %s", flabel_size,
515			    flabel, use_color ? "\\Zn" : "", " ");
516
517		/*
518		 * Append pbar/status (processing the current file differently)
519		 */
520		dstate = DPROMPT_NONE;
521		if (fp->msg != NULL)
522			dstate = DPROMPT_CUSTOM_MSG;
523		else if (pbar_size < 0)
524			dstate = DPROMPT_NONE;
525		else if (pbar_size < 4)
526			dstate = DPROMPT_MINIMAL;
527		else if (after_curfile)
528			dstate = DPROMPT_PENDING;
529		else if (fp == curfile) {
530			if (*pbar == '\0') {
531				if (fp->length < 0)
532					dstate = DPROMPT_DETAILS;
533				else if (fp->status == DPV_STATUS_RUNNING)
534					dstate = DPROMPT_DETAILS;
535				else
536					dstate = DPROMPT_END_STATE;
537			}
538			else if (dialog_test) /* status/length ignored */
539				dstate = pct < 100 ?
540				    DPROMPT_PBAR : DPROMPT_END_STATE;
541			else if (fp->status == DPV_STATUS_RUNNING)
542				dstate = fp->length < 0 ?
543				    DPROMPT_DETAILS : DPROMPT_PBAR;
544			else /* not running */
545				dstate = fp->length < 0 ?
546				    DPROMPT_DETAILS : DPROMPT_END_STATE;
547		} else { /* before curfile */
548			if (dialog_test)
549				dstate = DPROMPT_END_STATE;
550			else
551				dstate = fp->length < 0 ?
552				    DPROMPT_DETAILS : DPROMPT_END_STATE;
553		}
554		format = use_color ?
555		    " [\\Z%c%s%-*s%s%-*s\\Zn]\\n" :
556		    " [%-*s%s%-*s]\\n";
557		if (fp->status == DPV_STATUS_FAILED) {
558			bg_code = "\\Zr\\Z1"; /* Red */
559			estext_lsize = fail_lsize;
560			estext_rsize = fail_rsize;
561			estext = fail;
562		} else { /* e.g., DPV_STATUS_DONE */
563			bg_code = "\\Zr\\Z2"; /* Green */
564			estext_lsize = done_lsize;
565			estext_rsize = done_rsize;
566			estext = done;
567		}
568		switch (dstate) {
569		case DPROMPT_PENDING: /* Future file(s) */
570			dprompt_add(" [%-*s%s%-*s]\\n",
571			    pend_lsize, "", pend, pend_rsize, "");
572			break;
573		case DPROMPT_PBAR: /* Current file */
574			dprompt_add(" [%s]\\n", pbar);
575			break;
576		case DPROMPT_END_STATE: /* Past/Current file(s) */
577			if (use_color)
578				dprompt_add(format, bold_code, bg_code,
579				    estext_lsize, "", estext,
580				    estext_rsize, "");
581			else
582				dprompt_add(format,
583				    estext_lsize, "", estext,
584				    estext_rsize, "");
585			break;
586		case DPROMPT_DETAILS: /* Past/Current file(s) */
587			humanize_number(human, pbar_size + 2, fp->read, "",
588			    HN_AUTOSCALE, HN_NOSPACE | HN_DIVISOR_1000);
589
590			/* Calculate center alignment */
591			hlen = (int)strlen(human);
592			lsize = (pbar_size - hlen) / 2;
593			rsize = lsize;
594			if ((lsize+hlen+rsize) != pbar_size)
595				rsize++;
596
597			if (use_color)
598				dprompt_add(format, bold_code, bg_code,
599				    lsize, "", human, rsize, "");
600			else
601				dprompt_add(format,
602				    lsize, "", human, rsize, "");
603			break;
604		case DPROMPT_CUSTOM_MSG: /* File-specific message override */
605			snprintf(msg, PROMPT_MAX + 1, "%s", fp->msg);
606			if (pbar_size < (mesg_size = strlen(msg))) {
607				mesg_lsize = mesg_rsize = 0;
608				*(msg + pbar_size) = '\0';
609				mesg_size = pbar_size;
610			} else {
611				mesg_lsize = (pbar_size - mesg_size) / 2;
612				mesg_rsize = mesg_lsize;
613				if ((mesg_rsize + mesg_size + mesg_lsize)
614				    != pbar_size)
615					mesg_rsize++;
616			}
617			if (use_color)
618				dprompt_add(format, bold_code, bg_code,
619				    mesg_lsize, "", msg, mesg_rsize, "");
620			else
621				dprompt_add(format, mesg_lsize, "", msg,
622				    mesg_rsize, "");
623			break;
624		case DPROMPT_MINIMAL: /* Short progress bar, minimal room */
625			if (use_color)
626				dprompt_add(format, bold_code, bg_code,
627				    pbar_size, "", "", 0, "");
628			else
629				dprompt_add(format, pbar_size, "", "", 0, "");
630			break;
631		case DPROMPT_NONE: /* pbar_size < 0 */
632			/* FALLTHROUGH */
633		default:
634			dprompt_add(" \\n");
635			/*
636			 * NB: Leading space required for the case when
637			 * spin_char() returns a single backslash [\] which
638			 * without the space, changes the meaning of `\n'
639			 */
640		}
641
642		/* Stop building if we've hit the internal limit */
643		if (nthfile >= display_limit)
644			break;
645
646		/* If this is the current file, all others are pending */
647		if (fp == curfile)
648			after_curfile = TRUE;
649	}
650
651	/*
652	 * Since we cannot change the height/width of the [X]dialog(1) widget
653	 * after spawn, to make things look nice let's pad the height so that
654	 * the `-a text' always appears in the same spot.
655	 *
656	 * NOTE: fheight is calculated in dprompt_init(). It represents the
657	 * maximum height required to display the set of items (broken up into
658	 * pieces of display_limit chunks) whose names contain the most
659	 * newlines for any given set.
660	 */
661	while (nlines < fheight) {
662		dprompt_add("\n");
663		nlines++;
664	}
665
666	return (nthfile);
667}
668
669/*
670 * Process the dpv_file_node linked-list of named files, re-generating the
671 * [X]dialog(1) `--gauge' prompt text for the current state of transfers.
672 */
673void
674dprompt_recreate(struct dpv_file_node *file_list,
675    struct dpv_file_node *curfile, int pct)
676{
677	size_t len;
678
679	/*
680	 * Re-Build the prompt text
681	 */
682	dprompt_clear();
683	if (display_limit > 0)
684		dprompt_add_files(file_list, curfile, pct);
685
686	/* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
687	if (use_xdialog) {
688		/* Replace `\n' with `\n\\n\n' in dprompt */
689		len = strlen(dprompt);
690		len += strcount(dprompt, "\\n") * 5; /* +5 chars per count */
691		if (len > PROMPT_MAX)
692			errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow "
693			    "(%zu > %i)", __func__, len, PROMPT_MAX);
694		if (replaceall(dprompt, "\\n", "\n\\n\n") < 0)
695			err(EXIT_FAILURE, "%s: replaceall()", __func__);
696	}
697	else if (use_libdialog)
698		strexpandnl(dprompt);
699}
700
701/*
702 * Print the [X]dialog(1) `--gauge' prompt text to a buffer.
703 */
704int
705dprompt_sprint(char * restrict str, const char *prefix, const char *append)
706{
707
708	return (snprintf(str, PROMPT_MAX, "%s%s%s%s", use_color ? "\\Zn" : "",
709	    prefix ? prefix : "", dprompt, append ? append : ""));
710}
711
712/*
713 * Print the [X]dialog(1) `--gauge' prompt text to file descriptor fd (could
714 * be STDOUT_FILENO or a pipe(2) file descriptor to actual [X]dialog(1)).
715 */
716void
717dprompt_dprint(int fd, const char *prefix, const char *append, int overall)
718{
719	int percent = gauge_percent;
720
721	if (overall >= 0 && overall <= 100)
722		gauge_percent = percent = overall;
723	dprintf(fd, "XXX\n%s%s%s%s\nXXX\n%i\n", use_color ? "\\Zn" : "",
724	    prefix ? prefix : "", dprompt, append ? append : "", percent);
725	fsync(fd);
726}
727
728/*
729 * Print the dialog(3) `gauge' prompt text using libdialog.
730 */
731void
732dprompt_libprint(const char *prefix, const char *append, int overall)
733{
734	int percent = gauge_percent;
735	char buf[DPV_PPROMPT_MAX + DPV_APROMPT_MAX + DPV_DISPLAY_LIMIT * 1024];
736
737	dprompt_sprint(buf, prefix, append);
738
739	if (overall >= 0 && overall <= 100)
740		gauge_percent = percent = overall;
741	gauge = dlg_reallocate_gauge(gauge, title == NULL ? "" : title,
742	    buf, dheight, dwidth, percent);
743	dlg_update_gauge(gauge, percent);
744}
745
746/*
747 * Free allocated items initialized by dprompt_init()
748 */
749void
750dprompt_free(void)
751{
752	if ((dprompt_free_mask & FM_DONE) != 0) {
753		dprompt_free_mask ^= FM_DONE;
754		free(done);
755		done = NULL;
756	}
757	if ((dprompt_free_mask & FM_FAIL) != 0) {
758		dprompt_free_mask ^= FM_FAIL;
759		free(fail);
760		fail = NULL;
761	}
762	if ((dprompt_free_mask & FM_PEND) != 0) {
763		dprompt_free_mask ^= FM_PEND;
764		free(pend);
765		pend = NULL;
766	}
767}
768