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
46 static void custom_text(struct options *opt, char *text, char *buf);
47
48 /* Exit codes */
49 struct exitcode {
50 const char *name;
51 int value;
52 };
53
54 static 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
set_exit_code(int lib_retval,int exitcode)71 void set_exit_code(int lib_retval, int exitcode)
72 {
73 exitcodes[lib_retval + 1].value = exitcode;
74 }
75
76 /* Error */
exit_error(bool usage,const char * fmt,...)77 void 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
error_args(const char * dialog,int argc,char ** argv)96 void 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 */
sigint_handler(int UNUSED_PAR (sig))113 static void sigint_handler(int UNUSED_PAR(sig))
114 {
115 bsddialog_end();
116
117 exit(EXITCODE(BSDDIALOG_ERROR));
118 }
119
start_bsddialog_mode(void)120 static 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
getenv_exitcodes(void)130 static 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 */
main(int argc,char * argv[argc])151 int 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
custom_text(struct options * opt,char * text,char * buf)283 void 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