1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022 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 <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 
33 #include <bsddialog.h>
34 #include <bsddialog_theme.h>
35 
36 #include "util_theme.h"
37 
38 static struct bsddialog_theme t;
39 static char title[1024];
40 
41 enum typeprop {
42 	BOOL,
43 	CHAR,
44 	INT,
45 	UINT,
46 	COLOR
47 };
48 
49 struct property {
50 	const char* name;
51 	enum typeprop type;
52 	void *value;
53 };
54 
55 #define NPROPERTY 38
56 static struct property p[NPROPERTY] = {
57 	{ "theme.screen.color", COLOR, &t.screen.color },
58 
59 	{ "theme.shadow.color", COLOR, &t.shadow.color },
60 	{ "theme.shadow.y", UINT, &t.shadow.y },
61 	{ "theme.shadow.x", UINT, &t.shadow.x },
62 
63 	{ "theme.dialog.color", COLOR, &t.dialog.color },
64 	{ "theme.dialog.delimtitle", BOOL, &t.dialog.delimtitle },
65 	{ "theme.dialog.titlecolor", COLOR, &t.dialog.titlecolor },
66 	{ "theme.dialog.lineraisecolor", COLOR, &t.dialog.lineraisecolor },
67 	{ "theme.dialog.linelowercolor", COLOR, &t.dialog.linelowercolor },
68 	{ "theme.dialog.bottomtitlecolor", COLOR, &t.dialog.bottomtitlecolor },
69 	{ "theme.dialog.arrowcolor", COLOR, &t.dialog.arrowcolor },
70 
71 	{ "theme.menu.f_selectorcolor", COLOR, &t.menu.f_selectorcolor},
72 	{ "theme.menu.selectorcolor", COLOR, &t.menu.selectorcolor},
73 	{ "theme.menu.f_namecolor", COLOR, &t.menu.f_namecolor},
74 	{ "theme.menu.namecolor", COLOR, &t.menu.namecolor},
75 	{ "theme.menu.f_desccolor", COLOR, &t.menu.f_desccolor},
76 	{ "theme.menu.desccolor", COLOR, &t.menu.desccolor},
77 	{ "theme.menu.namesepcolor", COLOR, &t.menu.namesepcolor},
78 	{ "theme.menu.descsepcolor", COLOR, &t.menu.descsepcolor},
79 	{ "theme.menu.f_shortcutcolor", COLOR, &t.menu.f_shortcutcolor},
80 	{ "theme.menu.shortcutcolor", COLOR, &t.menu.shortcutcolor},
81 	{ "theme.menu.bottomdesccolor", COLOR, &t.menu.bottomdesccolor},
82 
83 	{ "theme.form.f_fieldcolor", COLOR, &t.form.f_fieldcolor},
84 	{ "theme.form.fieldcolor", COLOR, &t.form.fieldcolor},
85 	{ "theme.form.readonlycolor", COLOR, &t.form.readonlycolor},
86 	{ "theme.form.bottomdesccolor", COLOR, &t.form.bottomdesccolor},
87 
88 	{ "theme.bar.f_color", COLOR, &t.bar.f_color},
89 	{ "theme.bar.color", COLOR, &t.bar.color},
90 
91 	{ "theme.button.minmargin", UINT, &t.button.minmargin},
92 	{ "theme.button.maxmargin", UINT, &t.button.maxmargin},
93 	{ "theme.button.leftdelim", CHAR, &t.button.leftdelim},
94 	{ "theme.button.rightdelim", CHAR, &t.button.rightdelim},
95 	{ "theme.button.delimcolor", COLOR, &t.button.delimcolor},
96 	{ "theme.button.f_delimcolor", COLOR, &t.button.f_delimcolor},
97 	{ "theme.button.color", COLOR, &t.button.color},
98 	{ "theme.button.f_color", COLOR, &t.button.f_color},
99 	{ "theme.button.shortcutcolor", COLOR, &t.button.shortcutcolor},
100 	{ "theme.button.f_shortcutcolor", COLOR, &t.button.f_shortcutcolor}
101 };
102 
103 static const char *color[8] = {
104 	"black",
105 	"red",
106 	"green",
107 	"yellow",
108 	"blue",
109 	"magenta",
110 	"cyan",
111 	"white"
112 };
113 
114 #define EXIT_FMTERROR(fmt, ...) do {                                           \
115 	bsddialog_end();                                                       \
116 	printf("Error: ");                                                     \
117 	printf(fmt, __VA_ARGS__);                                              \
118 	printf(".\n");                                                         \
119 	exit (255);                                                            \
120 } while (0)
121 
savetheme(const char * file,const char * version)122 void savetheme(const char *file, const char *version)
123 {
124 	int i;
125 	unsigned int flags;
126 	enum bsddialog_color bg, fg;
127 	time_t clock;
128 	FILE *fp;
129 
130 	if (bsddialog_get_theme(&t) != BSDDIALOG_OK)
131 		EXIT_FMTERROR("cannot save theme: %s", bsddialog_geterror());
132 
133 	if(time(&clock) < 0)
134 		EXIT_FMTERROR("%s", "cannot save profile getting current time");
135 
136 	if ((fp = fopen(file, "w")) == NULL)
137 		EXIT_FMTERROR("cannot open %s to save profile", file);
138 
139 	fprintf(fp, "### bsddialog theme - %s", ctime(&clock));
140 	fputs("# Refer to bsddialog(3) manual for theme.* properties\n", fp);
141 	fprintf(fp, "version %s\n", version);
142 
143 	for (i = 0; i < NPROPERTY; i++) {
144 		switch (p[i].type) {
145 		case CHAR:
146 			fprintf(fp, "%s %c\n", p[i].name, *((char*)p[i].value));
147 			break;
148 		case INT:
149 			fprintf(fp, "%s %d\n", p[i].name, *((int*)p[i].value));
150 			break;
151 		case UINT:
152 			fprintf(fp, "%s %u\n", p[i].name,
153 			    *((unsigned int*)p[i].value));
154 			break;
155 		case BOOL:
156 			fprintf(fp, "%s %s\n", p[i].name,
157 			    *((bool*)p[i].value) ? "true" : "false");
158 			break;
159 		case COLOR:
160 			bsddialog_color_attrs(*(int*)p[i].value, &fg, &bg,
161 			    &flags);
162 			fprintf(fp, "%s %s %s%s%s%s\n",
163 			    p[i].name, color[fg], color[bg],
164 			    flags & BSDDIALOG_BOLD ? " bold" : "",
165 			    flags & BSDDIALOG_REVERSE ? " reverse" : "",
166 			    flags & BSDDIALOG_UNDERLINE ? " underline" : "");
167 			break;
168 		}
169 	}
170 
171 	fclose(fp);
172 }
173 
setdeftheme(enum bsddialog_default_theme theme)174 void setdeftheme(enum bsddialog_default_theme theme)
175 {
176 	if (bsddialog_set_default_theme(theme) != BSDDIALOG_OK)
177 		EXIT_FMTERROR("%s", bsddialog_geterror());
178 }
179 
loadtheme(const char * file)180 void loadtheme(const char *file)
181 {
182 	bool boolvalue;
183 	char charvalue, *value;
184 	char line[BUFSIZ], name[BUFSIZ], c1[BUFSIZ], c2[BUFSIZ];
185 	int i, j, intvalue, flags;
186 	unsigned int uintvalue;
187 	enum bsddialog_color bg, fg;
188 	FILE *fp;
189 
190 	if (bsddialog_get_theme(&t) != BSDDIALOG_OK)
191 		EXIT_FMTERROR("Cannot get current theme: %s",
192 		    bsddialog_geterror());
193 
194 	if((fp = fopen(file, "r")) == NULL)
195 		EXIT_FMTERROR("Cannot open theme \"%s\"", file);
196 
197 #define EXIT_ERROR(name, error) do {                                           \
198 	fclose(fp);                                                            \
199 	EXIT_FMTERROR("%s for \"%s\"", error, name);                           \
200 } while (0)
201 
202 	while(fgets(line, BUFSIZ, fp) != NULL) {
203 		if(line[0] == '#' || line[0] == '\n')
204 			continue;  /* superfluous, only for efficiency */
205 		sscanf(line, "%s", name);
206 		for (i = 0; i < NPROPERTY; i++) {
207 			if (strcmp(name, p[i].name) == 0) {
208 				value = &line[strlen(name)];
209 				break;
210 			}
211 		}
212 		if (i >= NPROPERTY) {
213 			if (strcmp(name, "version") == 0)
214 				continue;
215 			EXIT_ERROR(name, "Unknown theme property name");
216 		}
217 		switch (p[i].type) {
218 		case CHAR:
219 			while (value[0] == ' ' || value[0] == '\n' ||
220 			    value[0] == '\0')
221 				value++;
222 			if (sscanf(value, "%c", &charvalue) != 1)
223 				EXIT_ERROR(p[i].name, "Cannot get a char");
224 			*((int*)p[i].value) = charvalue;
225 			break;
226 		case INT:
227 			if (sscanf(value, "%d", &intvalue) != 1)
228 				EXIT_ERROR(p[i].name, "Cannot get a int");
229 			*((int*)p[i].value) = intvalue;
230 			break;
231 		case UINT:
232 			if (sscanf(value, "%u", &uintvalue) != 1)
233 				EXIT_ERROR(p[i].name, "Cannot get a uint");
234 			*((unsigned int*)p[i].value) = uintvalue;
235 			break;
236 		case BOOL:
237 			boolvalue = (strstr(value, "true") != NULL) ?
238 			    true :false;
239 			*((bool*)p[i].value) = boolvalue;
240 			break;
241 		case COLOR:
242 			if (sscanf(value, "%s %s", c1, c2) != 2)
243 				EXIT_ERROR(p[i].name, "Cannot get 2 colors");
244 			/* Foreground */
245 			for (j = 0; j < 8 ; j++)
246 				if ((strstr(c1, color[j])) != NULL)
247 					break;
248 			if ((fg = j) > 7)
249 				EXIT_ERROR(p[i].name, "Bad foreground");
250 			/* Background */
251 			for (j = 0; j < 8 ; j++)
252 				if ((value = strstr(c2, color[j])) != NULL)
253 					break;
254 			if ((bg = j) > 7)
255 				EXIT_ERROR(p[i].name, "Bad background");
256 			/* Flags */
257 			flags = 0;
258 			if (strstr(value, "bold") != NULL)
259 				flags |= BSDDIALOG_BOLD;
260 			if (strstr(value, "reverse") != NULL)
261 				flags |= BSDDIALOG_REVERSE;
262 			if (strstr(value, "underline") != NULL)
263 				flags |= BSDDIALOG_UNDERLINE;
264 			*((int*)p[i].value) = bsddialog_color(fg, bg, flags);
265 			break;
266 		}
267 	}
268 
269 	fclose(fp);
270 
271 	bsddialog_set_theme(&t);
272 }
273 
bikeshed(struct bsddialog_conf * conf)274 void bikeshed(struct bsddialog_conf *conf)
275 {
276 	int margin, i;
277 	int colors[8] = {0, 0, 0, 0 ,0 ,0 , 0, 0};
278 	enum bsddialog_color col[6];
279 	time_t clock;
280 
281 	time(&clock);
282 	srand(clock);
283 
284 	/* theme */
285 	if (bsddialog_get_theme(&t) != BSDDIALOG_OK)
286 		EXIT_FMTERROR("%s", bsddialog_geterror());
287 
288 	for (i = 0; i < 6; i++) {
289 		do {
290 			col[i] = rand() % 8;
291 		} while (colors[col[i]] == 1);
292 		colors[col[i]] = 1;
293 	}
294 
295 	t.screen.color = bsddialog_color(col[4], col[3], 0);
296 
297 	t.shadow.color   = bsddialog_color(col[0], col[0], 0);
298 	t.shadow.y       = 1,
299 	t.shadow.x       = 2,
300 
301 	t.dialog.delimtitle       = (rand() % 2 == 0) ? true : false;
302 	t.dialog.titlecolor       = bsddialog_color(col[3], col[5], 0);
303 	t.dialog.lineraisecolor   = bsddialog_color(col[0], col[5], 0);
304 	t.dialog.linelowercolor   = bsddialog_color(col[0], col[5], 0);
305 	t.dialog.color            = bsddialog_color(col[0], col[5], 0);
306 	t.dialog.bottomtitlecolor = bsddialog_color(col[0], col[5], 0);
307 	t.dialog.arrowcolor       = bsddialog_color(col[3], col[5], 0);
308 
309 	t.menu.f_selectorcolor = bsddialog_color(col[5], col[3], 0);
310 	t.menu.selectorcolor   = bsddialog_color(col[0], col[5], 0);
311 	t.menu.f_desccolor     = bsddialog_color(col[5], col[3], 0);
312 	t.menu.desccolor       = bsddialog_color(col[0], col[5], 0);
313 	t.menu.f_namecolor     = bsddialog_color(col[5], col[3], 0);
314 	t.menu.namecolor       = bsddialog_color(col[3], col[5], 0);
315 	t.menu.namesepcolor    = bsddialog_color(col[1], col[5], 0);
316 	t.menu.descsepcolor    = bsddialog_color(col[1], col[5], 0);
317 	t.menu.f_shortcutcolor = bsddialog_color(col[1], col[3], 0);
318 	t.menu.shortcutcolor   = bsddialog_color(col[1], col[5], 0);
319 	t.menu.bottomdesccolor = bsddialog_color(col[4], col[3], 0);
320 
321 	t.form.f_fieldcolor    = bsddialog_color(col[5], col[3], 0);
322 	t.form.fieldcolor      = bsddialog_color(col[5], col[4], 0);
323 	t.form.readonlycolor   = bsddialog_color(col[4], col[5], 0);
324 	t.form.bottomdesccolor = bsddialog_color(col[4], col[3], 0);
325 
326 	t.bar.f_color = bsddialog_color(col[5], col[3], 0);
327 	t.bar.color   = bsddialog_color(col[3], col[5], 0);
328 
329 	t.button.minmargin       = 1,
330 	t.button.maxmargin       = 5,
331 	t.button.leftdelim       = '[',
332 	t.button.rightdelim      = ']',
333 	t.button.f_delimcolor    = bsddialog_color(col[5], col[3], 0);
334 	t.button.delimcolor      = bsddialog_color(col[0], col[5], 0);
335 	t.button.f_color         = bsddialog_color(col[2], col[3], 0);
336 	t.button.color           = bsddialog_color(col[0], col[5], 0);
337 	t.button.f_shortcutcolor = bsddialog_color(col[5], col[3], 0);
338 	t.button.shortcutcolor   = bsddialog_color(col[1], col[5], 0);
339 
340 	if (bsddialog_set_theme(&t))
341 		EXIT_FMTERROR("%s", bsddialog_geterror());
342 
343 	/* conf */
344 	conf->button.always_active = (rand() % 2 == 0) ? true : false;
345 	if (conf->title != NULL) {
346 		memset(title, 0, 1024);
347 		margin = rand() % 5;
348 		memset(title, ' ', margin);
349 		strcpy(title + margin, conf->title);
350 		memset(title + strlen(title), ' ', margin);
351 		conf->title = title;
352 	}
353 }
354