1 /*
2 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
3 * Copyright (C) 2004-2021 Kim Woelders
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies of the Software, its documentation and marketing & publicity
14 * materials, and acknowledgment shall be given in the documentation, materials
15 * and software packages that this Software was used.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24 #include "E.h"
25 #include "aclass.h"
26 #include "backgrounds.h"
27 #include "borders.h"
28 #include "buttons.h"
29 #include "conf.h"
30 #include "cursors.h"
31 #include "file.h"
32 #include "iclass.h"
33 #include "menus.h"
34 #include "progress.h"
35 #include "session.h"
36 #include "tclass.h"
37 #include "tooltips.h"
38 #include "windowmatch.h"
39
40 #define MAX_E_CFG_VERSION 2 /* Max. supported configuration version */
41
42 void
SkipTillEnd(FILE * fs)43 SkipTillEnd(FILE * fs)
44 {
45 char s[FILEPATH_LEN_MAX];
46 int i1, i2;
47
48 while (GetLine(s, sizeof(s), fs))
49 {
50 i1 = i2 = 0;
51 sscanf(s, "%i %i", &i1, &i2);
52 if (i1 == CONFIG_CLOSE)
53 return;
54 if (i2 == CONFIG_OPEN)
55 SkipTillEnd(fs);
56 }
57 }
58
59 #define LINE_BUFFER_SIZE 1024
60 /*
61 * This function will get a single line from the file
62 * The string will be null terminated.
63 * Size must be >= 2.
64 */
65 char *
GetLine(char * s,int size,FILE * f)66 GetLine(char *s, int size, FILE * f)
67 {
68 static char *buffer = NULL;
69 static const char *bufptr = NULL;
70 char *so, ch, quote, escape;
71 const char *si;
72 size_t nr;
73
74 if (!buffer)
75 {
76 buffer = EMALLOC(char, LINE_BUFFER_SIZE);
77
78 if (!buffer)
79 return NULL;
80 buffer[LINE_BUFFER_SIZE - 1] = '\0';
81 }
82
83 si = bufptr;
84 so = s;
85 quote = '\0';
86 escape = '\0';
87 for (;;)
88 {
89 /* Get a line from the input file */
90 if (!si)
91 {
92 nr = fread(buffer, 1, LINE_BUFFER_SIZE - 1, f);
93 if (nr == 0)
94 break;
95 buffer[nr] = '\0';
96 si = buffer;
97 }
98
99 /* Split on ';' or '\n', handle quoting */
100 ch = *si++;
101 switch (ch)
102 {
103 case '\0':
104 si = NULL;
105 break;
106 case ';': /* Line separator */
107 if (escape || quote)
108 goto case_char;
109 /* FALLTHROUGH */
110 case '\n':
111 if (so == s) /* Skip empty lines */
112 break;
113 *so = '\0'; /* Terminate and return */
114 goto done;
115 case '\r': /* Ignore */
116 break;
117 case '\\': /* Escape */
118 if (escape)
119 goto case_char;
120 escape = ch;
121 break;
122 case '"': /* Quoting */
123 /* case '\'': */
124 if (escape)
125 goto case_char;
126 if (quote == '\0')
127 quote = ch;
128 else if (quote == ch)
129 quote = '\0';
130 else
131 goto case_char;
132 break;
133 case ' ': /* Whitespace */
134 case '\t':
135 if (so == s) /* Skip leading whitespace */
136 break;
137 /* FALLTHROUGH */
138 case_char: /* Normal character */
139 default:
140 *so++ = ch;
141 escape = '\0';
142 if (--size > 1)
143 break;
144 *so = '\0';
145 goto done;
146 }
147 }
148
149 done:
150 bufptr = si;
151 if (!si)
152 {
153 /* EOF */
154 EFREE_NULL(buffer);
155 if (so == s)
156 return NULL;
157 }
158
159 /* Strip trailing whitespace */
160 si = so;
161 for (; so > s; so--)
162 {
163 ch = so[-1];
164 if (ch != ' ' && ch != '\t')
165 break;
166 }
167 if (so != si)
168 *so = '\0';
169
170 if (EDebug(EDBUG_TYPE_CONFIG) > 1)
171 Eprintf("%s: %s\n", __func__, s);
172
173 return s;
174 }
175
176 int
ConfigParseline1(char * str,char * s2,char ** p2,char ** p3)177 ConfigParseline1(char *str, char *s2, char **p2, char **p3)
178 {
179 int i1, len1, len2, fields;
180
181 i1 = CONFIG_INVALID;
182 len1 = len2 = 0;
183 s2[0] = '\0';
184 fields = sscanf(str, "%i %n%4000s %n", &i1, &len1, s2, &len2);
185 if (p2)
186 *p2 = (len1) ? str + len1 : NULL;
187 if (p3)
188 *p3 = (len2) ? str + len2 : NULL;
189
190 if (fields <= 0)
191 {
192 i1 = CONFIG_INVALID;
193 }
194 else if (i1 == CONFIG_CLOSE || i1 == CONFIG_NEXT)
195 {
196 if (fields != 1)
197 {
198 Alert(_("CONFIG: ignoring extra data in \"%s\""), str);
199 }
200 }
201 else if (i1 != CONFIG_INVALID)
202 {
203 if (fields != 2)
204 {
205 i1 = CONFIG_INVALID;
206 Alert(_("CONFIG: missing required data in \"%s\""), str);
207 }
208 }
209
210 return i1;
211 }
212
213 void
ConfigParseError(const char * where,const char * line)214 ConfigParseError(const char *where, const char *line)
215 {
216 Alert(_("Warning: unable to determine what to do with\n"
217 "the following text in the middle of current %s definition:\n"
218 "%s\nWill ignore and continue...\n"), where, line);
219 }
220
221 void
ConfigAlertLoad(const char * txt)222 ConfigAlertLoad(const char *txt)
223 {
224 Alert(_("Warning: Configuration error in %s block.\n"
225 "Outcome is likely not good.\n"), txt);
226 }
227
228 static int
ConfigFilePreparse(const char * src,const char * dst,const char * themepath)229 ConfigFilePreparse(const char *src, const char *dst, const char *themepath)
230 {
231 const char *variant;
232
233 if (EDebug(EDBUG_TYPE_CONFIG))
234 Eprintf("%s: %s -> %s\n", __func__, src, dst);
235
236 /* When themepath is NULL it shouldn't be used, but this is consistent
237 * with old behavior */
238 if (!themepath)
239 themepath = Mode.theme.path;
240 variant = (Mode.theme.variant) ? Mode.theme.variant : "";
241
242 Esystem("%s/epp -P -nostdinc -undef "
243 "-include %s/config/definitions -I%s -I%s/config "
244 "-D ENLIGHTENMENT_VERSION=%s "
245 "-D ENLIGHTENMENT_ROOT=%s "
246 "-D ENLIGHTENMENT_BIN=%s "
247 "-D ENLIGHTENMENT_THEME=%s "
248 "-D THEME_VARIANT_%s=1 "
249 "-D ECONFDIR=%s "
250 "-D ECACHEDIR=%s "
251 "-D SCREEN_RESOLUTION_%ix%i=1 "
252 "-D SCREEN_WIDTH_%i=1 "
253 "-D SCREEN_HEIGHT_%i=1 "
254 "-D SCREEN_DEPTH_%i=1 "
255 "%s %s",
256 EDirBin(), EDirRoot(), themepath, EDirRoot(),
257 e_wm_version, EDirRoot(), EDirBin(), themepath, variant,
258 EDirUserConf(), EDirUserCache(),
259 WinGetW(VROOT), WinGetH(VROOT), WinGetW(VROOT), WinGetH(VROOT),
260 WinGetDepth(VROOT), src, dst);
261
262 return exists(dst) ? 0 : 1;
263 }
264
265 /* Split the process of finding the file from the process of loading it */
266 int
ConfigFileRead(FILE * fs)267 ConfigFileRead(FILE * fs)
268 {
269 int err;
270 int i1, i2, fields;
271 char s[FILEPATH_LEN_MAX];
272 int e_cfg_ver = 0;
273
274 while (GetLine(s, sizeof(s), fs))
275 {
276 i1 = i2 = CONFIG_INVALID;
277 fields = sscanf(s, "%i %i", &i1, &i2);
278
279 if (fields < 1)
280 {
281 i1 = CONFIG_INVALID;
282 }
283 else if (i1 == CONFIG_VERSION)
284 {
285 if (fields == 2)
286 e_cfg_ver = i2;
287 }
288 else if (i1 == CONFIG_CLOSE)
289 {
290 if (fields != 1)
291 {
292 Alert(_("CONFIG: ignoring extra data in \"%s\""), s);
293 }
294 }
295 else if (i1 != CONFIG_INVALID)
296 {
297 if (fields != 2)
298 {
299 Alert(_("CONFIG: missing required data in \"%s\""), s);
300 i1 = CONFIG_INVALID;
301 }
302 }
303
304 if (i2 == CONFIG_OPEN)
305 {
306 if (e_cfg_ver > MAX_E_CFG_VERSION)
307 {
308 AlertX(_("Theme versioning ERROR"),
309 _("Restart with Defaults"), " ",
310 _("Abort and Exit"),
311 _("ERROR:\n" "\n"
312 "The configuration for the theme you are running is\n"
313 "incompatible. It's config revision is %i.\n"
314 "It needs to be marked as being revision <= %i\n"
315 "\n"
316 "Please contact the theme author or maintainer and\n"
317 "inform them that in order for their theme to function\n"
318 "with this version of Enlightenment, they have to\n"
319 "update it to the current settings, and then match\n"
320 "the revision number.\n" "\n"
321 "If the theme revision is higher than Enlightenment's\n"
322 "it may be that you haven't upgraded Enlightenment for\n"
323 "a while and this theme takes advantages of new\n"
324 "features in Enlightenment in new versions.\n"),
325 e_cfg_ver, MAX_E_CFG_VERSION);
326 SessionExit(EEXIT_THEME, "DEFAULT");
327 }
328 else
329 {
330 switch (i1)
331 {
332 case CONFIG_CLOSE:
333 goto done;
334
335 case CONFIG_CURSOR:
336 err = ECursorConfigLoad(fs);
337 if (err)
338 ConfigAlertLoad("Cursor");
339 break;
340 case CONFIG_IMAGECLASS:
341 err = ImageclassConfigLoad(fs);
342 if (err)
343 ConfigAlertLoad("Image class");
344 break;
345 case CONFIG_TOOLTIP:
346 err = TooltipConfigLoad(fs);
347 if (err)
348 ConfigAlertLoad("Tooltip");
349 break;
350 case CONFIG_TEXT:
351 err = TextclassConfigLoad(fs);
352 if (err)
353 ConfigAlertLoad("Text class");
354 break;
355 case MENU_STYLE:
356 err = MenuStyleConfigLoad(fs);
357 if (err)
358 ConfigAlertLoad("Menu style");
359 break;
360 case CONFIG_MENU:
361 err = MenuConfigLoad(fs);
362 if (err)
363 ConfigAlertLoad("Menu");
364 break;
365 case CONFIG_BORDER:
366 err = BorderConfigLoad(fs);
367 if (err)
368 ConfigAlertLoad("Border");
369 break;
370 case CONFIG_BUTTON:
371 err = ButtonsConfigLoad(fs);
372 if (err)
373 ConfigAlertLoad("Button");
374 break;
375 case CONFIG_DESKTOP:
376 err = BackgroundsConfigLoad(fs);
377 if (err)
378 ConfigAlertLoad("Background");
379 break;
380 case CONFIG_WINDOWMATCH:
381 err = WindowMatchConfigLoad(fs);
382 if (err)
383 ConfigAlertLoad("Window match");
384 break;
385 case CONFIG_COLORMOD:
386 break;
387 case CONFIG_ACTIONCLASS:
388 err = AclassConfigLoad(fs);
389 if (err)
390 ConfigAlertLoad("Action class");
391 break;
392 case CONFIG_SLIDEOUT:
393 err = SlideoutsConfigLoad(fs);
394 if (err)
395 ConfigAlertLoad("Slideout");
396 break;
397 default:
398 break;
399 }
400 }
401 }
402 }
403
404 done:
405 return 0;
406 }
407
408 static char *
FindFilePath(const char * name,const char * path)409 FindFilePath(const char *name, const char *path)
410 {
411 char s[FILEPATH_LEN_MAX];
412 int len;
413
414 #if 0
415 Eprintf("%s: %s (%s)\n", __func__, name, path);
416 #endif
417 if (path)
418 {
419 len = Esnprintf(s, sizeof(s), "%s/%s", path, name);
420 name = s;
421 }
422 else
423 {
424 len = strlen(name);
425 }
426 if (len <= 0)
427 return NULL;
428
429 if (canread(name))
430 return Estrdup(name);
431 else
432 return NULL;
433 }
434
435 /* *INDENT-OFF* */
436 static const struct {
437 const char *where, *subdir;
438 } fprm[] = {
439 { "utE", "config" },
440 { "ute", NULL },
441 { "UuEt", "menus" },
442 { "UuE", "icons" }
443 };
444 /* *INDENT-ON* */
445
446 char *
FindFile(const char * file,const char * themepath,int type)447 FindFile(const char *file, const char *themepath, int type)
448 {
449 const char *w, *f, *path;
450 char s[FILEPATH_LEN_MAX];
451 char *p;
452
453 /* if absolute path - and file exists - return it */
454 if (isabspath(file))
455 {
456 p = FindFilePath(file, NULL);
457 /* Absolute path - no need to look elsewhere */
458 goto done;
459 }
460
461 p = NULL;
462 for (w = fprm[type].where; *w; w++)
463 {
464 f = file;
465 if (*w <= 'Z')
466 {
467 /* Look in subdir */
468 Esnprintf(s, sizeof(s), "%s/%s", fprm[type].subdir, file);
469 f = s;
470 }
471
472 switch (*w & 0xdf)
473 {
474 default:
475 continue;
476 case 'U': /* User config */
477 path = EDirUserConf();
478 break;
479 case 'E': /* e16 config */
480 path = EDirRoot();
481 break;
482 case 'T': /* Theme */
483 path = themepath;
484 if (!path)
485 continue;
486 break;
487 }
488 p = FindFilePath(f, path);
489 if (p)
490 break;
491 }
492
493 done:
494 #if 0
495 Eprintf("%s %d: %s (%s): %s\n", __func__, type, file, themepath, p);
496 #endif
497 return p;
498 }
499
500 char *
ThemeFileFind(const char * file,int type)501 ThemeFileFind(const char *file, int type)
502 {
503 return FindFile(file, Mode.theme.path, type);
504 }
505
506 static char *
ConfigFileFind(const char * name,const char * themepath,int pp)507 ConfigFileFind(const char *name, const char *themepath, int pp)
508 {
509 char s[FILEPATH_LEN_MAX];
510 char *fullname, *file, *ppfile;
511 int i, err;
512
513 fullname = FindFile(name, themepath, FILE_TYPE_CONFIG);
514 if (!fullname)
515 return NULL;
516
517 /* Quit if not preparsing */
518 if (!pp)
519 return fullname;
520
521 /* The file exists. Now check the preparsed one. */
522 file = Estrdup(fullname);
523 for (i = 0; file[i]; i++)
524 if (file[i] == '/')
525 file[i] = '.';
526
527 if (Mode.theme.variant)
528 file = Estrdupcat2(file, "_", Mode.theme.variant);
529 Esnprintf(s, sizeof(s), "%s/cached/cfg/%s.preparsed", EDirUserCache(), file);
530
531 ppfile = Estrdup(s);
532 if (exists(s) && moddate(s) > moddate(fullname))
533 goto done;
534
535 /* No preparesd file or source is newer. Do preparsing. */
536 err = ConfigFilePreparse(fullname, ppfile, themepath);
537 if (err)
538 {
539 EFREE_NULL(ppfile);
540 }
541
542 done:
543 Efree(fullname);
544 Efree(file);
545 return ppfile;
546 }
547
548 int
ConfigFileLoad(const char * name,const char * themepath,int (* parse)(FILE * fs),int preparse)549 ConfigFileLoad(const char *name, const char *themepath,
550 int (*parse)(FILE * fs), int preparse)
551 {
552 int err = -1;
553 char *file;
554 FILE *fs;
555
556 if (EDebug(EDBUG_TYPE_CONFIG))
557 Eprintf("%s: %s\n", __func__, name);
558
559 file = ConfigFileFind(name, themepath, preparse);
560 if (!file)
561 goto done;
562
563 fs = fopen(file, "r");
564 Efree(file);
565 if (!fs)
566 goto done;
567
568 err = parse(fs);
569
570 fclose(fs);
571
572 done:
573 return err;
574 }
575
576 int
ThemeConfigLoad(void)577 ThemeConfigLoad(void)
578 {
579 static const char *const config_files[] = {
580 "init.cfg",
581 "cursors.cfg",
582 "textclasses.cfg",
583 "imageclasses.cfg",
584 "desktops.cfg",
585 "actionclasses.cfg",
586 "buttons.cfg",
587 "slideouts.cfg",
588 "borders.cfg",
589 "windowmatches.cfg",
590 "tooltips.cfg",
591 "menustyles.cfg",
592 };
593 Progressbar *p = NULL;
594 unsigned int i;
595
596 /* Font mappings */
597 FontConfigLoad();
598
599 for (i = 0; i < E_ARRAY_SIZE(config_files); i++)
600
601 {
602 if (!Mode.wm.restart && Conf.startup.animate)
603 {
604 if (i == 2)
605 StartupWindowsCreate();
606
607 if ((i > 1) && (!p))
608 {
609 p = ProgressbarCreate(_("Enlightenment Starting..."), 400,
610 16);
611 if (p)
612 ProgressbarShow(p);
613 }
614 }
615
616 ConfigFileLoad(config_files[i], Mode.theme.path, ConfigFileRead, 1);
617
618 if (p)
619 ProgressbarSet(p, (i * 100) / E_ARRAY_SIZE(config_files));
620
621 /* Hack - We are not running in the event loop here */
622 EobjsRepaint();
623 }
624
625 if (p)
626 ProgressbarDestroy(p);
627
628 /* Font mappings no longer needed */
629 FontConfigUnload();
630
631 return 0;
632 }
633