1 /*
2  * MOC - music on console
3  * Copyright (C) 2004 - 2006 Damian Pietras <daper@daper.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  */
11 
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15 
16 #ifdef HAVE_NCURSESW_H
17 # include <ncursesw/curses.h>
18 #elif HAVE_NCURSES_H
19 # include <ncurses.h>
20 #elif HAVE_CURSES_H
21 # include <curses.h>
22 #endif
23 
24 #include <stdio.h>
25 #include <assert.h>
26 #include <string.h>
27 #include <strings.h>
28 #include <errno.h>
29 
30 #include "common.h"
31 #include "interface.h"
32 #include "themes.h"
33 #include "files.h"
34 #include "options.h"
35 
36 /* ncurses extension */
37 #ifndef COLOR_DEFAULT
38 # define COLOR_DEFAULT	-2
39 #endif
40 
41 /* hidden color? */
42 #ifndef COLOR_GREY
43 # define COLOR_GREY	10
44 #endif
45 
46 static char current_theme[PATH_MAX];
47 
48 static int colors[CLR_LAST];
49 
50 /* Counter used for making colors (init_pair()) */
51 static short pair_count = 1;
52 
53 /* Initialize a color item of given index (CLR_*) with colors and
54  * attributes. Do nothing if the item is already initialized. */
make_color(const enum color_index index,const short foreground,const short background,const attr_t attr)55 static void make_color (const enum color_index index, const short foreground,
56 		const short background,	const attr_t attr)
57 {
58 	assert (pair_count < COLOR_PAIRS);
59 	assert (index < CLR_LAST);
60 
61 	if (colors[index] == -1) {
62 		init_pair (pair_count, foreground, background);
63 		colors[index] = COLOR_PAIR (pair_count) | attr;
64 
65 		pair_count++;
66 	}
67 }
68 
set_default_colors()69 static void set_default_colors ()
70 {
71 	make_color (CLR_BACKGROUND, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
72 	make_color (CLR_FRAME, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
73 	make_color (CLR_WIN_TITLE, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
74 	make_color (CLR_MENU_ITEM_DIR, COLOR_WHITE, COLOR_BLUE, A_BOLD);
75 	make_color (CLR_MENU_ITEM_DIR_SELECTED, COLOR_WHITE, COLOR_BLACK,
76 			A_BOLD);
77 	make_color (CLR_MENU_ITEM_PLAYLIST, COLOR_WHITE, COLOR_BLUE, A_BOLD);
78 	make_color (CLR_MENU_ITEM_PLAYLIST_SELECTED, COLOR_WHITE, COLOR_BLACK,
79 			A_BOLD);
80 	make_color (CLR_MENU_ITEM_FILE, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
81 	make_color (CLR_MENU_ITEM_FILE_SELECTED, COLOR_WHITE,
82 			COLOR_BLACK, A_NORMAL);
83 	make_color (CLR_MENU_ITEM_FILE_MARKED, COLOR_GREEN, COLOR_BLUE,
84 			A_BOLD);
85 	make_color (CLR_MENU_ITEM_FILE_MARKED_SELECTED, COLOR_GREEN,
86 			COLOR_BLACK, A_BOLD);
87 	make_color (CLR_MENU_ITEM_INFO, COLOR_BLUE, COLOR_BLUE, A_BOLD);
88 	make_color (CLR_MENU_ITEM_INFO_SELECTED, COLOR_BLUE, COLOR_BLACK, A_BOLD);
89 	make_color (CLR_MENU_ITEM_INFO_MARKED, COLOR_BLUE, COLOR_BLUE, A_BOLD);
90 	make_color (CLR_MENU_ITEM_INFO_MARKED_SELECTED, COLOR_BLUE, COLOR_BLACK, A_BOLD);
91 	make_color (CLR_STATUS, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
92 	make_color (CLR_TITLE, COLOR_WHITE, COLOR_BLUE, A_BOLD);
93 	make_color (CLR_STATE, COLOR_WHITE, COLOR_BLUE, A_BOLD);
94 	make_color (CLR_TIME_CURRENT, COLOR_WHITE, COLOR_BLUE, A_BOLD);
95 	make_color (CLR_TIME_LEFT, COLOR_WHITE, COLOR_BLUE, A_BOLD);
96 	make_color (CLR_TIME_TOTAL_FRAMES, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
97 	make_color (CLR_TIME_TOTAL, COLOR_WHITE, COLOR_BLUE, A_BOLD);
98 	make_color (CLR_SOUND_PARAMS, COLOR_WHITE, COLOR_BLUE, A_BOLD);
99 	make_color (CLR_LEGEND, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
100 	make_color (CLR_INFO_DISABLED, COLOR_BLUE, COLOR_BLUE, A_BOLD);
101 	make_color (CLR_INFO_ENABLED, COLOR_WHITE, COLOR_BLUE, A_BOLD);
102 	make_color (CLR_MIXER_BAR_EMPTY, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
103 	make_color (CLR_MIXER_BAR_FILL, COLOR_BLACK, COLOR_CYAN, A_NORMAL);
104 	make_color (CLR_TIME_BAR_EMPTY, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
105 	make_color (CLR_TIME_BAR_FILL, COLOR_BLACK, COLOR_CYAN, A_NORMAL);
106 	make_color (CLR_ENTRY, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
107 	make_color (CLR_ENTRY_TITLE, COLOR_BLACK, COLOR_CYAN, A_BOLD);
108 	make_color (CLR_ERROR, COLOR_RED, COLOR_BLUE, A_BOLD);
109 	make_color (CLR_MESSAGE, COLOR_GREEN, COLOR_BLUE, A_BOLD);
110 	make_color (CLR_PLIST_TIME, COLOR_WHITE, COLOR_BLUE, A_NORMAL);
111 }
112 
113 /* Set default colors for black and white terminal. */
set_bw_colors()114 static void set_bw_colors ()
115 {
116 	colors[CLR_BACKGROUND] = A_NORMAL;
117 	colors[CLR_FRAME] = A_NORMAL;
118 	colors[CLR_WIN_TITLE] = A_NORMAL;
119 	colors[CLR_MENU_ITEM_DIR] = A_NORMAL;
120 	colors[CLR_MENU_ITEM_DIR_SELECTED] = A_REVERSE;
121 	colors[CLR_MENU_ITEM_PLAYLIST] = A_NORMAL;
122 	colors[CLR_MENU_ITEM_PLAYLIST_SELECTED] = A_REVERSE;
123 	colors[CLR_MENU_ITEM_FILE] = A_NORMAL;
124 	colors[CLR_MENU_ITEM_FILE_SELECTED] = A_REVERSE;
125 	colors[CLR_MENU_ITEM_FILE_MARKED] = A_BOLD;
126 	colors[CLR_MENU_ITEM_FILE_MARKED_SELECTED] = A_BOLD | A_REVERSE;
127 	colors[CLR_MENU_ITEM_INFO] = A_NORMAL;
128 	colors[CLR_MENU_ITEM_INFO_SELECTED] = A_REVERSE;
129 	colors[CLR_MENU_ITEM_INFO_MARKED] = A_BOLD;
130 	colors[CLR_MENU_ITEM_INFO_MARKED_SELECTED] = A_BOLD | A_REVERSE;
131 	colors[CLR_STATUS] = A_NORMAL;
132 	colors[CLR_TITLE] = A_BOLD;
133 	colors[CLR_STATE] = A_BOLD;
134 	colors[CLR_TIME_CURRENT] = A_BOLD;
135 	colors[CLR_TIME_LEFT] = A_BOLD;
136 	colors[CLR_TIME_TOTAL_FRAMES] = A_NORMAL;
137 	colors[CLR_TIME_TOTAL] = A_BOLD;
138 	colors[CLR_SOUND_PARAMS] = A_BOLD;
139 	colors[CLR_LEGEND] = A_NORMAL;
140 	colors[CLR_INFO_DISABLED] = A_BOLD;
141 	colors[CLR_INFO_ENABLED] = A_BOLD;
142 	colors[CLR_MIXER_BAR_EMPTY] = A_NORMAL;
143 	colors[CLR_MIXER_BAR_FILL] = A_REVERSE;
144 	colors[CLR_TIME_BAR_EMPTY] = A_NORMAL;
145 	colors[CLR_TIME_BAR_FILL] = A_REVERSE;
146 	colors[CLR_ENTRY] = A_NORMAL;
147 	colors[CLR_ENTRY_TITLE] = A_BOLD;
148 	colors[CLR_ERROR] = A_BOLD;
149 	colors[CLR_MESSAGE] = A_BOLD;
150 	colors[CLR_PLIST_TIME] = A_NORMAL;
151 }
152 
theme_parse_error(const int line,const char * msg)153 static void theme_parse_error (const int line, const char *msg)
154 {
155 	interface_fatal ("Parse error in theme file line %d: %s", line, msg);
156 }
157 
158 /* Find the index of a color element by name. Return CLR_WRONG if not found. */
find_color_element_name(const char * name)159 static enum color_index find_color_element_name (const char *name)
160 {
161 	size_t ix;
162 	static struct
163 	{
164 		char *name;
165 		enum color_index idx;
166 	} color_tab[] = {
167 		{ "background",		CLR_BACKGROUND },
168 		{ "frame",		CLR_FRAME },
169 		{ "window_title",	CLR_WIN_TITLE },
170 		{ "directory",		CLR_MENU_ITEM_DIR },
171 		{ "selected_directory", CLR_MENU_ITEM_DIR_SELECTED },
172 		{ "playlist",		CLR_MENU_ITEM_PLAYLIST },
173 		{ "selected_playlist",	CLR_MENU_ITEM_PLAYLIST_SELECTED },
174 		{ "file",		CLR_MENU_ITEM_FILE },
175 		{ "selected_file",	CLR_MENU_ITEM_FILE_SELECTED },
176 		{ "marked_file",	CLR_MENU_ITEM_FILE_MARKED },
177 		{ "marked_selected_file", CLR_MENU_ITEM_FILE_MARKED_SELECTED },
178 		{ "info",		CLR_MENU_ITEM_INFO },
179 		{ "selected_info",	CLR_MENU_ITEM_INFO_SELECTED },
180 		{ "marked_info",	CLR_MENU_ITEM_INFO_MARKED },
181 		{ "marked_selected_info", CLR_MENU_ITEM_INFO_MARKED_SELECTED },
182 		{ "status",		CLR_STATUS },
183 		{ "title",		CLR_TITLE },
184 		{ "state",		CLR_STATE },
185 		{ "current_time",	CLR_TIME_CURRENT },
186 		{ "time_left",		CLR_TIME_LEFT },
187 		{ "total_time",		CLR_TIME_TOTAL },
188 		{ "time_total_frames",	CLR_TIME_TOTAL_FRAMES },
189 		{ "sound_parameters",	CLR_SOUND_PARAMS },
190 		{ "legend",		CLR_LEGEND },
191 		{ "disabled",		CLR_INFO_DISABLED },
192 		{ "enabled",		CLR_INFO_ENABLED },
193 		{ "empty_mixer_bar",	CLR_MIXER_BAR_EMPTY },
194 		{ "filled_mixer_bar",	CLR_MIXER_BAR_FILL },
195 		{ "empty_time_bar",	CLR_TIME_BAR_EMPTY },
196 		{ "filled_time_bar",	CLR_TIME_BAR_FILL },
197 		{ "entry",		CLR_ENTRY },
198 		{ "entry_title",	CLR_ENTRY_TITLE },
199 		{ "error",		CLR_ERROR },
200 		{ "message",		CLR_MESSAGE },
201 		{ "plist_time",		CLR_PLIST_TIME }
202 	};
203 
204 	assert (name != NULL);
205 
206 	for (ix = 0; ix < ARRAY_SIZE(color_tab); ix += 1) {
207 		if (!strcasecmp(color_tab[ix].name, name))
208 			return color_tab[ix].idx;
209 	}
210 
211 	return CLR_WRONG;
212 }
213 
214 /* Find the curses color by name. Return -1 if the color is unknown. */
find_color_name(const char * name)215 static short find_color_name (const char *name)
216 {
217 	size_t ix;
218 	static struct
219 	{
220 		char *name;
221 		short color;
222 	} color_tab[] = {
223 		{ "black",	COLOR_BLACK },
224 		{ "red",	COLOR_RED },
225 		{ "green",	COLOR_GREEN },
226 		{ "yellow",	COLOR_YELLOW },
227 		{ "blue",	COLOR_BLUE },
228 		{ "magenta",	COLOR_MAGENTA },
229 		{ "cyan",	COLOR_CYAN },
230 		{ "white",	COLOR_WHITE },
231 		{ "default",	COLOR_DEFAULT },
232 		{ "grey",	COLOR_GREY }
233 	};
234 
235 	for (ix = 0; ix < ARRAY_SIZE(color_tab); ix += 1) {
236 		if (!strcasecmp(color_tab[ix].name, name))
237 			return color_tab[ix].color;
238 	}
239 
240 	return -1;
241 }
242 
new_colordef(const int line_num,const char * name,const short red,const short green,const short blue,const int errors_are_fatal)243 static int new_colordef (const int line_num, const char *name, const short red,
244 		const short green, const short blue, const int errors_are_fatal)
245 {
246 	short color = find_color_name (name);
247 
248 	if (color == -1) {
249 		if (errors_are_fatal)
250 			theme_parse_error (line_num, "bad color name");
251 		return 0;
252 	}
253 	if (can_change_color())
254 		init_color (color, red, green, blue);
255 
256 	return 1;
257 }
258 
259 /* Find path to the theme for the given name. Returned memory is static. */
find_theme_file(const char * name)260 static char *find_theme_file (const char *name)
261 {
262 	static char path[PATH_MAX];
263 
264 	path[sizeof(path)-1] = 0;
265 	if (name[0] == '/') {
266 
267 		/* Absolute path */
268 		strncpy (path, name, sizeof(path));
269 		if (path[sizeof(path)-1])
270 			interface_fatal ("Theme path too long!");
271 		return path;
272 	}
273 
274 	/* Try the user directory */
275 	if (snprintf(path, sizeof(path), "%s/%s", create_file_name("themes"),
276 				name) >= (int)sizeof(path))
277 		interface_fatal ("Theme path too long!");
278 	if (file_exists(path))
279 		return path;
280 
281 	/* Try the system directory */
282 	if (snprintf(path, sizeof(path), "%s/%s", SYSTEM_THEMES_DIR,
283 				name) >= (int)sizeof(path))
284 		interface_fatal ("Theme path too long!");
285 	if (file_exists(path))
286 		return path;
287 
288 	/* File related to the current directory? */
289 	strncpy (path, name, sizeof(path));
290 	if (path[sizeof(path)-1])
291 		interface_fatal ("Theme path too long!");
292 	return path;
293 }
294 
295 /* Parse a theme element line. strtok() should be already invoked and consumed
296  * the element name.
297  * On error: if errors_are_fatal is true,
298  * theme_parse_error() is invoked, otherwise 0 is returned. */
parse_theme_element(const int line_num,const char * name,const int errors_are_fatal)299 static int parse_theme_element (const int line_num, const char *name,
300 		const int errors_are_fatal)
301 {
302 	char *tmp;
303 	char *foreground, *background, *attributes;
304 	attr_t curses_attr = 0;
305 	enum color_index element;
306 	short clr_fore, clr_back;
307 
308 	if (!(tmp = strtok(NULL, " \t")) || strcmp(tmp, "=")) {
309 		if (errors_are_fatal)
310 			theme_parse_error (line_num, "expected '='");
311 		return 0;
312 	}
313 	if (!(foreground = strtok(NULL, " \t"))) {
314 		if (errors_are_fatal)
315 			theme_parse_error (line_num,
316 					"foreground color not specified");
317 		return 0;
318 	}
319 	if (!(background = strtok(NULL, " \t"))) {
320 		if (errors_are_fatal)
321 			theme_parse_error (line_num,
322 					"background color not specified");
323 		return 0;
324 	}
325 	if ((attributes = strtok(NULL, " \t"))) {
326 		char *attr;
327 
328 		if ((tmp = strtok(NULL, " \t"))) {
329 			if (errors_are_fatal)
330 				theme_parse_error (line_num,
331 					"unexpected chars at the end of line");
332 			return 0;
333 		}
334 
335 		attr = strtok (attributes, ",");
336 
337 		do {
338 			if (!strcasecmp(attr, "normal"))
339 				curses_attr |= A_NORMAL;
340 			else if (!strcasecmp(attr, "standout"))
341 				curses_attr |= A_STANDOUT;
342 			else if (!strcasecmp(attr, "underline"))
343 				curses_attr |= A_UNDERLINE;
344 			else if (!strcasecmp(attr, "reverse"))
345 				curses_attr |= A_REVERSE;
346 			else if (!strcasecmp(attr, "blink"))
347 				curses_attr |= A_BLINK;
348 			else if (!strcasecmp(attr, "dim"))
349 				curses_attr |= A_DIM;
350 			else if (!strcasecmp(attr, "bold"))
351 				curses_attr |= A_BOLD;
352 			else if (!strcasecmp(attr, "protect"))
353 				curses_attr |= A_PROTECT;
354 			else {
355 				if (errors_are_fatal)
356 					theme_parse_error (line_num,
357 						"unknown attribute");
358 				return 0;
359 			}
360 		} while ((attr = strtok(NULL, ",")));
361 	}
362 
363 	if ((element = find_color_element_name(name)) == CLR_WRONG) {
364 		if (errors_are_fatal)
365 			theme_parse_error (line_num, "unknown element");
366 		return 0;
367 	}
368 	if ((clr_fore = find_color_name(foreground)) == -1) {
369 		if (errors_are_fatal)
370 			theme_parse_error (line_num,
371 					"bad foreground color name");
372 		return 0;
373 	}
374 	if ((clr_back = find_color_name(background)) == -1) {
375 		if (errors_are_fatal)
376 			theme_parse_error (line_num,
377 					"bad background color name");
378 		return 0;
379 	}
380 
381 	make_color (element, clr_fore, clr_back, curses_attr);
382 
383 	return 1;
384 }
385 
386 /* Parse a color value. strtok() should be already invoked and should "point"
387  * to the number. If errors_are_fatal, use theme_parse_error() on error,
388  * otherwise return -1. */
parse_rgb_color_value(const int line_num,const int errors_are_fatal)389 static short parse_rgb_color_value (const int line_num,
390 		const int errors_are_fatal)
391 {
392 	char *tmp;
393 	char *end;
394 	long color;
395 
396 	if (!(tmp = strtok(NULL, " \t"))) {
397 		if (errors_are_fatal)
398 			theme_parse_error (line_num, "3 color values expected");
399 		return -1;
400 	}
401 	color = strtol (tmp, &end, 10);
402 	if (*end) {
403 		if (errors_are_fatal)
404 			theme_parse_error (line_num,
405 					"color value is not a valid number");
406 		return -1;
407 	}
408 	if (!RANGE(0, color, 1000)) {
409 		if (errors_are_fatal)
410 			theme_parse_error (line_num,
411 					"color value should be in range 0-1000");
412 		return -1;
413 	}
414 
415 	return color;
416 }
417 
418 /* Parse a theme color definition. strtok() should be already invoked and
419  * consumed 'colordef'. On error: if errors_are_fatal is true,
420  * theme_parse_error() is invoked, otherwise 0 is returned. */
parse_theme_colordef(const int line_num,const int errors_are_fatal)421 static int parse_theme_colordef (const int line_num,
422 		const int errors_are_fatal)
423 {
424 	char *name;
425 	char *tmp;
426 	short red, green, blue;
427 
428 	if (!(name = strtok(NULL, " \t"))) {
429 		if (errors_are_fatal)
430 			theme_parse_error (line_num, "expected color name");
431 		return 0;
432 	}
433 	if (!(tmp = strtok(NULL, " \t")) || strcmp(tmp, "=")) {
434 		if (errors_are_fatal)
435 			theme_parse_error (line_num, "expected '='");
436 		return 0;
437 	}
438 
439 	red = parse_rgb_color_value (line_num, errors_are_fatal);
440 	green = parse_rgb_color_value (line_num, errors_are_fatal);
441 	blue = parse_rgb_color_value (line_num, errors_are_fatal);
442 	if (red == -1 || green == -1 || blue == -1)
443 		return 0;
444 
445 	if (!new_colordef(line_num, name, red, green, blue, errors_are_fatal))
446 		return 0;
447 
448 	return 1;
449 }
450 
451 /* The lines should be in format:
452  *
453  * ELEMENT = FOREGROUND BACKGROUND [ATTRIBUTE[,ATTRIBUTE,..]]
454  * or:
455  * colordef COLORNAME = RED GREEN BLUE
456  *
457  * Blank lines and beginning with # are ignored, see example_theme.
458  *
459  * On error: if errors_are_fatal is true, interface_fatal() is invoked,
460  * otherwise 0 is returned. */
parse_theme_line(const int line_num,char * line,const int errors_are_fatal)461 static int parse_theme_line (const int line_num, char *line,
462 		const int errors_are_fatal)
463 {
464 	char *name;
465 
466 	if (line[0] == '#' || !(name = strtok(line, " \t"))) {
467 
468 		/* empty line or a comment */
469 		return 1;
470 	}
471 
472 	if (!strcasecmp(name, "colordef"))
473 		return parse_theme_colordef (line_num, errors_are_fatal);
474 	return parse_theme_element (line_num, name, errors_are_fatal);
475 }
476 
477 /* Load a color theme. If errors_are_fatal is true, errors cause
478  * interface_fatal(), otherwise 0 is returned on error. */
load_color_theme(const char * name,const int errors_are_fatal)479 static int load_color_theme (const char *name, const int errors_are_fatal)
480 {
481 	FILE *file;
482 	char *line;
483 	int result = 1;
484 	int line_num = 0;
485 	char *theme_file = find_theme_file (name);
486 
487 	if (!(file = fopen(theme_file, "r"))) {
488 		if (errors_are_fatal)
489 			interface_fatal ("Can't open theme file: %s", strerror(errno));
490 		return 0;
491 	}
492 
493 	while (result && (line = read_line (file))) {
494 		line_num++;
495 		result = parse_theme_line (line_num, line, errors_are_fatal);
496 		free (line);
497 	}
498 
499 	fclose (file);
500 
501 	return result;
502 }
503 
reset_colors_table()504 static void reset_colors_table ()
505 {
506 	int i;
507 
508 	pair_count = 1;
509 	for (i = 0; i < CLR_LAST; i++)
510 		colors[i] = -1;
511 }
512 
theme_init(bool has_xterm)513 void theme_init (bool has_xterm)
514 {
515 	reset_colors_table ();
516 
517 	if (has_colors ()) {
518 		char *file;
519 
520 		if ((file = options_get_str ("ForceTheme"))) {
521 			load_color_theme (file, 1);
522 			strncpy (current_theme, find_theme_file (file), PATH_MAX);
523 		}
524 		else if (has_xterm && (file = options_get_str ("XTermTheme"))) {
525 			load_color_theme (file, 1);
526 			strncpy (current_theme, find_theme_file (file), PATH_MAX);
527 		}
528 		else if ((file = options_get_str ("Theme"))) {
529 			load_color_theme (file, 1);
530 			strncpy (current_theme, find_theme_file (file), PATH_MAX);
531 		}
532 		else
533 			snprintf (current_theme, PATH_MAX, "%s/example_theme",
534 			                                   SYSTEM_THEMES_DIR);
535 
536 		set_default_colors ();
537 	}
538 	else
539 		set_bw_colors ();
540 }
541 
get_color(const enum color_index index)542 int get_color (const enum color_index index)
543 {
544 	return colors[index];
545 }
546 
themes_switch_theme(const char * file)547 void themes_switch_theme (const char *file)
548 {
549 	if (has_colors()) {
550 		reset_colors_table ();
551 		if (!load_color_theme(file, 0)) {
552 			interface_error ("Error loading theme!");
553 			reset_colors_table ();
554 		}
555 		else
556 			strncpy (current_theme, file, PATH_MAX);
557 
558 		set_default_colors ();
559 	}
560 }
561 
get_current_theme()562 const char *get_current_theme ()
563 {
564 	return current_theme;
565 }
566