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 71 void set_exit_code(int lib_retval, int exitcode) 72 { 73 exitcodes[lib_retval + 1].value = exitcode; 74 } 75 76 /* Error */ 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 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 */ 113 static void sigint_handler(int UNUSED_PAR(sig)) 114 { 115 bsddialog_end(); 116 117 exit(EXITCODE(BSDDIALOG_ERROR)); 118 } 119 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 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 */ 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 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