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 "emodule.h"
26 #include "file.h"
27 #include "user.h"
28 #include "util.h"
29 #include "session.h"
30 
31 /* Update Mode.theme.paths (theme path list) */
32 static void
_ThemePathsUpdate(void)33 _ThemePathsUpdate(void)
34 {
35    char                paths[FILEPATH_LEN_MAX];
36 
37    Esnprintf(paths, sizeof(paths), "%s/themes:%s/.themes:%s/themes:%s",
38 	     EDirUserConf(), userhome(), EDirRoot(),
39 	     (Conf.theme.extra_path) ? Conf.theme.extra_path : "");
40    EFREE_DUP(Mode.theme.paths, paths);
41 }
42 
43 /* Check if this is a theme dir */
44 static int
_ThemeCheckDir(const char * path,const char * px)45 _ThemeCheckDir(const char *path, const char *px)
46 {
47    static const char  *const theme_files[] = {
48       "init.cfg",
49 #if 0
50       "epplets/epplets.cfg",
51 #endif
52       NULL
53    };
54    const char         *tf;
55    int                 i;
56    char                s[FILEPATH_LEN_MAX];
57 
58    if (EDebug(EDBUG_TYPE_CONFIG))
59       Eprintf("%s: %s%s\n", __func__, path, px);
60 
61    for (i = 0; (tf = theme_files[i]); i++)
62      {
63 	Esnprintf(s, sizeof(s), "%s%s/%s", path, px, tf);
64 	if (!isfile(s))
65 	   return 0;
66      }
67 
68    return 1;
69 }
70 
71 static int
_ThemeCheckPath1(const char * path)72 _ThemeCheckPath1(const char *path)
73 {
74    if (_ThemeCheckDir(path, ""))
75       return 1;
76 
77    if (_ThemeCheckDir(path, "/e16"))
78       return 1;
79 
80    return 0;
81 }
82 
83 static char        *
_ThemeCheckPath(const char * path)84 _ThemeCheckPath(const char *path)
85 {
86    char                ss[FILEPATH_LEN_MAX];
87 
88    if (EDebug(EDBUG_TYPE_CONFIG))
89       Eprintf("%s: %s\n", __func__, path);
90 
91    if (_ThemeCheckDir(path, ""))
92       return Estrdup(path);
93 
94    if (_ThemeCheckDir(path, "/e16"))
95      {
96 	Esnprintf(ss, sizeof(ss), "%s/e16", path);
97 	return Estrdup(ss);
98      }
99 
100    return NULL;
101 }
102 
103 char               *
ThemePathName(const char * path)104 ThemePathName(const char *path)
105 {
106    const char         *p;
107    char               *s;
108 
109    if (!path)
110       return NULL;
111    p = strrchr(path, '/');
112    if (!p)
113       return Estrdup(path);	/* Name only */
114    if (strcmp(p + 1, "e16"))
115       return Estrdup(p + 1);	/* Regular path */
116 
117    /* <path>/<themename>/e16 */
118    s = Estrdup(path);
119    s[p - path] = '\0';
120    p = strrchr(s, '/');
121    if (!p)
122       return s;			/* Should not happen */
123    p++;
124    memmove(s, p, strlen(p) + 1);
125    return s;
126 }
127 
128 static void
_append_merge_dir(char * dir,char *** list,int * count)129 _append_merge_dir(char *dir, char ***list, int *count)
130 {
131    char                ss[4000], s1[FILEPATH_LEN_MAX];
132    char              **str, *s;
133    int                 i, num;
134 
135    str = E_ls(dir, &num);
136    if (!str)
137       return;
138 
139    for (i = 0; i < num; i++)
140      {
141 	if (!strcmp(str[i], "DEFAULT"))
142 	   continue;
143 
144 	Esnprintf(ss, sizeof(ss), "%s/%s", dir, str[i]);
145 
146 	if (isdir(ss))
147 	  {
148 	     if (_ThemeCheckPath1(ss))
149 		goto got_one;
150 	     continue;
151 	  }
152 
153 	if (!isfile(ss))
154 	   continue;
155 
156 	s = strrchr(ss, '.');
157 	if (!s)
158 	   continue;
159 
160 	if (strcmp(s + 1, "etheme") == 0)
161 	  {
162 	     Esnprintf(s1, sizeof(s1), "%s/themes/%s", EDirUserConf(), str[i]);
163 	     s = strrchr(s1, '.');
164 	     if (!s)
165 		continue;
166 	     *s = '\0';
167 	     if (!isdir(s1))
168 		goto got_one;
169 	  }
170 
171 	if (strcmp(s + 1, "theme") == 0)
172 	  {
173 	     Esnprintf(s1, sizeof(s1), "%s/.themes/%s", userhome(), str[i]);
174 	     s = strrchr(s1, '.');
175 	     if (!s)
176 		continue;
177 	     *s = '\0';
178 	     if (!isdir(s1))
179 		goto got_one;
180 	  }
181 
182 	continue;
183 
184       got_one:
185 	(*count)++;
186 	*list = EREALLOC(char *, *list, *count);
187 
188 	(*list)[(*count) - 1] = Estrdup(ss);
189      }
190    StrlistFree(str, num);
191 }
192 
193 char              **
ThemesList(int * number)194 ThemesList(int *number)
195 {
196    char              **lst, **list;
197    int                 i, num, count;
198 
199    _ThemePathsUpdate();
200    lst = StrlistFromString(Mode.theme.paths, ':', &num);
201 
202    count = 0;
203    list = NULL;
204    for (i = 0; i < num; i++)
205       _append_merge_dir(lst[i], &list, &count);
206 
207    StrlistFree(lst, num);
208 
209    *number = count;
210    return list;
211 }
212 
213 static char        *
_ThemeExtract(const char * path)214 _ThemeExtract(const char *path)
215 {
216    char                th[4000];
217    FILE               *f;
218    unsigned char       buf[262];
219    size_t              ret;
220    const char         *p;
221    char                name[128], *type;
222 
223    if (EDebug(EDBUG_TYPE_CONFIG))
224       Eprintf("%s: %s\n", __func__, path);
225 
226    /* its a file - check its type */
227    f = fopen(path, "r");
228    if (!f)
229       return NULL;
230    ret = fread(buf, 1, sizeof(buf), f);
231    memset(buf + ret, 0, sizeof(buf) - ret);
232    fclose(f);
233 
234    p = strrchr(path, '/');
235    p = (p) ? p + 1 : path;
236    Esnprintf(name, sizeof(name), "%.127s", p);
237    type = strchr(name, '.');
238    if (type)
239       *type++ = '\0';
240 
241    if (type && strcmp(type, "theme") == 0)
242      {
243 	Esnprintf(th, sizeof(th), "%s/.themes", userhome());
244 	if (!isdir(th))
245 	   E_md(th);
246 	Esnprintf(th, sizeof(th), "%s/.themes/%s", userhome(), name);
247      }
248    else
249       Esnprintf(th, sizeof(th), "%s/themes/%s", EDirUserConf(), name);
250 
251    /* check magic numbers */
252    if ((buf[0] == 31) && (buf[1] == 139))
253      {
254 	/* gzipped tarball */
255 	E_md(th);
256 	Esystem("gzip -d -c < %s | (cd %s ; tar -xf -)", path, th);
257      }
258    else if ((buf[257] == 'u') && (buf[258] == 's') &&
259 	    (buf[259] == 't') && (buf[260] == 'a') && (buf[261] == 'r'))
260      {
261 	/* vanilla tarball */
262 	E_md(th);
263 	Esystem("cat %s | (cd %s ; tar -xf -)", path, th);
264      }
265    else
266       return NULL;
267 
268    return _ThemeCheckPath(th);
269 }
270 
271 char               *
ThemePathFind(const char * theme)272 ThemePathFind(const char *theme)
273 {
274    static const char  *const default_themes[] = {
275       "DEFAULT", "winter", "BrushedMetal-Tigert", "ShinyMetal", NULL
276    };
277    char                tpbuf[4000], *path;
278    char              **lst;
279    int                 i, j, num;
280 
281    if (EDebug(EDBUG_TYPE_CONFIG))
282       Eprintf("%s: %s\n", __func__, theme);
283 
284    _ThemePathsUpdate();
285 
286    if (!theme || !theme[0])
287       theme = NULL;		/* Lookup default */
288    else if (!strcmp(theme, "-"))	/* Use fallbacks */
289       return NULL;
290    else if (exists(theme))
291       goto check;
292 
293    lst = StrlistFromString(Mode.theme.paths, ':', &num);
294 
295    i = 0;
296    do
297      {
298 	if (!theme)
299 	   goto next;
300 	for (j = 0; j < num; j++)
301 	  {
302 	     Esnprintf(tpbuf, sizeof(tpbuf), "%s/%s", lst[j], theme);
303 	     if (exists(tpbuf))
304 	       {
305 		  theme = tpbuf;
306 		  goto done;
307 	       }
308 	  }
309       next:
310 	theme = default_themes[i++];
311      }
312    while (theme);
313 
314  done:
315    StrlistFree(lst, num);
316 
317  check:
318    if (theme)
319      {
320 	path = NULL;
321 	if (isdir(theme))
322 	   path = _ThemeCheckPath(theme);
323 	else if (isfile(theme))
324 	   path = _ThemeExtract(theme);
325 	if (path)
326 	   return path;
327      }
328 
329    /* No theme found yet, just find any theme */
330    lst = ThemesList(&num);
331    if (!lst)
332       return NULL;
333    path = Estrdup(lst[0]);
334    StrlistFree(lst, num);
335 
336    return path;
337 }
338 
339 void
ThemeFind(const char * theme)340 ThemeFind(const char *theme)
341 {
342    char                name[4000];
343    char                namx[FILEPATH_LEN_MAX];
344    const char         *p;
345    char               *path, *s;
346 
347    if (EDebug(EDBUG_TYPE_CONFIG))
348       Eprintf("%s: %s\n", __func__, theme);
349 
350    name[0] = '\0';
351    p = (theme && *theme != ':') ? theme : Conf.theme.name;
352    if (p)
353       snprintf(name, sizeof(name), "%s", p);
354 
355    s = strchr(name, ':');
356    if (s)
357       *s++ = '\0';
358 
359    p = (theme && *theme == ':') ? theme + 1 : s;
360    EFREE_DUP(Mode.theme.variant, p);
361 
362    path = ThemePathFind(name);
363 
364    if (!path && strcmp(name, "-"))
365      {
366 	Alert(_("No themes were found in the default directories:\n"
367 		" %s\n"
368 		"Proceeding from here is mostly pointless.\n"),
369 	      Mode.theme.paths);
370      }
371 
372    if (theme)
373      {
374 	Efree(Conf.theme.name);
375 	if (isfile(theme))
376 	   Conf.theme.name = ThemePathName(path);
377 	else
378 	  {
379 	     s = namx;
380 	     if (Mode.theme.variant)
381 		snprintf(namx, sizeof(namx), "%s:%s", name, Mode.theme.variant);
382 	     else
383 		s = name;
384 	     Conf.theme.name = Estrdup(s);
385 	  }
386      }
387 
388    EFREE_SET(Mode.theme.path, (path) ? path : Estrdup("-"));
389 }
390 
391 #if ENABLE_DIALOGS
392 #include "dialog.h"
393 #include "settings.h"
394 /*
395  * Configuration dialog
396  */
397 static char         tmp_use_theme_font;
398 static char         tmp_use_alt_font;
399 
400 static void
_DlgThemeApply(Dialog * d __UNUSED__,int val __UNUSED__,void * data __UNUSED__)401 _DlgThemeApply(Dialog * d __UNUSED__, int val __UNUSED__, void *data __UNUSED__)
402 {
403    if (Conf.theme.use_theme_font_cfg == tmp_use_theme_font &&
404        Conf.theme.use_alt_font_cfg == tmp_use_alt_font)
405       return;
406 
407    DialogOK(_("Message"), _("Changes will take effect after restart"));
408 
409    Conf.theme.use_theme_font_cfg = tmp_use_theme_font;
410    Conf.theme.use_alt_font_cfg = tmp_use_alt_font;
411    autosave();
412 }
413 
414 static void
_DlgThemeFill(Dialog * d __UNUSED__,DItem * table,void * data __UNUSED__)415 _DlgThemeFill(Dialog * d __UNUSED__, DItem * table, void *data __UNUSED__)
416 {
417    DItem              *di;
418    char                buf[1024];
419 
420    tmp_use_theme_font = Conf.theme.use_theme_font_cfg;
421    tmp_use_alt_font = Conf.theme.use_alt_font_cfg;
422 
423    DialogItemTableSetOptions(table, 2, 0, 0, 0);
424 
425    di = DialogAddItem(table, DITEM_CHECKBUTTON);
426    DialogItemSetColSpan(di, 2);
427    DialogItemSetText(di, _("Use theme font configuration"));
428    DialogItemCheckButtonSetPtr(di, &tmp_use_theme_font);
429 
430    di = DialogAddItem(table, DITEM_CHECKBUTTON);
431    DialogItemSetColSpan(di, 2);
432    Esnprintf(buf, sizeof(buf), _("Use alternate font configuration (%s)"),
433 	     Conf.theme.font_cfg ? Conf.theme.font_cfg : _("Not set"));
434    DialogItemSetText(di, buf);
435    DialogItemCheckButtonSetPtr(di, &tmp_use_alt_font);
436 }
437 
438 const DialogDef     DlgTheme = {
439    "CONFIGURE_AUDIO",
440    N_("Theme"), N_("Theme Settings"),
441    0,
442    SOUND_SETTINGS_MISCELLANEOUS,
443    "pix/miscellaneous.png",
444    N_("Enlightenment Theme\n" "Settings Dialog"),
445    _DlgThemeFill,
446    DLG_OAC, _DlgThemeApply, NULL
447 };
448 #endif /* ENABLE_DIALOGS */
449 
450 /*
451  * Theme module
452  */
453 
454 static void
ThemesIpc(const char * params)455 ThemesIpc(const char *params)
456 {
457    const char         *p;
458    char                cmd[128], prm[128];
459    int                 len;
460 
461    cmd[0] = prm[0] = '\0';
462    p = params;
463    if (p)
464      {
465 	len = 0;
466 	sscanf(p, "%100s %100s %n", cmd, prm, &len);
467 	p += len;
468      }
469 
470    if (!p || cmd[0] == '?')
471      {
472 	char               *path;
473 
474 	IpcPrintf("Name: %s\n", (Conf.theme.name) ? Conf.theme.name : "-");
475 	IpcPrintf("Full: %s\n", Mode.theme.path);
476 	path = ThemePathFind(NULL);
477 	IpcPrintf("Default: %s\n", path);
478 	Efree(path);
479 	IpcPrintf("Path: %s\n", Mode.theme.paths);
480      }
481    else if (!strncmp(cmd, "list", 2))
482      {
483 	char              **lst;
484 	int                 i, num;
485 
486 	lst = ThemesList(&num);
487 	if (!lst)
488 	   return;
489 	for (i = 0; i < num; i++)
490 	   IpcPrintf("%s\n", lst[i]);
491 	StrlistFree(lst, num);
492      }
493    else if (!strcmp(cmd, "use"))
494      {
495 	/* FIXME - ThemeCheckIfValid(s) */
496 	SessionExit(EEXIT_THEME, prm);
497      }
498 }
499 
500 static const IpcItem ThemeIpcArray[] = {
501    {
502     ThemesIpc,
503     "theme", "th",
504     "Theme commands",
505     "  theme             Show current theme\n"
506     "  theme list        Show all themes\n"
507     "  theme use <name>  Switch to theme <name>\n"}
508    ,
509 };
510 
511 static const CfgItem ThemeCfgItems[] = {
512    CFG_ITEM_STR(Conf.theme, name),
513    CFG_ITEM_STR(Conf.theme, extra_path),
514    CFG_ITEM_BOOL(Conf.theme, use_theme_font_cfg, 0),
515    CFG_ITEM_BOOL(Conf.theme, use_alt_font_cfg, 0),
516    CFG_ITEM_STR(Conf.theme, font_cfg),
517 };
518 
519 /*
520  * Module descriptor
521  */
522 extern const EModule ModTheme;
523 
524 const EModule       ModTheme = {
525    "theme", "th",
526    NULL,
527    MOD_ITEMS(ThemeIpcArray),
528    MOD_ITEMS(ThemeCfgItems)
529 };
530