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