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