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