1274116Sdteske/*-
2274116Sdteske * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
3274116Sdteske * All rights reserved.
4274116Sdteske *
5274116Sdteske * Redistribution and use in source and binary forms, with or without
6274116Sdteske * modification, are permitted provided that the following conditions
7274116Sdteske * are met:
8274116Sdteske * 1. Redistributions of source code must retain the above copyright
9274116Sdteske *    notice, this list of conditions and the following disclaimer.
10274116Sdteske * 2. Redistributions in binary form must reproduce the above copyright
11274116Sdteske *    notice, this list of conditions and the following disclaimer in the
12274116Sdteske *    documentation and/or other materials provided with the distribution.
13274116Sdteske *
14274116Sdteske * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15274116Sdteske * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16274116Sdteske * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17274116Sdteske * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18274116Sdteske * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19274116Sdteske * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20274116Sdteske * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21274116Sdteske * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22274116Sdteske * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23274116Sdteske * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24274116Sdteske * SUCH DAMAGE.
25274116Sdteske */
26274116Sdteske
27274116Sdteske#include <sys/cdefs.h>
28274116Sdteske__FBSDID("$FreeBSD: releng/10.2/lib/libdpv/dprompt.c 284714 2015-06-23 04:03:54Z dteske $");
29274116Sdteske
30274116Sdteske#include <sys/types.h>
31274116Sdteske
32274116Sdteske#define _BSD_SOURCE /* to get dprintf() prototype in stdio.h below */
33274116Sdteske#include <dialog.h>
34274116Sdteske#include <err.h>
35274116Sdteske#include <libutil.h>
36274116Sdteske#include <stdarg.h>
37274116Sdteske#include <stdio.h>
38274116Sdteske#include <stdlib.h>
39274116Sdteske#include <string.h>
40274116Sdteske#include <string_m.h>
41274116Sdteske#include <unistd.h>
42274116Sdteske
43274116Sdteske#include "dialog_util.h"
44274116Sdteske#include "dialogrc.h"
45274116Sdteske#include "dprompt.h"
46274116Sdteske#include "dpv.h"
47274116Sdteske#include "dpv_private.h"
48274116Sdteske
49274116Sdteske#define FLABEL_MAX 1024
50274116Sdteske
51274116Sdteskestatic int fheight = 0; /* initialized by dprompt_init() */
52274116Sdteskestatic char dprompt[PROMPT_MAX + 1] = "";
53274116Sdteskestatic char *dprompt_pos = (char *)(0); /* treated numerically */
54274116Sdteske
55274116Sdteske/* Display characteristics */
56274116Sdteske#define FM_DONE 0x01
57274116Sdteske#define FM_FAIL 0x02
58274116Sdteske#define FM_PEND 0x04
59274116Sdteskestatic uint8_t dprompt_free_mask;
60274116Sdteskestatic char *done = NULL;
61274116Sdteskestatic char *fail = NULL;
62274116Sdteskestatic char *pend = NULL;
63274116Sdteskeint display_limit = DISPLAY_LIMIT_DEFAULT;	/* Max entries to show */
64274116Sdteskeint label_size    = LABEL_SIZE_DEFAULT;		/* Max width for labels */
65274116Sdteskeint pbar_size     = PBAR_SIZE_DEFAULT;		/* Mini-progressbar size */
66274116Sdteskestatic int gauge_percent = 0;
67274116Sdteskestatic int done_size, done_lsize, done_rsize;
68274116Sdteskestatic int fail_size, fail_lsize, fail_rsize;
69274116Sdteskestatic int mesg_size, mesg_lsize, mesg_rsize;
70274116Sdteskestatic int pend_size, pend_lsize, pend_rsize;
71274116Sdteskestatic int pct_lsize, pct_rsize;
72274116Sdteskestatic void *gauge = NULL;
73274116Sdteske#define SPIN_SIZE 4
74274116Sdteskestatic char spin[SPIN_SIZE + 1] = "/-\\|";
75274116Sdteskestatic char msg[PROMPT_MAX + 1];
76274116Sdteskestatic char *spin_cp = spin;
77274116Sdteske
78274116Sdteske/* Function prototypes */
79274116Sdteskestatic char	spin_char(void);
80274116Sdteskestatic int	dprompt_add_files(struct dpv_file_node *file_list,
81274116Sdteske		    struct dpv_file_node *curfile, int pct);
82274116Sdteske
83274116Sdteske/*
84274116Sdteske * Returns a pointer to the current spin character in the spin string and
85274116Sdteske * advances the global position to the next character for the next call.
86274116Sdteske */
87274116Sdteskestatic char
88274116Sdteskespin_char(void)
89274116Sdteske{
90274116Sdteske	char ch;
91274116Sdteske
92274116Sdteske	if (spin_cp == '\0')
93274116Sdteske		spin_cp = spin;
94274116Sdteske	ch = *spin_cp;
95274116Sdteske
96274116Sdteske	/* Advance the spinner to the next char */
97274116Sdteske	if (++spin_cp >= (spin + SPIN_SIZE))
98274116Sdteske		spin_cp = spin;
99274116Sdteske
100274116Sdteske	return (ch);
101274116Sdteske}
102274116Sdteske
103274116Sdteske/*
104274116Sdteske * Initialize heights and widths based on various strings and environment
105274116Sdteske * variables (such as ENV_USE_COLOR).
106274116Sdteske */
107274116Sdteskevoid
108274116Sdteskedprompt_init(struct dpv_file_node *file_list)
109274116Sdteske{
110274116Sdteske	uint8_t nls = 0;
111274116Sdteske	int len;
112274116Sdteske	int max_cols;
113274116Sdteske	int max_rows;
114274116Sdteske	int nthfile;
115274116Sdteske	int numlines;
116274116Sdteske	struct dpv_file_node *curfile;
117274116Sdteske
118274116Sdteske	/*
119274116Sdteske	 * Initialize dialog(3) `colors' support and draw backtitle
120274116Sdteske	 */
121274116Sdteske	if (use_libdialog && !debug) {
122274116Sdteske		init_dialog(stdin, stdout);
123274116Sdteske		dialog_vars.colors = 1;
124274116Sdteske		if (backtitle != NULL) {
125274116Sdteske			dialog_vars.backtitle = (char *)backtitle;
126274116Sdteske			dlg_put_backtitle();
127274116Sdteske		}
128274116Sdteske	}
129274116Sdteske
130274116Sdteske	/* Calculate width of dialog(3) or [X]dialog(1) --gauge box */
131274116Sdteske	dwidth = label_size + pbar_size + 9;
132274116Sdteske
133274116Sdteske	/*
134274116Sdteske	 * Calculate height of dialog(3) or [X]dialog(1) --gauge box
135274116Sdteske	 */
136274116Sdteske	dheight = 5;
137274116Sdteske	max_rows = dialog_maxrows();
138274116Sdteske	/* adjust max_rows for backtitle and/or dialog(3) statusLine */
139274116Sdteske	if (backtitle != NULL)
140274116Sdteske		max_rows -= use_shadow ? 3 : 2;
141274116Sdteske	if (use_libdialog && use_shadow)
142274116Sdteske		max_rows -= 2;
143274116Sdteske	/* add lines for `-p text' */
144274116Sdteske	numlines = dialog_prompt_numlines(pprompt, 0);
145274116Sdteske	if (debug)
146274116Sdteske		warnx("`-p text' is %i line%s long", numlines,
147274116Sdteske		    numlines == 1 ? "" : "s");
148274116Sdteske	dheight += numlines;
149274116Sdteske	/* adjust dheight for various implementations */
150274116Sdteske	if (use_dialog) {
151274116Sdteske		dheight -= dialog_prompt_nlstate(pprompt);
152274116Sdteske		nls = dialog_prompt_nlstate(pprompt);
153274116Sdteske	} else if (use_xdialog) {
154274116Sdteske		if (pprompt == NULL || *pprompt == '\0')
155274116Sdteske			dheight++;
156274116Sdteske	} else if (use_libdialog) {
157274116Sdteske		if (pprompt != NULL && *pprompt != '\0')
158274116Sdteske			dheight--;
159274116Sdteske	}
160274116Sdteske	/* limit the number of display items (necessary per dialog(1,3)) */
161274116Sdteske	if (display_limit == 0 || display_limit > DPV_DISPLAY_LIMIT)
162274116Sdteske		display_limit = DPV_DISPLAY_LIMIT;
163274116Sdteske	/* verify fheight will fit (stop if we hit 1) */
164274116Sdteske	for (; display_limit > 0; display_limit--) {
165274116Sdteske		nthfile = numlines = 0;
166274116Sdteske		fheight = (int)dpv_nfiles > display_limit ?
167274116Sdteske		    (unsigned int)display_limit : dpv_nfiles;
168274116Sdteske		for (curfile = file_list; curfile != NULL;
169274116Sdteske		    curfile = curfile->next) {
170274116Sdteske			nthfile++;
171274116Sdteske			numlines += dialog_prompt_numlines(curfile->name, nls);
172274116Sdteske			if ((nthfile % display_limit) == 0) {
173274116Sdteske				if (numlines > fheight)
174274116Sdteske					fheight = numlines;
175274116Sdteske				numlines = nthfile = 0;
176274116Sdteske			}
177274116Sdteske		}
178274116Sdteske		if (numlines > fheight)
179274116Sdteske			fheight = numlines;
180274116Sdteske		if ((dheight + fheight +
181274116Sdteske		    (int)dialog_prompt_numlines(aprompt, use_dialog) -
182274116Sdteske		    (use_dialog ? (int)dialog_prompt_nlstate(aprompt) : 0))
183274116Sdteske		    <= max_rows)
184274116Sdteske			break;
185274116Sdteske	}
186274116Sdteske	/* don't show any items if we run the risk of hitting a blank set */
187274116Sdteske	if ((max_rows - (use_shadow ? 5 : 4)) >= fheight)
188274116Sdteske		dheight += fheight;
189274116Sdteske	else
190274116Sdteske		fheight = 0;
191274116Sdteske	/* add lines for `-a text' */
192274116Sdteske	numlines = dialog_prompt_numlines(aprompt, use_dialog);
193274116Sdteske	if (debug)
194274116Sdteske		warnx("`-a text' is %i line%s long", numlines,
195274116Sdteske		    numlines == 1 ? "" : "s");
196274116Sdteske	dheight += numlines;
197274116Sdteske
198274116Sdteske	/* If using Xdialog(1), adjust accordingly (based on testing) */
199274116Sdteske	if (use_xdialog)
200274116Sdteske		dheight += dheight / 4;
201274116Sdteske
202274116Sdteske	/* For wide mode, long prefix (`pprompt') or append (`aprompt')
203274116Sdteske	 * strings will bump width */
204274116Sdteske	if (wide) {
205274116Sdteske		len = (int)dialog_prompt_longestline(pprompt, 0); /* !nls */
206274116Sdteske		if ((len + 4) > dwidth)
207274116Sdteske			dwidth = len + 4;
208274116Sdteske		len = (int)dialog_prompt_longestline(aprompt, 1); /* nls */
209274116Sdteske		if ((len + 4) > dwidth)
210274116Sdteske			dwidth = len + 4;
211274116Sdteske	}
212274116Sdteske
213274116Sdteske	/* Enforce width constraints to maximum values */
214274116Sdteske	max_cols = dialog_maxcols();
215274116Sdteske	if (max_cols > 0 && dwidth > max_cols)
216274116Sdteske		dwidth = max_cols;
217274116Sdteske
218274116Sdteske	/* Optimize widths to sane values*/
219274116Sdteske	if (pbar_size > dwidth - 9) {
220274116Sdteske		pbar_size = dwidth - 9;
221274116Sdteske		label_size = 0;
222274116Sdteske		/* -9 = "|  - [" ... "] |" */
223274116Sdteske	}
224274116Sdteske	if (pbar_size < 0)
225274116Sdteske		label_size = dwidth - 8;
226274116Sdteske		/* -8 = "|  " ... " -  |" */
227274116Sdteske	else if (label_size > (dwidth - pbar_size - 9) || wide)
228274116Sdteske		label_size = no_labels ? 0 : dwidth - pbar_size - 9;
229274116Sdteske		/* -9 = "| " ... " - [" ... "] |" */
230274116Sdteske
231274116Sdteske	/* Hide labels if requested */
232274116Sdteske	if (no_labels)
233274116Sdteske		label_size = 0;
234274116Sdteske
235274116Sdteske	/* Touch up the height (now that we know dwidth) */
236274116Sdteske	dheight += dialog_prompt_wrappedlines(pprompt, dwidth - 4, 0);
237274116Sdteske	dheight += dialog_prompt_wrappedlines(aprompt, dwidth - 4, 1);
238274116Sdteske
239274116Sdteske	if (debug)
240274116Sdteske		warnx("dheight = %i dwidth = %i fheight = %i",
241274116Sdteske		    dheight, dwidth, fheight);
242274116Sdteske
243274116Sdteske	/* Calculate left/right portions of % */
244274116Sdteske	pct_lsize = (pbar_size - 4) / 2; /* -4 == printf("%-3s%%", pct) */
245274116Sdteske	pct_rsize = pct_lsize;
246274116Sdteske	/* If not evenly divisible by 2, increment the right-side */
247274116Sdteske	if ((pct_rsize + pct_rsize + 4) != pbar_size)
248274116Sdteske		pct_rsize++;
249274116Sdteske
250274116Sdteske	/* Initialize "Done" text */
251274116Sdteske	if (done == NULL && (done = msg_done) == NULL) {
252274116Sdteske		if ((done = getenv(ENV_MSG_DONE)) != NULL)
253274116Sdteske			done_size = strlen(done);
254274116Sdteske		else {
255274116Sdteske			done_size = strlen(DPV_DONE_DEFAULT);
256274116Sdteske			if ((done = malloc(done_size + 1)) == NULL)
257274116Sdteske				errx(EXIT_FAILURE, "Out of memory?!");
258274116Sdteske			dprompt_free_mask |= FM_DONE;
259274116Sdteske			snprintf(done, done_size + 1, DPV_DONE_DEFAULT);
260274116Sdteske		}
261274116Sdteske	}
262274116Sdteske	if (pbar_size < done_size) {
263274116Sdteske		done_lsize = done_rsize = 0;
264274116Sdteske		*(done + pbar_size) = '\0';
265274116Sdteske		done_size = pbar_size;
266274116Sdteske	} else {
267274116Sdteske		/* Calculate left/right portions for mini-progressbar */
268274116Sdteske		done_lsize = (pbar_size - done_size) / 2;
269274116Sdteske		done_rsize = done_lsize;
270274116Sdteske		/* If not evenly divisible by 2, increment the right-side */
271274116Sdteske		if ((done_rsize + done_size + done_lsize) != pbar_size)
272274116Sdteske			done_rsize++;
273274116Sdteske	}
274274116Sdteske
275274116Sdteske	/* Initialize "Fail" text */
276274116Sdteske	if (fail == NULL && (fail = msg_fail) == NULL) {
277274116Sdteske		if ((fail = getenv(ENV_MSG_FAIL)) != NULL)
278274116Sdteske			fail_size = strlen(fail);
279274116Sdteske		else {
280274116Sdteske			fail_size = strlen(DPV_FAIL_DEFAULT);
281274116Sdteske			if ((fail = malloc(fail_size + 1)) == NULL)
282274116Sdteske				errx(EXIT_FAILURE, "Out of memory?!");
283274116Sdteske			dprompt_free_mask |= FM_FAIL;
284274116Sdteske			snprintf(fail, fail_size + 1, DPV_FAIL_DEFAULT);
285274116Sdteske		}
286274116Sdteske	}
287274116Sdteske	if (pbar_size < fail_size) {
288274116Sdteske		fail_lsize = fail_rsize = 0;
289274116Sdteske		*(fail + pbar_size) = '\0';
290274116Sdteske		fail_size = pbar_size;
291274116Sdteske	} else {
292274116Sdteske		/* Calculate left/right portions for mini-progressbar */
293274116Sdteske		fail_lsize = (pbar_size - fail_size) / 2;
294274116Sdteske		fail_rsize = fail_lsize;
295274116Sdteske		/* If not evenly divisible by 2, increment the right-side */
296274116Sdteske		if ((fail_rsize + fail_size + fail_lsize) != pbar_size)
297274116Sdteske			fail_rsize++;
298274116Sdteske	}
299274116Sdteske
300274116Sdteske	/* Initialize "Pending" text */
301274116Sdteske	if (pend == NULL && (pend = msg_pending) == NULL) {
302274116Sdteske		if ((pend = getenv(ENV_MSG_PENDING)) != NULL)
303274116Sdteske			pend_size = strlen(pend);
304274116Sdteske		else {
305274116Sdteske			pend_size = strlen(DPV_PENDING_DEFAULT);
306274116Sdteske			if ((pend = malloc(pend_size + 1)) == NULL)
307274116Sdteske				errx(EXIT_FAILURE, "Out of memory?!");
308274116Sdteske			dprompt_free_mask |= FM_PEND;
309274116Sdteske			snprintf(pend, pend_size + 1, DPV_PENDING_DEFAULT);
310274116Sdteske		}
311274116Sdteske	}
312274116Sdteske	if (pbar_size < pend_size) {
313274116Sdteske		pend_lsize = pend_rsize = 0;
314274116Sdteske		*(pend + pbar_size) = '\0';
315274116Sdteske		pend_size = pbar_size;
316274116Sdteske	} else {
317274116Sdteske		/* Calculate left/right portions for mini-progressbar */
318274116Sdteske		pend_lsize = (pbar_size - pend_size) / 2;
319274116Sdteske		pend_rsize = pend_lsize;
320274116Sdteske		/* If not evenly divisible by 2, increment the right-side */
321274116Sdteske		if ((pend_rsize + pend_lsize + pend_size) != pbar_size)
322274116Sdteske			pend_rsize++;
323274116Sdteske	}
324274116Sdteske
325274116Sdteske	if (debug)
326274116Sdteske		warnx("label_size = %i pbar_size = %i", label_size, pbar_size);
327274116Sdteske
328274116Sdteske	dprompt_clear();
329274116Sdteske}
330274116Sdteske
331274116Sdteske/*
332274116Sdteske * Clear the [X]dialog(1) `--gauge' prompt buffer.
333274116Sdteske */
334274116Sdteskevoid
335274116Sdteskedprompt_clear(void)
336274116Sdteske{
337274116Sdteske
338274116Sdteske	*dprompt = '\0';
339274116Sdteske	dprompt_pos = dprompt;
340274116Sdteske}
341274116Sdteske
342274116Sdteske/*
343274116Sdteske * Append to the [X]dialog(1) `--gauge' prompt buffer. Syntax is like printf(3)
344274116Sdteske * and returns the number of bytes appended to the buffer.
345274116Sdteske */
346274116Sdteskeint
347274116Sdteskedprompt_add(const char *format, ...)
348274116Sdteske{
349274116Sdteske	int len;
350274116Sdteske	va_list ap;
351274116Sdteske
352274116Sdteske	if (dprompt_pos >= (dprompt + PROMPT_MAX))
353274116Sdteske		return (0);
354274116Sdteske
355274116Sdteske	va_start(ap, format);
356274116Sdteske	len = vsnprintf(dprompt_pos, (size_t)(PROMPT_MAX -
357274116Sdteske	    (dprompt_pos - dprompt)), format, ap);
358274116Sdteske	va_end(ap);
359274116Sdteske	if (len == -1)
360274116Sdteske		errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow",
361274116Sdteske		    __func__);
362274116Sdteske
363274116Sdteske	if ((dprompt_pos + len) < (dprompt + PROMPT_MAX))
364274116Sdteske		dprompt_pos += len;
365274116Sdteske	else
366274116Sdteske		dprompt_pos = dprompt + PROMPT_MAX;
367274116Sdteske
368274116Sdteske	return (len);
369274116Sdteske}
370274116Sdteske
371274116Sdteske/*
372274116Sdteske * Append active files to the [X]dialog(1) `--gauge' prompt buffer. Syntax
373274116Sdteske * requires a pointer to the head of the dpv_file_node linked-list. Returns the
374274116Sdteske * number of files processed successfully.
375274116Sdteske */
376274116Sdteskestatic int
377274116Sdteskedprompt_add_files(struct dpv_file_node *file_list,
378274116Sdteske    struct dpv_file_node *curfile, int pct)
379274116Sdteske{
380274116Sdteske	char c;
381274116Sdteske	char bold_code = 'b'; /* default: enabled */
382274116Sdteske	char color_code = '4'; /* default: blue */
383274116Sdteske	uint8_t after_curfile = curfile != NULL ? FALSE : TRUE;
384274116Sdteske	uint8_t nls = 0;
385274116Sdteske	char *cp;
386274116Sdteske	char *lastline;
387274116Sdteske	char *name;
388274116Sdteske	const char *bg_code;
389274116Sdteske	const char *estext;
390274116Sdteske	const char *format;
391274116Sdteske	enum dprompt_state dstate;
392274116Sdteske	int estext_lsize;
393274116Sdteske	int estext_rsize;
394274116Sdteske	int flabel_size;
395274116Sdteske	int hlen;
396274116Sdteske	int lsize;
397274116Sdteske	int nlines = 0;
398274116Sdteske	int nthfile = 0;
399274116Sdteske	int pwidth;
400274116Sdteske	int rsize;
401274116Sdteske	struct dpv_file_node *fp;
402274116Sdteske	char flabel[FLABEL_MAX + 1];
403274116Sdteske	char human[32];
404274116Sdteske	char pbar[pbar_size + 16]; /* +15 for optional color */
405274116Sdteske	char pbar_cap[sizeof(pbar)];
406274116Sdteske	char pbar_fill[sizeof(pbar)];
407274116Sdteske
408274116Sdteske
409274116Sdteske	/* Override color defaults with that of main progress bar */
410274116Sdteske	if (use_colors || use_shadow) { /* NB: shadow enables color */
411274116Sdteske		color_code = gauge_color[0];
412274116Sdteske		/* NB: str[1] aka bg is unused */
413274116Sdteske		bold_code = gauge_color[2];
414274116Sdteske	}
415274116Sdteske
416274116Sdteske	/*
417274116Sdteske	 * Create mini-progressbar for current file (if applicable)
418274116Sdteske	 */
419274116Sdteske	*pbar = '\0';
420274116Sdteske	if (pbar_size >= 0 && pct >= 0 && curfile != NULL &&
421274116Sdteske	    (curfile->length >= 0 || dialog_test)) {
422274116Sdteske		snprintf(pbar, pbar_size + 1, "%*s%3u%%%*s", pct_lsize, "",
423274116Sdteske		    pct, pct_rsize, "");
424274116Sdteske		if (use_color) {
425274116Sdteske			/* Calculate the fill-width of progressbar */
426274116Sdteske			pwidth = pct * pbar_size / 100;
427274116Sdteske			/* Round up based on one-tenth of a percent */
428274116Sdteske			if ((pct * pbar_size % 100) > 50)
429274116Sdteske				pwidth++;
430274116Sdteske
431274116Sdteske			/*
432274116Sdteske			 * Make two copies of pbar. Make one represent the fill
433274116Sdteske			 * and the other the remainder (cap). We'll insert the
434274116Sdteske			 * ANSI delimiter in between.
435274116Sdteske			 */
436274116Sdteske			*pbar_fill = '\0';
437274116Sdteske			*pbar_cap = '\0';
438274116Sdteske			strncat(pbar_fill, (const char *)(pbar), dwidth);
439274116Sdteske			*(pbar_fill + pwidth) = '\0';
440274116Sdteske			strncat(pbar_cap, (const char *)(pbar+pwidth), dwidth);
441274116Sdteske
442274116Sdteske			/* Finalize the mini [color] progressbar */
443274116Sdteske			snprintf(pbar, sizeof(pbar),
444274116Sdteske			    "\\Z%c\\Zr\\Z%c%s%s%s\\Zn", bold_code, color_code,
445274116Sdteske			    pbar_fill, "\\ZR", pbar_cap);
446274116Sdteske		}
447274116Sdteske	}
448274116Sdteske
449274116Sdteske	for (fp = file_list; fp != NULL; fp = fp->next) {
450274116Sdteske		flabel_size = label_size;
451274116Sdteske		name = fp->name;
452274116Sdteske		nthfile++;
453274116Sdteske
454274116Sdteske		/*
455274116Sdteske		 * Support multiline filenames (where the filename is taken as
456274116Sdteske		 * the last line and the text leading up to the last line can
457274116Sdteske		 * be used as (for example) a heading/separator between files.
458274116Sdteske		 */
459274116Sdteske		if (use_dialog)
460274116Sdteske			nls = dialog_prompt_nlstate(pprompt);
461274116Sdteske		nlines += dialog_prompt_numlines(name, nls);
462274116Sdteske		lastline = dialog_prompt_lastline(name, 1);
463274116Sdteske		if (name != lastline) {
464274116Sdteske			c = *lastline;
465274116Sdteske			*lastline = '\0';
466274116Sdteske			dprompt_add("%s", name);
467274116Sdteske			*lastline = c;
468274116Sdteske			name = lastline;
469274116Sdteske		}
470274116Sdteske
471274116Sdteske		/* Support color codes (for dialog(1,3)) in file names */
472274116Sdteske		if ((use_dialog || use_libdialog) && use_color) {
473274116Sdteske			cp = name;
474274116Sdteske			while (*cp != '\0') {
475274116Sdteske				if (*cp == '\\' && *(cp + 1) != '\0' &&
476274116Sdteske				    *(++cp) == 'Z' && *(cp + 1) != '\0') {
477274116Sdteske					cp++;
478274116Sdteske					flabel_size += 3;
479274116Sdteske				}
480274116Sdteske				cp++;
481274116Sdteske			}
482274116Sdteske			if (flabel_size > FLABEL_MAX)
483274116Sdteske				flabel_size = FLABEL_MAX;
484274116Sdteske		}
485274116Sdteske
486274116Sdteske		/* If no mini-progressbar, increase label width */
487274116Sdteske		if (pbar_size < 0 && flabel_size <= FLABEL_MAX - 2 &&
488274116Sdteske		    no_labels == FALSE)
489274116Sdteske			flabel_size += 2;
490274116Sdteske
491274116Sdteske		/* If name is too long, add an ellipsis */
492274116Sdteske		if (snprintf(flabel, flabel_size + 1, "%s", name) >
493274116Sdteske		    flabel_size) sprintf(flabel + flabel_size - 3, "...");
494274116Sdteske
495274116Sdteske		/*
496274116Sdteske		 * Append the label (processing the current file differently)
497274116Sdteske		 */
498274116Sdteske		if (fp == curfile && pct < 100) {
499274116Sdteske			/*
500274116Sdteske			 * Add an ellipsis to current file name if it will fit.
501274116Sdteske			 * There may be an ellipsis already from truncating the
502274116Sdteske			 * label (in which case, we already have one).
503274116Sdteske			 */
504274116Sdteske			cp = flabel + strlen(flabel);
505274116Sdteske			if (cp < (flabel + flabel_size))
506274116Sdteske				snprintf(cp, flabel_size -
507274116Sdteske				    (cp - flabel) + 1, "...");
508274116Sdteske
509274116Sdteske			/* Append label (with spinner and optional color) */
510274116Sdteske			dprompt_add("%s%-*s%s %c", use_color ? "\\Zb" : "",
511274116Sdteske			    flabel_size, flabel, use_color ? "\\Zn" : "",
512274116Sdteske			    spin_char());
513274116Sdteske		} else
514274116Sdteske			dprompt_add("%-*s%s %s", flabel_size,
515274116Sdteske			    flabel, use_color ? "\\Zn" : "", " ");
516274116Sdteske
517274116Sdteske		/*
518274116Sdteske		 * Append pbar/status (processing the current file differently)
519274116Sdteske		 */
520274116Sdteske		dstate = DPROMPT_NONE;
521274116Sdteske		if (fp->msg != NULL)
522274116Sdteske			dstate = DPROMPT_CUSTOM_MSG;
523274116Sdteske		else if (pbar_size < 0)
524274116Sdteske			dstate = DPROMPT_NONE;
525274116Sdteske		else if (pbar_size < 4)
526274116Sdteske			dstate = DPROMPT_MINIMAL;
527274116Sdteske		else if (after_curfile)
528274116Sdteske			dstate = DPROMPT_PENDING;
529274116Sdteske		else if (fp == curfile) {
530274116Sdteske			if (*pbar == '\0') {
531274116Sdteske				if (fp->length < 0)
532274116Sdteske					dstate = DPROMPT_DETAILS;
533274116Sdteske				else if (fp->status == DPV_STATUS_RUNNING)
534274116Sdteske					dstate = DPROMPT_DETAILS;
535274116Sdteske				else
536274116Sdteske					dstate = DPROMPT_END_STATE;
537274116Sdteske			}
538274116Sdteske			else if (dialog_test) /* status/length ignored */
539274116Sdteske				dstate = pct < 100 ?
540274116Sdteske				    DPROMPT_PBAR : DPROMPT_END_STATE;
541274116Sdteske			else if (fp->status == DPV_STATUS_RUNNING)
542274116Sdteske				dstate = fp->length < 0 ?
543274116Sdteske				    DPROMPT_DETAILS : DPROMPT_PBAR;
544274116Sdteske			else /* not running */
545274116Sdteske				dstate = fp->length < 0 ?
546274116Sdteske				    DPROMPT_DETAILS : DPROMPT_END_STATE;
547274116Sdteske		} else { /* before curfile */
548274116Sdteske			if (dialog_test)
549274116Sdteske				dstate = DPROMPT_END_STATE;
550274116Sdteske			else
551274116Sdteske				dstate = fp->length < 0 ?
552274116Sdteske				    DPROMPT_DETAILS : DPROMPT_END_STATE;
553274116Sdteske		}
554274116Sdteske		format = use_color ?
555274116Sdteske		    " [\\Z%c%s%-*s%s%-*s\\Zn]\\n" :
556274116Sdteske		    " [%-*s%s%-*s]\\n";
557274116Sdteske		if (fp->status == DPV_STATUS_FAILED) {
558274116Sdteske			bg_code = "\\Zr\\Z1"; /* Red */
559274116Sdteske			estext_lsize = fail_lsize;
560274116Sdteske			estext_rsize = fail_rsize;
561274116Sdteske			estext = fail;
562274116Sdteske		} else { /* e.g., DPV_STATUS_DONE */
563274116Sdteske			bg_code = "\\Zr\\Z2"; /* Green */
564274116Sdteske			estext_lsize = done_lsize;
565274116Sdteske			estext_rsize = done_rsize;
566274116Sdteske			estext = done;
567274116Sdteske		}
568274116Sdteske		switch (dstate) {
569274116Sdteske		case DPROMPT_PENDING: /* Future file(s) */
570274116Sdteske			dprompt_add(" [%-*s%s%-*s]\\n",
571274116Sdteske			    pend_lsize, "", pend, pend_rsize, "");
572274116Sdteske			break;
573274116Sdteske		case DPROMPT_PBAR: /* Current file */
574274116Sdteske			dprompt_add(" [%s]\\n", pbar);
575274116Sdteske			break;
576274116Sdteske		case DPROMPT_END_STATE: /* Past/Current file(s) */
577274116Sdteske			if (use_color)
578274116Sdteske				dprompt_add(format, bold_code, bg_code,
579274116Sdteske				    estext_lsize, "", estext,
580274116Sdteske				    estext_rsize, "");
581274116Sdteske			else
582274116Sdteske				dprompt_add(format,
583274116Sdteske				    estext_lsize, "", estext,
584274116Sdteske				    estext_rsize, "");
585274116Sdteske			break;
586274116Sdteske		case DPROMPT_DETAILS: /* Past/Current file(s) */
587274116Sdteske			humanize_number(human, pbar_size + 2, fp->read, "",
588274116Sdteske			    HN_AUTOSCALE, HN_NOSPACE | HN_DIVISOR_1000);
589274116Sdteske
590274116Sdteske			/* Calculate center alignment */
591274116Sdteske			hlen = (int)strlen(human);
592274116Sdteske			lsize = (pbar_size - hlen) / 2;
593274116Sdteske			rsize = lsize;
594274116Sdteske			if ((lsize+hlen+rsize) != pbar_size)
595274116Sdteske				rsize++;
596274116Sdteske
597274116Sdteske			if (use_color)
598274116Sdteske				dprompt_add(format, bold_code, bg_code,
599274116Sdteske				    lsize, "", human, rsize, "");
600274116Sdteske			else
601274116Sdteske				dprompt_add(format,
602274116Sdteske				    lsize, "", human, rsize, "");
603274116Sdteske			break;
604274116Sdteske		case DPROMPT_CUSTOM_MSG: /* File-specific message override */
605274116Sdteske			snprintf(msg, PROMPT_MAX + 1, "%s", fp->msg);
606274116Sdteske			if (pbar_size < (mesg_size = strlen(msg))) {
607274116Sdteske				mesg_lsize = mesg_rsize = 0;
608274116Sdteske				*(msg + pbar_size) = '\0';
609274116Sdteske				mesg_size = pbar_size;
610274116Sdteske			} else {
611274116Sdteske				mesg_lsize = (pbar_size - mesg_size) / 2;
612274116Sdteske				mesg_rsize = mesg_lsize;
613274116Sdteske				if ((mesg_rsize + mesg_size + mesg_lsize)
614274116Sdteske				    != pbar_size)
615274116Sdteske					mesg_rsize++;
616274116Sdteske			}
617274116Sdteske			if (use_color)
618274116Sdteske				dprompt_add(format, bold_code, bg_code,
619274116Sdteske				    mesg_lsize, "", msg, mesg_rsize, "");
620274116Sdteske			else
621274116Sdteske				dprompt_add(format, mesg_lsize, "", msg,
622274116Sdteske				    mesg_rsize, "");
623274116Sdteske			break;
624274116Sdteske		case DPROMPT_MINIMAL: /* Short progress bar, minimal room */
625274116Sdteske			if (use_color)
626274116Sdteske				dprompt_add(format, bold_code, bg_code,
627274116Sdteske				    pbar_size, "", "", 0, "");
628274116Sdteske			else
629274116Sdteske				dprompt_add(format, pbar_size, "", "", 0, "");
630274116Sdteske			break;
631274116Sdteske		case DPROMPT_NONE: /* pbar_size < 0 */
632274116Sdteske			/* FALLTHROUGH */
633274116Sdteske		default:
634274116Sdteske			dprompt_add(" \\n");
635274116Sdteske			/*
636274116Sdteske			 * NB: Leading space required for the case when
637274116Sdteske			 * spin_char() returns a single backslash [\] which
638274116Sdteske			 * without the space, changes the meaning of `\n'
639274116Sdteske			 */
640274116Sdteske		}
641274116Sdteske
642274116Sdteske		/* Stop building if we've hit the internal limit */
643274116Sdteske		if (nthfile >= display_limit)
644274116Sdteske			break;
645274116Sdteske
646274116Sdteske		/* If this is the current file, all others are pending */
647274116Sdteske		if (fp == curfile)
648274116Sdteske			after_curfile = TRUE;
649274116Sdteske	}
650274116Sdteske
651274116Sdteske	/*
652274116Sdteske	 * Since we cannot change the height/width of the [X]dialog(1) widget
653274116Sdteske	 * after spawn, to make things look nice let's pad the height so that
654274116Sdteske	 * the `-a text' always appears in the same spot.
655274116Sdteske	 *
656274116Sdteske	 * NOTE: fheight is calculated in dprompt_init(). It represents the
657274116Sdteske	 * maximum height required to display the set of items (broken up into
658274116Sdteske	 * pieces of display_limit chunks) whose names contain the most
659274116Sdteske	 * newlines for any given set.
660274116Sdteske	 */
661274116Sdteske	while (nlines < fheight) {
662274116Sdteske		dprompt_add("\n");
663274116Sdteske		nlines++;
664274116Sdteske	}
665274116Sdteske
666274116Sdteske	return (nthfile);
667274116Sdteske}
668274116Sdteske
669274116Sdteske/*
670274116Sdteske * Process the dpv_file_node linked-list of named files, re-generating the
671274116Sdteske * [X]dialog(1) `--gauge' prompt text for the current state of transfers.
672274116Sdteske */
673274116Sdteskevoid
674274116Sdteskedprompt_recreate(struct dpv_file_node *file_list,
675274116Sdteske    struct dpv_file_node *curfile, int pct)
676274116Sdteske{
677274116Sdteske	size_t len;
678274116Sdteske
679274116Sdteske	/*
680274116Sdteske	 * Re-Build the prompt text
681274116Sdteske	 */
682274116Sdteske	dprompt_clear();
683274116Sdteske	if (display_limit > 0)
684274116Sdteske		dprompt_add_files(file_list, curfile, pct);
685274116Sdteske
686274116Sdteske	/* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
687274116Sdteske	if (use_xdialog) {
688274116Sdteske		/* Replace `\n' with `\n\\n\n' in dprompt */
689274116Sdteske		len = strlen(dprompt);
690274116Sdteske		len += strcount(dprompt, "\\n") * 5; /* +5 chars per count */
691274116Sdteske		if (len > PROMPT_MAX)
692274116Sdteske			errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow "
693274116Sdteske			    "(%zu > %i)", __func__, len, PROMPT_MAX);
694274116Sdteske		if (replaceall(dprompt, "\\n", "\n\\n\n") < 0)
695274116Sdteske			err(EXIT_FAILURE, "%s: replaceall()", __func__);
696274116Sdteske	}
697274116Sdteske	else if (use_libdialog)
698274116Sdteske		strexpandnl(dprompt);
699274116Sdteske}
700274116Sdteske
701274116Sdteske/*
702274116Sdteske * Print the [X]dialog(1) `--gauge' prompt text to a buffer.
703274116Sdteske */
704274116Sdteskeint
705274116Sdteskedprompt_sprint(char * restrict str, const char *prefix, const char *append)
706274116Sdteske{
707274116Sdteske
708274116Sdteske	return (snprintf(str, PROMPT_MAX, "%s%s%s%s", use_color ? "\\Zn" : "",
709274116Sdteske	    prefix ? prefix : "", dprompt, append ? append : ""));
710274116Sdteske}
711274116Sdteske
712274116Sdteske/*
713274116Sdteske * Print the [X]dialog(1) `--gauge' prompt text to file descriptor fd (could
714274116Sdteske * be STDOUT_FILENO or a pipe(2) file descriptor to actual [X]dialog(1)).
715274116Sdteske */
716274116Sdteskevoid
717274116Sdteskedprompt_dprint(int fd, const char *prefix, const char *append, int overall)
718274116Sdteske{
719274116Sdteske	int percent = gauge_percent;
720274116Sdteske
721274116Sdteske	if (overall >= 0 && overall <= 100)
722274116Sdteske		gauge_percent = percent = overall;
723274116Sdteske	dprintf(fd, "XXX\n%s%s%s%s\nXXX\n%i\n", use_color ? "\\Zn" : "",
724274116Sdteske	    prefix ? prefix : "", dprompt, append ? append : "", percent);
725274116Sdteske	fsync(fd);
726274116Sdteske}
727274116Sdteske
728274116Sdteske/*
729274116Sdteske * Print the dialog(3) `gauge' prompt text using libdialog.
730274116Sdteske */
731274116Sdteskevoid
732274116Sdteskedprompt_libprint(const char *prefix, const char *append, int overall)
733274116Sdteske{
734274116Sdteske	int percent = gauge_percent;
735274116Sdteske	char buf[DPV_PPROMPT_MAX + DPV_APROMPT_MAX + DPV_DISPLAY_LIMIT * 1024];
736274116Sdteske
737274116Sdteske	dprompt_sprint(buf, prefix, append);
738274116Sdteske
739274116Sdteske	if (overall >= 0 && overall <= 100)
740274116Sdteske		gauge_percent = percent = overall;
741274116Sdteske	gauge = dlg_reallocate_gauge(gauge, title == NULL ? "" : title,
742274116Sdteske	    buf, dheight, dwidth, percent);
743274116Sdteske	dlg_update_gauge(gauge, percent);
744274116Sdteske}
745274116Sdteske
746274116Sdteske/*
747274116Sdteske * Free allocated items initialized by dprompt_init()
748274116Sdteske */
749274116Sdteskevoid
750274116Sdteskedprompt_free(void)
751274116Sdteske{
752274116Sdteske	if ((dprompt_free_mask & FM_DONE) != 0) {
753274116Sdteske		dprompt_free_mask ^= FM_DONE;
754274116Sdteske		free(done);
755274116Sdteske		done = NULL;
756274116Sdteske	}
757274116Sdteske	if ((dprompt_free_mask & FM_FAIL) != 0) {
758274116Sdteske		dprompt_free_mask ^= FM_FAIL;
759274116Sdteske		free(fail);
760274116Sdteske		fail = NULL;
761274116Sdteske	}
762274116Sdteske	if ((dprompt_free_mask & FM_PEND) != 0) {
763274116Sdteske		dprompt_free_mask ^= FM_PEND;
764274116Sdteske		free(pend);
765274116Sdteske		pend = NULL;
766274116Sdteske	}
767274116Sdteske}
768