1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2021-2024 Alfonso Sabato Siciliano
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <getopt.h>
29#include <limits.h>
30#include <locale.h>
31#include <signal.h>
32#include <stdarg.h>
33#include <stdlib.h>
34#include <stdio.h>
35#include <string.h>
36#include <term.h>
37
38#include <bsddialog.h>
39#include <bsddialog_theme.h>
40
41#include "util.h"
42
43#define EXITCODE(retval) (exitcodes[retval + 1].value)
44#define UNUSED_PAR(x) UNUSED_ ## x __attribute__((__unused__))
45
46static void custom_text(struct options *opt, char *text, char *buf);
47
48/* Exit codes */
49struct exitcode {
50	const char *name;
51	int value;
52};
53
54static struct exitcode exitcodes[14] = {
55	{ "BSDDIALOG_ERROR",    255 },
56	{ "BSDDIALOG_OK",         0 },
57	{ "BSDDIALOG_CANCEL",     1 },
58	{ "BSDDIALOG_HELP",       2 },
59	{ "BSDDIALOG_EXTRA",      3 },
60	{ "BSDDIALOG_TIMEOUT",    4 },
61	{ "BSDDIALOG_ESC",        5 },
62	{ "BSDDIALOG_LEFT1",      6 },
63	{ "BSDDIALOG_LEFT2",      7 },
64	{ "BSDDIALOG_LEFT3",      8 },
65	{ "BSDDIALOG_RIGHT1",     9 },
66	{ "BSDDIALOG_RIGHT2",    10 },
67	{ "BSDDIALOG_RIGHT3",    11 },
68	{ "BSDDIALOG_ITEM_HELP",  2 } /* like HELP by default */
69};
70
71void set_exit_code(int lib_retval, int exitcode)
72{
73	exitcodes[lib_retval + 1].value = exitcode;
74}
75
76/* Error */
77void exit_error(bool usage, const char *fmt, ...)
78{
79	va_list arg_ptr;
80
81	if (bsddialog_inmode())
82		bsddialog_end();
83	printf("Error: ");
84	va_start(arg_ptr, fmt);
85	vprintf(fmt, arg_ptr);
86	va_end(arg_ptr);
87	printf(".\n\n");
88	if (usage) {
89		printf("See \'bsddialog --help\' or \'man 1 bsddialog\' ");
90		printf("for more information.\n");
91	}
92
93	exit (EXITCODE(BSDDIALOG_ERROR));
94}
95
96void error_args(const char *dialog, int argc, char **argv)
97{
98	int i;
99
100	if (bsddialog_inmode())
101		bsddialog_end();
102	printf("Error: %s unexpected argument%s:", dialog, argc > 1 ? "s" : "");
103	for (i = 0; i < argc; i++)
104		printf(" \"%s\"", argv[i]);
105	printf(".\n\n");
106	printf("See \'bsddialog --help\' or \'man 1 bsddialog\' ");
107	printf("for more information.\n");
108
109	exit (EXITCODE(BSDDIALOG_ERROR));
110}
111
112/* init */
113static void sigint_handler(int UNUSED_PAR(sig))
114{
115	bsddialog_end();
116
117	exit(EXITCODE(BSDDIALOG_ERROR));
118}
119
120static void start_bsddialog_mode(void)
121{
122	if (bsddialog_inmode())
123		return;
124	if (bsddialog_init() != BSDDIALOG_OK)
125		exit_error(false, bsddialog_geterror());
126
127	signal(SIGINT, sigint_handler);
128}
129
130static void getenv_exitcodes(void)
131{
132	int i;
133	int value;
134	char *envvalue;
135
136	for (i = 0; i < 10; i++) {
137		envvalue = getenv(exitcodes[i].name);
138		if (envvalue == NULL || envvalue[0] == '\0')
139			continue;
140		value = (int)strtol(envvalue, NULL, 10);
141		exitcodes[i].value = value;
142		/* ITEM_HELP follows HELP without explicit setting */
143		if (i == BSDDIALOG_HELP + 1)
144			exitcodes[BSDDIALOG_ITEM_HELP + 1].value = value;
145	}
146}
147
148/*
149 * bsddialog utility: TUI widgets and dialogs.
150 */
151int main(int argc, char *argv[argc])
152{
153	bool startup;
154	int i, rows, cols, retval, parsed, nargc, firstoptind;
155	char *text, **nargv, *pn;
156	struct bsddialog_conf conf;
157	struct options opt;
158
159	setlocale(LC_ALL, "");
160	getenv_exitcodes();
161	firstoptind = optind;
162	pn = argv[0];
163	retval = BSDDIALOG_OK;
164
165	for (i = 0; i < argc; i++) {
166		if (strcmp(argv[i], "--version") == 0) {
167			printf("Version: %s\n", LIBBSDDIALOG_VERSION);
168			return (BSDDIALOG_OK);
169		}
170		if (strcmp(argv[i], "--help") == 0) {
171			usage();
172			return (BSDDIALOG_OK);
173		}
174	}
175
176	startup = true;
177	while (true) {
178		parsed = parseargs(argc, argv, &conf, &opt);
179		nargc = argc - parsed;
180		nargv = argv + parsed;
181		argc = parsed - optind;
182		argv += optind;
183
184		if (opt.mandatory_dialog && opt.dialogbuilder == NULL)
185			exit_error(true, "expected a --<dialog>");
186
187		if (opt.dialogbuilder == NULL && argc > 0)
188			error_args("(no --<dialog>)", argc, argv);
189
190		/* --print-maxsize or --print-version */
191		if (opt.mandatory_dialog == false && opt.clearscreen == false &&
192		    opt.savethemefile == NULL && opt.dialogbuilder == NULL) {
193			retval = BSDDIALOG_OK;
194			break;
195		}
196
197		/* --<dialog>, --save-theme or clear-screen */
198		text = NULL; /* useless inits, fix compiler warnings */
199		rows = BSDDIALOG_AUTOSIZE;
200		cols = BSDDIALOG_AUTOSIZE;
201		if (opt.dialogbuilder != NULL) {
202			if (argc < 3)
203				exit_error(true,
204				    "expected <text> <rows> <cols>");
205			if ((text = strdup(argv[0])) == NULL)
206				exit_error(false, "cannot allocate <text>");
207			if (opt.dialogbuilder != textbox_builder)
208				custom_text(&opt, argv[0], text);
209			rows = (int)strtol(argv[1], NULL, 10);
210			cols = (int)strtol(argv[2], NULL, 10);
211			argc -= 3;
212			argv += 3;
213		}
214
215		/* bsddialog terminal mode (first iteration) */
216		start_bsddialog_mode();
217
218		if (opt.screen_mode != NULL) {
219			opt.screen_mode = tigetstr(opt.screen_mode);
220			if (opt.screen_mode != NULL &&
221			    opt.screen_mode != (char*)-1) {
222				tputs(opt.screen_mode, 1, putchar);
223				fflush(stdout);
224				bsddialog_refresh();
225			}
226		}
227
228		/* theme */
229		if (startup)
230			startuptheme();
231		startup = false;
232		if ((int)opt.theme >= 0)
233			setdeftheme(opt.theme);
234		if (opt.loadthemefile != NULL)
235			loadtheme(opt.loadthemefile, false);
236		if (opt.bikeshed)
237			bikeshed(&conf);
238		if (opt.savethemefile != NULL)
239			savetheme(opt.savethemefile);
240
241		/* backtitle and dialog */
242		if (opt.dialogbuilder == NULL)
243			break;
244		if (opt.backtitle != NULL)
245			if (bsddialog_backtitle(&conf, opt.backtitle))
246				exit_error(false, bsddialog_geterror());
247		retval = opt.dialogbuilder(&conf, text, rows, cols, argc, argv,
248		    &opt);
249		free(text);
250		if (retval == BSDDIALOG_ERROR)
251			exit_error(false, bsddialog_geterror());
252		if (conf.get_height != NULL && conf.get_width != NULL)
253			dprintf(opt.output_fd, "DialogSize: %d, %d\n",
254			    *conf.get_height, *conf.get_width);
255		if (opt.clearscreen)
256			bsddialog_clear(0);
257		opt.clearscreen = false;
258		/* --and-dialog ends loop with Cancel or ESC */
259		if (retval == BSDDIALOG_CANCEL || retval == BSDDIALOG_ESC)
260			break;
261		argc = nargc;
262		argv = nargv;
263		if (argc <= 0)
264			break;
265		/* prepare next parseargs() call */
266		argc++;
267		argv--;
268		argv[0] = pn;
269		optind = firstoptind;
270	}
271
272	if (bsddialog_inmode()) {
273		/* --clear-screen can be a single option */
274		if (opt.clearscreen)
275			bsddialog_clear(0);
276		bsddialog_end();
277	}
278	/* end bsddialog terminal mode */
279
280	return (EXITCODE(retval));
281}
282
283void custom_text(struct options *opt, char *text, char *buf)
284{
285	bool trim, crwrap;
286	int i, j;
287
288	if (strstr(text, "\\n") == NULL) {
289		/* "hasnl" mode */
290		trim = true;
291		crwrap = true;
292	} else {
293		trim = false;
294		crwrap = opt->cr_wrap;
295	}
296	if (opt->text_unchanged) {
297		trim = false;
298		crwrap = true;
299	}
300
301	i = j = 0;
302	while (text[i] != '\0') {
303		switch (text[i]) {
304		case '\\':
305			buf[j] = '\\';
306			switch (text[i+1]) {
307			case 'n': /* implicitly in "hasnl" mode */
308				buf[j] = '\n';
309				i++;
310				if (text[i+1] == '\n')
311					i++;
312				break;
313			case 't':
314				if (opt->tab_escape) {
315					buf[j] = '\t';
316				} else {
317					j++;
318					buf[j] = 't';
319				}
320				i++;
321				break;
322			}
323			break;
324		case '\n':
325			buf[j] = crwrap ? '\n' : ' ';
326			break;
327		case '\t':
328			buf[j] = opt->text_unchanged ? '\t' : ' ';
329			break;
330		default:
331			buf[j] = text[i];
332		}
333		i++;
334		if (!trim || buf[j] != ' ' || j == 0 || buf[j-1] != ' ')
335			j++;
336	}
337	buf[j] = '\0';
338}
339