1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2022-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 <sys/time.h> 29 30 #include <limits.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 #include <time.h> 36 37 #include <bsddialog.h> 38 #include <bsddialog_theme.h> 39 40 #include "util.h" 41 42 static struct bsddialog_theme t; 43 static char title[1024]; 44 45 #define NPROPERTY 41 46 #define NCOLOR 8 47 #define NATTR 6 48 49 #define PROP_ERROR(name, error) do { \ 50 fclose(fp); \ 51 exit_error(false, "%s for \"%s\"", error, name); \ 52 } while (0) 53 54 enum typeproperty { 55 BOOL, 56 CHAR, 57 INT, 58 UINT, 59 COLOR, 60 COMPAT 61 }; 62 63 struct property { 64 const char *comment; 65 const char *name; 66 enum typeproperty type; 67 void *value; 68 }; 69 70 struct namevalue { 71 const char *name; 72 unsigned int value; 73 }; 74 75 static struct namevalue color[NCOLOR] = { 76 {"black", BSDDIALOG_BLACK}, 77 {"red", BSDDIALOG_RED}, 78 {"green", BSDDIALOG_GREEN}, 79 {"yellow", BSDDIALOG_YELLOW}, 80 {"blue", BSDDIALOG_BLUE}, 81 {"magenta", BSDDIALOG_MAGENTA}, 82 {"cyan", BSDDIALOG_CYAN}, 83 {"white", BSDDIALOG_WHITE} 84 }; 85 86 static struct namevalue attr[NATTR] = { 87 {"bold", BSDDIALOG_BOLD}, 88 {"reverse", BSDDIALOG_REVERSE}, 89 {"underline", BSDDIALOG_UNDERLINE}, 90 {"blink", BSDDIALOG_BLINK}, 91 {"halfbright", BSDDIALOG_HALFBRIGHT}, 92 {"highlight", BSDDIALOG_HIGHLIGHT} 93 }; 94 95 static struct property p[NPROPERTY] = { 96 {"\n#Terminal\n", "theme.screen.color", COLOR, &t.screen.color}, 97 98 {"\n# Shadow\n", 99 "theme.shadow.color", COLOR, &t.shadow.color}, 100 {"# shift down right from main widget\n", 101 "theme.shadow.y", UINT, &t.shadow.y}, 102 {"", "theme.shadow.x", UINT, &t.shadow.x}, 103 104 {"\n# Main widget\n", 105 "theme.dialog.color", COLOR, &t.dialog.color}, 106 {"", "theme.dialog.delimtitle", BOOL, &t.dialog.delimtitle}, 107 {"", "theme.dialog.titlecolor", COLOR, &t.dialog.titlecolor}, 108 {"", "theme.dialog.lineraisecolor", COLOR, &t.dialog.lineraisecolor}, 109 {"", "theme.dialog.linelowercolor", COLOR, &t.dialog.linelowercolor}, 110 {"", "theme.dialog.bottomtitlecolor", COLOR, 111 &t.dialog.bottomtitlecolor}, 112 {"", "theme.dialog.arrowcolor", COLOR, &t.dialog.arrowcolor}, 113 114 {"\n# Menus: --checklist, --menu, --radiolist\n" 115 "# prefix [selector] shortcut name desc bottomdesc\n", 116 "theme.menu.f_prefixcolor", COLOR, &t.menu.f_prefixcolor}, 117 {"", "theme.menu.prefixcolor", COLOR, &t.menu.prefixcolor}, 118 {"", "theme.menu.f_selectorcolor", COLOR, &t.menu.f_selectorcolor}, 119 {"", "theme.menu.selectorcolor", COLOR, &t.menu.selectorcolor}, 120 {"", "theme.menu.f_namecolor", COLOR, &t.menu.f_namecolor}, 121 {"", "theme.menu.namecolor", COLOR, &t.menu.namecolor}, 122 {"", "theme.menu.f_desccolor", COLOR, &t.menu.f_desccolor}, 123 {"", "theme.menu.desccolor", COLOR, &t.menu.desccolor}, 124 {"", "theme.menu.f_shortcutcolor", COLOR, &t.menu.f_shortcutcolor}, 125 {"", "theme.menu.shortcutcolor", COLOR, &t.menu.shortcutcolor}, 126 {"", "theme.menu.bottomdesccolor", COLOR, &t.menu.bottomdesccolor}, 127 {"# bsddialog_menutype BSDDIALOG_SEPARATOR\n", 128 "theme.menu.sepnamecolor", COLOR, &t.menu.sepnamecolor}, 129 {"", "theme.menu.sepdesccolor", COLOR, &t.menu.sepdesccolor}, 130 131 {"\n# Forms\n", 132 "theme.form.f_fieldcolor", COLOR, &t.form.f_fieldcolor}, 133 {"", "theme.form.fieldcolor", COLOR, &t.form.fieldcolor}, 134 {"", "theme.form.readonlycolor", COLOR, &t.form.readonlycolor}, 135 {"", "theme.form.bottomdesccolor", COLOR, &t.form.bottomdesccolor}, 136 137 {"\n# Bar of --gauge, --mixedgauge, --pause, --rangebox\n", 138 "theme.bar.f_color", COLOR, &t.bar.f_color}, 139 {"", "theme.bar.color", COLOR, &t.bar.color}, 140 141 {"\n# Buttons\n", 142 "theme.button.minmargin", UINT, &t.button.minmargin}, 143 {"", "theme.button.maxmargin", UINT, &t.button.maxmargin}, 144 {"", "theme.button.leftdelim", CHAR, &t.button.leftdelim}, 145 {"", "theme.button.rightdelim", CHAR, &t.button.rightdelim}, 146 {"", "theme.button.f_delimcolor", COLOR, &t.button.f_delimcolor}, 147 {"", "theme.button.delimcolor", COLOR, &t.button.delimcolor}, 148 {"", "theme.button.f_color", COLOR, &t.button.f_color}, 149 {"", "theme.button.color", COLOR, &t.button.color}, 150 {"", "theme.button.f_shortcutcolor", COLOR, &t.button.f_shortcutcolor}, 151 {"", "theme.button.shortcutcolor", COLOR, &t.button.shortcutcolor}, 152 153 {"\n#Compatibility. Do not use, can be deleted\n", 154 "use_shadow", COMPAT, NULL} 155 }; 156 157 void savetheme(const char *file) 158 { 159 int i, j; 160 unsigned int flags; 161 enum bsddialog_color bg, fg; 162 time_t clock; 163 FILE *fp; 164 165 if (bsddialog_get_theme(&t) != BSDDIALOG_OK) 166 exit_error(false, 167 "cannot save theme: %s", bsddialog_geterror()); 168 169 if (time(&clock) < 0) 170 exit_error(false, "cannot save profile getting current time"); 171 if ((fp = fopen(file, "w")) == NULL) 172 exit_error(false, "cannot open %s to save profile", file); 173 174 fprintf(fp, "### bsddialog theme - %s\n", ctime(&clock)); 175 176 fputs("# Colors: ", fp); 177 fputs("black red green yellow blue magenta cyan white.\n", fp); 178 fputs("# Attributes: ", fp); 179 fputs("bold reverse underline blink halfbright highlight.\n", fp); 180 fputs("# f_* refers to focus for an element with selected or ", fp); 181 fputs("unselected state.\n\n", fp); 182 183 fprintf(fp, "version %s\n", LIBBSDDIALOG_VERSION); 184 185 for (i = 0; i < NPROPERTY; i++) { 186 if (p[i].type == COMPAT) 187 continue; 188 fprintf(fp, "%s%s", p[i].comment, p[i].name); 189 switch (p[i].type) { 190 case CHAR: 191 fprintf(fp, " %c\n", *((char*)p[i].value)); 192 break; 193 case INT: 194 fprintf(fp, " %d\n", *((int*)p[i].value)); 195 break; 196 case UINT: 197 fprintf(fp, " %u\n", *((unsigned int*)p[i].value)); 198 break; 199 case BOOL: 200 fprintf(fp, " %s\n", 201 *((bool*)p[i].value) ? "true" : "false"); 202 break; 203 case COLOR: 204 bsddialog_color_attrs(*(int*)p[i].value, &fg, &bg, 205 &flags); 206 fprintf(fp, " %s %s", color[fg].name, color[bg].name); 207 for (j = 0; j < NATTR; j++) 208 if (flags & attr[j].value) 209 fprintf(fp, " %s", attr[j].name); 210 fputs("\n", fp); 211 break; 212 case COMPAT: 213 /* Do not save compat property for now */ 214 break; 215 } 216 } 217 218 fclose(fp); 219 } 220 221 void loadtheme(const char *file, bool compatibility) 222 { 223 bool boolvalue; 224 char charvalue, *value; 225 char line[BUFSIZ], name[BUFSIZ], c1[BUFSIZ], c2[BUFSIZ]; 226 int i, j, intvalue; 227 unsigned int uintvalue, flags; 228 enum bsddialog_color bg, fg; 229 FILE *fp; 230 231 if (bsddialog_hascolors() == false) 232 return; 233 234 if (bsddialog_get_theme(&t) != BSDDIALOG_OK) 235 exit_error(false, "Cannot get current theme: %s", 236 bsddialog_geterror()); 237 238 if ((fp = fopen(file, "r")) == NULL) 239 exit_error(false, "Cannot open theme \"%s\" file", file); 240 241 while (fgets(line, BUFSIZ, fp) != NULL) { 242 if (line[0] == '#' || line[0] == '\n') 243 continue; /* superfluous, only for efficiency */ 244 sscanf(line, "%s", name); 245 value = NULL; /* useless init, fix compiler warning */ 246 for (i = 0; i < NPROPERTY; i++) { 247 if (strcmp(name, p[i].name) == 0) { 248 value = &line[strlen(name)]; 249 break; 250 } 251 } 252 if (i >= NPROPERTY) { 253 /* unknown name in property p[] */ 254 if (strcmp(name, "version") == 0) 255 continue; /* nothing for now */ 256 else if (compatibility) 257 continue; /* just ignore */ 258 else 259 PROP_ERROR(name, "Unknown theme property name"); 260 } 261 switch (p[i].type) { 262 case CHAR: 263 while (value[0] == ' ' || value[0] == '\n' || 264 value[0] == '\0') 265 value++; 266 if (sscanf(value, "%c", &charvalue) != 1) 267 PROP_ERROR(p[i].name, "Cannot get a char"); 268 *((int*)p[i].value) = charvalue; 269 break; 270 case INT: 271 if (sscanf(value, "%d", &intvalue) != 1) 272 PROP_ERROR(p[i].name, "Cannot get a int"); 273 *((int*)p[i].value) = intvalue; 274 break; 275 case UINT: 276 if (sscanf(value, "%u", &uintvalue) != 1) 277 PROP_ERROR(p[i].name, "Cannot get a uint"); 278 *((unsigned int*)p[i].value) = uintvalue; 279 break; 280 case BOOL: 281 boolvalue = (strstr(value, "true") != NULL) ? 282 true :false; 283 *((bool*)p[i].value) = boolvalue; 284 break; 285 case COLOR: 286 if (sscanf(value, "%s %s", c1, c2) != 2) 287 PROP_ERROR(p[i].name, "Cannot get 2 colors"); 288 /* Foreground */ 289 for (j = 0; j < NCOLOR ; j++) 290 if ((strstr(c1, color[j].name)) != NULL) 291 break; 292 if (j >= NCOLOR) 293 PROP_ERROR(p[i].name, "Bad foreground"); 294 fg = color[j].value; 295 /* Background */ 296 for (j = 0; j < NCOLOR ; j++) 297 if ((strstr(c2, color[j].name)) != NULL) 298 break; 299 if (j >= NCOLOR) 300 PROP_ERROR(p[i].name, "Bad background"); 301 bg = color[j].value; 302 /* Flags */ 303 flags = 0; 304 for (j = 0; j < NATTR; j++) 305 if (strstr(value, attr[j].name) != NULL) 306 flags |= attr[j].value; 307 *((int*)p[i].value) = bsddialog_color(fg, bg, flags); 308 break; 309 case COMPAT: 310 /* 311 * usr.sbin/bsdconfig/share/dialog.subr:2255 312 * uses this parameter to set NO_SHADOW. 313 * Set t.shadow.[y|x] for compatibilty. 314 */ 315 if (strcmp(name, "use_shadow") == 0) { 316 if (strcasestr(value, "off") != NULL) 317 t.shadow.y = t.shadow.x = 0; 318 } 319 break; 320 } 321 } 322 323 fclose(fp); 324 325 if (bsddialog_set_theme(&t) != BSDDIALOG_OK) 326 exit_error(false, bsddialog_geterror()); 327 } 328 329 void setdeftheme(enum bsddialog_default_theme theme) 330 { 331 if (bsddialog_hascolors() == false) 332 return; 333 if (bsddialog_set_default_theme(theme) != BSDDIALOG_OK) 334 exit_error(false, bsddialog_geterror()); 335 } 336 337 void startuptheme(void) 338 { 339 bool sep; 340 char *env, *file, *home, path[PATH_MAX]; 341 342 env = getenv("NO_COLOR"); 343 if (env != NULL && env[0] != '\0') 344 setdeftheme(BSDDIALOG_THEME_BLACKWHITE); 345 346 if ((home = getenv("HOME")) != NULL) { 347 sep = (strcmp(home, "/") == 0) ? false : true; 348 349 snprintf(path, PATH_MAX, "%s%s.bsddialog.conf", 350 home, sep ? "/" : ""); 351 if (access(path, F_OK) == 0) 352 loadtheme(path, false); 353 354 if ((file = getenv("BSDDIALOG_COMPATRC")) != NULL) { 355 snprintf(path, PATH_MAX, "%s%s%s", 356 home, sep ? "/" : "", file); 357 if (access(path, F_OK) == 0) 358 loadtheme(path, true); 359 } 360 } 361 if ((file = getenv("BSDDIALOG_THEMEFILE")) != NULL) { 362 if (access(file, F_OK) == 0) 363 loadtheme(file, false); 364 } 365 } 366 367 void bikeshed(struct bsddialog_conf *conf) 368 { 369 int margin, i; 370 int colors[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 371 char delim[8] = {'[', '<', '(', '|', ']', '>', ')', '|'}; 372 enum bsddialog_color col[6]; 373 struct timeval tv; 374 375 /* theme */ 376 if (bsddialog_get_theme(&t) != BSDDIALOG_OK) 377 exit_error(false, bsddialog_geterror()); 378 379 gettimeofday(&tv, NULL); 380 srand(tv.tv_usec); 381 for (i = 0; i < 6; i++) { 382 do { 383 col[i] = rand() % 8; 384 } while (colors[col[i]] == 1); 385 colors[col[i]] = 1; 386 } 387 388 t.screen.color = bsddialog_color(col[4], col[3], 0); 389 390 t.shadow.color = bsddialog_color(col[0], col[0], 0); 391 t.shadow.y = 1, 392 t.shadow.x = 2, 393 394 t.dialog.delimtitle = (~rand() & 1) ? true : false; 395 t.dialog.titlecolor = bsddialog_color(col[3], col[5], 0); 396 t.dialog.lineraisecolor = bsddialog_color(col[0], col[5], 0); 397 t.dialog.linelowercolor = bsddialog_color(col[0], col[5], 0); 398 t.dialog.color = bsddialog_color(col[0], col[5], 0); 399 t.dialog.bottomtitlecolor = bsddialog_color(col[0], col[5], 0); 400 t.dialog.arrowcolor = bsddialog_color(col[3], col[5], 0); 401 402 t.menu.f_prefixcolor = bsddialog_color(col[5], col[3], 0); 403 t.menu.prefixcolor = bsddialog_color(col[0], col[5], 0); 404 t.menu.f_selectorcolor = bsddialog_color(col[5], col[3], 0); 405 t.menu.selectorcolor = bsddialog_color(col[0], col[5], 0); 406 t.menu.f_desccolor = bsddialog_color(col[5], col[3], 0); 407 t.menu.desccolor = bsddialog_color(col[0], col[5], 0); 408 t.menu.f_namecolor = bsddialog_color(col[5], col[3], 0); 409 t.menu.namecolor = bsddialog_color(col[3], col[5], 0); 410 t.menu.f_shortcutcolor = bsddialog_color(col[1], col[3], 0); 411 t.menu.shortcutcolor = bsddialog_color(col[1], col[5], 0); 412 t.menu.bottomdesccolor = bsddialog_color(col[4], col[3], 0); 413 t.menu.sepnamecolor = bsddialog_color(col[1], col[5], 0); 414 t.menu.sepdesccolor = bsddialog_color(col[1], col[5], 0); 415 416 t.form.f_fieldcolor = bsddialog_color(col[5], col[3], 0); 417 t.form.fieldcolor = bsddialog_color(col[5], col[4], 0); 418 t.form.readonlycolor = bsddialog_color(col[4], col[5], 0); 419 t.form.bottomdesccolor = bsddialog_color(col[4], col[3], 0); 420 421 t.bar.f_color = bsddialog_color(col[5], col[3], 0); 422 t.bar.color = bsddialog_color(col[3], col[5], 0); 423 424 t.button.minmargin = 1, 425 t.button.maxmargin = 5, 426 i = rand() % 4; 427 t.button.leftdelim = delim[i]; 428 t.button.rightdelim = delim[i + 4]; 429 t.button.f_delimcolor = bsddialog_color(col[5], col[3], 0); 430 t.button.delimcolor = bsddialog_color(col[0], col[5], 0); 431 t.button.f_color = bsddialog_color(col[2], col[3], 0); 432 t.button.color = bsddialog_color(col[0], col[5], 0); 433 t.button.f_shortcutcolor = bsddialog_color(col[5], col[3], 0); 434 t.button.shortcutcolor = bsddialog_color(col[1], col[5], 0); 435 436 if (bsddialog_set_theme(&t)) 437 exit_error(false, bsddialog_geterror()); 438 439 /* conf */ 440 conf->button.always_active = (~rand() & 1) ? true : false; 441 if ((i = rand() % 3) != 0) /* default "d/m/y" */ 442 conf->date.format = (i & 1) ? "m/d/y" : "y/m/d" ; 443 if (conf->title != NULL) { 444 memset(title, 0, 1024); 445 margin = rand() % 5; 446 memset(title, ' ', margin); 447 strcpy(title + margin, conf->title); 448 memset(title + strlen(title), ' ', margin); 449 conf->title = title; 450 } 451 } 452