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
savetheme(const char * file)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
loadtheme(const char * file,bool compatibility)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
setdeftheme(enum bsddialog_default_theme theme)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
startuptheme(void)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
bikeshed(struct bsddialog_conf * conf)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