xref: /freebsd/contrib/bsddialog/utility/util_theme.c (revision a6d8be451f62d425b71a4874f7d4e133b9fb393c)
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