1 /*
2  * theme.c
3  * vim: expandtab:ts=4:sts=4:sw=4
4  *
5  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
6  * Copyright (C) 2019 - 2021 Michael Vetter <jubalh@iodoru.org>
7  *
8  * This file is part of Profanity.
9  *
10  * Profanity is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * Profanity is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
22  *
23  * In addition, as a special exception, the copyright holders give permission to
24  * link the code of portions of this program with the OpenSSL library under
25  * certain conditions as described in each individual source file, and
26  * distribute linked combinations including the two.
27  *
28  * You must obey the GNU General Public License in all respects for all of the
29  * code used other than OpenSSL. If you modify file(s) with this exception, you
30  * may extend this exception to your version of the file(s), but you are not
31  * obligated to do so. If you do not wish to do so, delete this exception
32  * statement from your version. If you delete this exception statement from all
33  * source files in the program, then also delete it here.
34  *
35  */
36 
37 #include "config.h"
38 
39 #include <stdlib.h>
40 #include <string.h>
41 
42 #include <glib.h>
43 
44 #ifdef HAVE_NCURSESW_NCURSES_H
45 #include <ncursesw/ncurses.h>
46 #elif HAVE_NCURSES_H
47 #include <ncurses.h>
48 #elif HAVE_CURSES_H
49 #include <curses.h>
50 #endif
51 
52 #include "common.h"
53 #include "log.h"
54 #include "config/files.h"
55 #include "config/theme.h"
56 #include "config/preferences.h"
57 #include "config/color.h"
58 
59 static GString* theme_loc;
60 static GKeyFile* theme;
61 static GHashTable* bold_items;
62 static GHashTable* defaults;
63 
64 static void _load_preferences(void);
65 static void _theme_list_dir(const gchar* const dir, GSList** result);
66 static GString* _theme_find(const char* const theme_name);
67 static gboolean _theme_load_file(const char* const theme_name);
68 
69 void
theme_init(const char * const theme_name)70 theme_init(const char* const theme_name)
71 {
72     if (!_theme_load_file(theme_name)) {
73         log_error("Loading theme %s failed.", theme_name);
74 
75         if (!_theme_load_file("default")) {
76             log_error("Theme initialisation failed.");
77         }
78     }
79 
80     defaults = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
81 
82     // Set default colors
83     g_hash_table_insert(defaults, strdup("main.text"), strdup("default"));
84     g_hash_table_insert(defaults, strdup("main.text.history"), strdup("default"));
85     g_hash_table_insert(defaults, strdup("main.text.me"), strdup("default"));
86     g_hash_table_insert(defaults, strdup("main.text.them"), strdup("default"));
87     g_hash_table_insert(defaults, strdup("main.splash"), strdup("cyan"));
88     g_hash_table_insert(defaults, strdup("main.help.header"), strdup("default"));
89     g_hash_table_insert(defaults, strdup("main.trackbar"), strdup("default"));
90     g_hash_table_insert(defaults, strdup("error"), strdup("red"));
91     g_hash_table_insert(defaults, strdup("incoming"), strdup("yellow"));
92     g_hash_table_insert(defaults, strdup("mention"), strdup("yellow"));
93     g_hash_table_insert(defaults, strdup("trigger"), strdup("yellow"));
94     g_hash_table_insert(defaults, strdup("input.text"), strdup("default"));
95     g_hash_table_insert(defaults, strdup("main.time"), strdup("default"));
96     g_hash_table_insert(defaults, strdup("titlebar.text"), strdup("white"));
97     g_hash_table_insert(defaults, strdup("titlebar.brackets"), strdup("cyan"));
98     g_hash_table_insert(defaults, strdup("titlebar.unencrypted"), strdup("red"));
99     g_hash_table_insert(defaults, strdup("titlebar.encrypted"), strdup("white"));
100     g_hash_table_insert(defaults, strdup("titlebar.untrusted"), strdup("yellow"));
101     g_hash_table_insert(defaults, strdup("titlebar.trusted"), strdup("white"));
102     g_hash_table_insert(defaults, strdup("titlebar.online"), strdup("white"));
103     g_hash_table_insert(defaults, strdup("titlebar.offline"), strdup("white"));
104     g_hash_table_insert(defaults, strdup("titlebar.away"), strdup("white"));
105     g_hash_table_insert(defaults, strdup("titlebar.chat"), strdup("white"));
106     g_hash_table_insert(defaults, strdup("titlebar.dnd"), strdup("white"));
107     g_hash_table_insert(defaults, strdup("titlebar.xa"), strdup("white"));
108     g_hash_table_insert(defaults, strdup("titlebar.scrolled"), strdup("default"));
109     g_hash_table_insert(defaults, strdup("statusbar.text"), strdup("white"));
110     g_hash_table_insert(defaults, strdup("statusbar.brackets"), strdup("cyan"));
111     g_hash_table_insert(defaults, strdup("statusbar.active"), strdup("cyan"));
112     g_hash_table_insert(defaults, strdup("statusbar.current"), strdup("cyan"));
113     g_hash_table_insert(defaults, strdup("statusbar.new"), strdup("white"));
114     g_hash_table_insert(defaults, strdup("statusbar.time"), strdup("white"));
115     g_hash_table_insert(defaults, strdup("me"), strdup("yellow"));
116     g_hash_table_insert(defaults, strdup("them"), strdup("green"));
117     g_hash_table_insert(defaults, strdup("receipt.sent"), strdup("red"));
118     g_hash_table_insert(defaults, strdup("roominfo"), strdup("yellow"));
119     g_hash_table_insert(defaults, strdup("roommention"), strdup("yellow"));
120     g_hash_table_insert(defaults, strdup("roommention.term"), strdup("yellow"));
121     g_hash_table_insert(defaults, strdup("roomtrigger"), strdup("yellow"));
122     g_hash_table_insert(defaults, strdup("roomtrigger.term"), strdup("yellow"));
123     g_hash_table_insert(defaults, strdup("online"), strdup("green"));
124     g_hash_table_insert(defaults, strdup("offline"), strdup("red"));
125     g_hash_table_insert(defaults, strdup("away"), strdup("cyan"));
126     g_hash_table_insert(defaults, strdup("chat"), strdup("green"));
127     g_hash_table_insert(defaults, strdup("dnd"), strdup("red"));
128     g_hash_table_insert(defaults, strdup("xa"), strdup("cyan"));
129     g_hash_table_insert(defaults, strdup("typing"), strdup("yellow"));
130     g_hash_table_insert(defaults, strdup("gone"), strdup("red"));
131     g_hash_table_insert(defaults, strdup("subscribed"), strdup("green"));
132     g_hash_table_insert(defaults, strdup("unsubscribed"), strdup("red"));
133     g_hash_table_insert(defaults, strdup("otr.started.trusted"), strdup("green"));
134     g_hash_table_insert(defaults, strdup("otr.started.untrusted"), strdup("yellow"));
135     g_hash_table_insert(defaults, strdup("otr.ended"), strdup("red"));
136     g_hash_table_insert(defaults, strdup("otr.trusted"), strdup("green"));
137     g_hash_table_insert(defaults, strdup("otr.untrusted"), strdup("yellow"));
138     g_hash_table_insert(defaults, strdup("roster.header"), strdup("yellow"));
139     g_hash_table_insert(defaults, strdup("roster.online"), strdup("green"));
140     g_hash_table_insert(defaults, strdup("roster.offline"), strdup("red"));
141     g_hash_table_insert(defaults, strdup("roster.chat"), strdup("green"));
142     g_hash_table_insert(defaults, strdup("roster.away"), strdup("cyan"));
143     g_hash_table_insert(defaults, strdup("roster.dnd"), strdup("red"));
144     g_hash_table_insert(defaults, strdup("roster.xa"), strdup("cyan"));
145     g_hash_table_insert(defaults, strdup("roster.online.active"), strdup("green"));
146     g_hash_table_insert(defaults, strdup("roster.offline.active"), strdup("red"));
147     g_hash_table_insert(defaults, strdup("roster.chat.active"), strdup("green"));
148     g_hash_table_insert(defaults, strdup("roster.away.active"), strdup("cyan"));
149     g_hash_table_insert(defaults, strdup("roster.dnd.active"), strdup("red"));
150     g_hash_table_insert(defaults, strdup("roster.xa.active"), strdup("cyan"));
151     g_hash_table_insert(defaults, strdup("roster.online.unread"), strdup("green"));
152     g_hash_table_insert(defaults, strdup("roster.offline.unread"), strdup("red"));
153     g_hash_table_insert(defaults, strdup("roster.chat.unread"), strdup("green"));
154     g_hash_table_insert(defaults, strdup("roster.away.unread"), strdup("cyan"));
155     g_hash_table_insert(defaults, strdup("roster.dnd.unread"), strdup("red"));
156     g_hash_table_insert(defaults, strdup("roster.xa.unread"), strdup("cyan"));
157     g_hash_table_insert(defaults, strdup("roster.room"), strdup("green"));
158     g_hash_table_insert(defaults, strdup("roster.room.unread"), strdup("green"));
159     g_hash_table_insert(defaults, strdup("roster.room.trigger"), strdup("green"));
160     g_hash_table_insert(defaults, strdup("roster.room.mention"), strdup("green"));
161     g_hash_table_insert(defaults, strdup("occupants.header"), strdup("yellow"));
162     g_hash_table_insert(defaults, strdup("untrusted"), strdup("red"));
163     g_hash_table_insert(defaults, strdup("cmd.wins.unread"), strdup("default"));
164 
165     //_load_preferences();
166 }
167 
168 gboolean
theme_exists(const char * const theme_name)169 theme_exists(const char* const theme_name)
170 {
171     if (g_strcmp0(theme_name, "default") == 0) {
172         return TRUE;
173     }
174 
175     GString* new_theme_file = _theme_find(theme_name);
176     if (new_theme_file == NULL) {
177         return FALSE;
178     }
179 
180     g_string_free(new_theme_file, TRUE);
181     return TRUE;
182 }
183 
184 gboolean
theme_load(const char * const theme_name,gboolean load_theme_prefs)185 theme_load(const char* const theme_name, gboolean load_theme_prefs)
186 {
187     color_pair_cache_reset();
188 
189     if (_theme_load_file(theme_name)) {
190         if (load_theme_prefs) {
191             _load_preferences();
192         }
193         return TRUE;
194     } else {
195         return FALSE;
196     }
197 }
198 
199 static gboolean
_theme_load_file(const char * const theme_name)200 _theme_load_file(const char* const theme_name)
201 {
202     // use default theme
203     if (theme_name == NULL || strcmp(theme_name, "default") == 0) {
204         if (theme) {
205             g_key_file_free(theme);
206         }
207         theme = g_key_file_new();
208 
209         // load theme from file
210     } else {
211         GString* new_theme_file = _theme_find(theme_name);
212         if (new_theme_file == NULL) {
213             log_info("Theme does not exist \"%s\"", theme_name);
214             return FALSE;
215         }
216 
217         if (theme_loc) {
218             g_string_free(theme_loc, TRUE);
219         }
220         theme_loc = new_theme_file;
221         log_info("Loading theme \"%s\"", theme_name);
222         if (theme) {
223             g_key_file_free(theme);
224         }
225         theme = g_key_file_new();
226         g_key_file_load_from_file(theme, theme_loc->str, G_KEY_FILE_KEEP_COMMENTS,
227                                   NULL);
228     }
229 
230     return TRUE;
231 }
232 
233 GSList*
theme_list(void)234 theme_list(void)
235 {
236     GSList* result = NULL;
237     gchar* themes_dir = files_get_config_path(DIR_THEMES);
238 
239     _theme_list_dir(themes_dir, &result);
240     g_free(themes_dir);
241 
242 #ifdef THEMES_PATH
243     _theme_list_dir(THEMES_PATH, &result);
244 #endif
245 
246     return result;
247 }
248 
249 void
theme_close(void)250 theme_close(void)
251 {
252     if (theme) {
253         g_key_file_free(theme);
254         theme = NULL;
255     }
256     if (theme_loc) {
257         g_string_free(theme_loc, TRUE);
258         theme_loc = NULL;
259     }
260     if (bold_items) {
261         g_hash_table_destroy(bold_items);
262         bold_items = NULL;
263     }
264     if (defaults) {
265         g_hash_table_destroy(defaults);
266         defaults = NULL;
267     }
268 }
269 
270 void
theme_init_colours(void)271 theme_init_colours(void)
272 {
273     assume_default_colors(-1, -1);
274     color_pair_cache_reset();
275 }
276 
277 static void
_set_string_preference(char * prefstr,preference_t pref)278 _set_string_preference(char* prefstr, preference_t pref)
279 {
280     if (g_key_file_has_key(theme, "ui", prefstr, NULL)) {
281         gchar* val = g_key_file_get_string(theme, "ui", prefstr, NULL);
282         prefs_set_string(pref, val);
283         g_free(val);
284     }
285 }
286 
287 static void
_set_boolean_preference(char * prefstr,preference_t pref)288 _set_boolean_preference(char* prefstr, preference_t pref)
289 {
290     if (g_key_file_has_key(theme, "ui", prefstr, NULL)) {
291         gboolean val = g_key_file_get_boolean(theme, "ui", prefstr, NULL);
292         prefs_set_boolean(pref, val);
293     }
294 }
295 
296 static void
_load_preferences(void)297 _load_preferences(void)
298 {
299     // load booleans from theme and set them to prefs
300     _set_boolean_preference("beep", PREF_BEEP);
301     _set_boolean_preference("flash", PREF_FLASH);
302     _set_boolean_preference("splash", PREF_SPLASH);
303     _set_boolean_preference("wrap", PREF_WRAP);
304     _set_boolean_preference("resource.title", PREF_RESOURCE_TITLE);
305     _set_boolean_preference("resource.message", PREF_RESOURCE_MESSAGE);
306     _set_boolean_preference("occupants", PREF_OCCUPANTS);
307     _set_boolean_preference("occupants.jid", PREF_OCCUPANTS_JID);
308     _set_boolean_preference("occupants.offline", PREF_OCCUPANTS_OFFLINE);
309     _set_boolean_preference("occupants.wrap", PREF_OCCUPANTS_WRAP);
310     _set_boolean_preference("roster", PREF_ROSTER);
311     _set_boolean_preference("roster.offline", PREF_ROSTER_OFFLINE);
312     _set_boolean_preference("roster.resource", PREF_ROSTER_RESOURCE);
313     _set_boolean_preference("roster.resource.join", PREF_ROSTER_RESOURCE_JOIN);
314     _set_boolean_preference("roster.presence", PREF_ROSTER_PRESENCE);
315     _set_boolean_preference("roster.status", PREF_ROSTER_STATUS);
316     _set_boolean_preference("roster.empty", PREF_ROSTER_EMPTY);
317     _set_boolean_preference("roster.wrap", PREF_ROSTER_WRAP);
318     _set_boolean_preference("roster.count.zero", PREF_ROSTER_COUNT_ZERO);
319     _set_boolean_preference("roster.priority", PREF_ROSTER_PRIORITY);
320     _set_boolean_preference("roster.contacts", PREF_ROSTER_CONTACTS);
321     _set_boolean_preference("roster.unsubscribed", PREF_ROSTER_UNSUBSCRIBED);
322     _set_boolean_preference("roster.rooms", PREF_ROSTER_ROOMS);
323     _set_boolean_preference("roster.rooms.server", PREF_ROSTER_ROOMS_SERVER);
324     _set_boolean_preference("privileges", PREF_MUC_PRIVILEGES);
325     _set_boolean_preference("presence", PREF_PRESENCE);
326     _set_boolean_preference("intype", PREF_INTYPE);
327     _set_boolean_preference("enc.warn", PREF_ENC_WARN);
328     _set_boolean_preference("titlebar.muc.title.name", PREF_TITLEBAR_MUC_TITLE_NAME);
329     _set_boolean_preference("titlebar.muc.title.jid", PREF_TITLEBAR_MUC_TITLE_JID);
330     _set_boolean_preference("tls.show", PREF_TLS_SHOW);
331     _set_boolean_preference("statusbar.show.name", PREF_STATUSBAR_SHOW_NAME);
332     _set_boolean_preference("statusbar.show.number", PREF_STATUSBAR_SHOW_NUMBER);
333 
334     // load strings from theme and set them to prefs
335     _set_string_preference("time.console", PREF_TIME_CONSOLE);
336     _set_string_preference("time.chat", PREF_TIME_CHAT);
337     _set_string_preference("time.muc", PREF_TIME_MUC);
338     _set_string_preference("time.config", PREF_TIME_CONFIG);
339     _set_string_preference("time.private", PREF_TIME_PRIVATE);
340     _set_string_preference("time.xmlconsole", PREF_TIME_XMLCONSOLE);
341     _set_string_preference("time.statusbar", PREF_TIME_STATUSBAR);
342     _set_string_preference("time.lastactivity", PREF_TIME_LASTACTIVITY);
343     _set_string_preference("statuses.console", PREF_STATUSES_CONSOLE);
344     _set_string_preference("statuses.chat", PREF_STATUSES_CHAT);
345     _set_string_preference("statuses.muc", PREF_STATUSES_MUC);
346     _set_string_preference("console.muc", PREF_CONSOLE_MUC);
347     _set_string_preference("console.private", PREF_CONSOLE_PRIVATE);
348     _set_string_preference("console.chat", PREF_CONSOLE_CHAT);
349     _set_string_preference("roster.by", PREF_ROSTER_BY);
350     _set_string_preference("roster.order", PREF_ROSTER_ORDER);
351     _set_string_preference("roster.unread", PREF_ROSTER_UNREAD);
352     _set_string_preference("roster.rooms.order", PREF_ROSTER_ROOMS_ORDER);
353     _set_string_preference("roster.rooms.unread", PREF_ROSTER_ROOMS_UNREAD);
354     _set_string_preference("roster.rooms.pos", PREF_ROSTER_ROOMS_POS);
355     _set_string_preference("roster.rooms.by", PREF_ROSTER_ROOMS_BY);
356     _set_string_preference("roster.private", PREF_ROSTER_PRIVATE);
357     _set_string_preference("roster.count", PREF_ROSTER_COUNT);
358     _set_string_preference("roster.rooms.use.name", PREF_ROSTER_ROOMS_USE_AS_NAME);
359     _set_string_preference("statusbar.self", PREF_STATUSBAR_SELF);
360     _set_string_preference("statusbar.chat", PREF_STATUSBAR_CHAT);
361     _set_string_preference("statusbar.room", PREF_STATUSBAR_ROOM);
362 
363     // load ints from theme and set them to prefs
364     // with custom set functions
365     if (g_key_file_has_key(theme, "ui", "statusbar.tabs", NULL)) {
366         gint tabs_size = g_key_file_get_integer(theme, "ui", "statusbar.tabs", NULL);
367         prefs_set_statusbartabs(tabs_size);
368     }
369 
370     if (g_key_file_has_key(theme, "ui", "statusbar.tablen", NULL)) {
371         gint tab_len = g_key_file_get_integer(theme, "ui", "statusbar.tablen", NULL);
372         prefs_set_statusbartablen(tab_len);
373     }
374 
375     if (g_key_file_has_key(theme, "ui", "occupants.size", NULL)) {
376         gint occupants_size = g_key_file_get_integer(theme, "ui", "occupants.size", NULL);
377         prefs_set_occupants_size(occupants_size);
378     }
379 
380     if (g_key_file_has_key(theme, "ui", "occupants.indent", NULL)) {
381         gint occupants_indent = g_key_file_get_integer(theme, "ui", "occupants.indent", NULL);
382         prefs_set_occupants_indent(occupants_indent);
383     }
384 
385     if (g_key_file_has_key(theme, "ui", "roster.size", NULL)) {
386         gint roster_size = g_key_file_get_integer(theme, "ui", "roster.size", NULL);
387         prefs_set_roster_size(roster_size);
388     }
389 
390     if (g_key_file_has_key(theme, "ui", "roster.contact.indent", NULL)) {
391         gint contact_indent = g_key_file_get_integer(theme, "ui", "roster.contact.indent", NULL);
392         prefs_set_roster_contact_indent(contact_indent);
393     }
394 
395     if (g_key_file_has_key(theme, "ui", "roster.resource.indent", NULL)) {
396         gint resource_indent = g_key_file_get_integer(theme, "ui", "roster.resource.indent", NULL);
397         prefs_set_roster_resource_indent(resource_indent);
398     }
399 
400     if (g_key_file_has_key(theme, "ui", "roster.presence.indent", NULL)) {
401         gint presence_indent = g_key_file_get_integer(theme, "ui", "roster.presence.indent", NULL);
402         prefs_set_roster_presence_indent(presence_indent);
403     }
404 
405     // load chars from theme and set them to prefs
406     // with custom set functions
407     if (g_key_file_has_key(theme, "ui", "occupants.char", NULL)) {
408         gchar* ch = g_key_file_get_string(theme, "ui", "occupants.char", NULL);
409         if (ch && strlen(ch) > 0) {
410             prefs_set_occupants_char(ch[0]);
411             g_free(ch);
412         }
413     }
414 
415     if (g_key_file_has_key(theme, "ui", "occupants.header.char", NULL)) {
416         gchar* ch = g_key_file_get_string(theme, "ui", "occupants.header.char", NULL);
417         if (ch && strlen(ch) > 0) {
418             prefs_set_occupants_header_char(ch[0]);
419             g_free(ch);
420         }
421     }
422 
423     if (g_key_file_has_key(theme, "ui", "roster.header.char", NULL)) {
424         gchar* ch = g_key_file_get_string(theme, "ui", "roster.header.char", NULL);
425         if (ch && strlen(ch) > 0) {
426             prefs_set_roster_header_char(ch[0]);
427             g_free(ch);
428         }
429     }
430 
431     if (g_key_file_has_key(theme, "ui", "roster.contact.char", NULL)) {
432         gchar* ch = g_key_file_get_string(theme, "ui", "roster.contact.char", NULL);
433         if (ch && strlen(ch) > 0) {
434             prefs_set_roster_contact_char(ch[0]);
435             g_free(ch);
436         }
437     }
438 
439     if (g_key_file_has_key(theme, "ui", "roster.resource.char", NULL)) {
440         gchar* ch = g_key_file_get_string(theme, "ui", "roster.resource.char", NULL);
441         if (ch && strlen(ch) > 0) {
442             prefs_set_roster_resource_char(ch[0]);
443             g_free(ch);
444         }
445     } else {
446         prefs_clear_roster_resource_char();
447     }
448 
449     if (g_key_file_has_key(theme, "ui", "roster.rooms.char", NULL)) {
450         gchar* ch = g_key_file_get_string(theme, "ui", "roster.rooms.char", NULL);
451         if (ch && strlen(ch) > 0) {
452             prefs_set_roster_room_char(ch[0]);
453             g_free(ch);
454         }
455     }
456 
457     if (g_key_file_has_key(theme, "ui", "roster.rooms.private.char", NULL)) {
458         gchar* ch = g_key_file_get_string(theme, "ui", "roster.rooms.private.char", NULL);
459         if (ch && strlen(ch) > 0) {
460             prefs_set_roster_room_private_char(ch[0]);
461             g_free(ch);
462         }
463     }
464 
465     if (g_key_file_has_key(theme, "ui", "roster.private.char", NULL)) {
466         gchar* ch = g_key_file_get_string(theme, "ui", "roster.private.char", NULL);
467         if (ch && strlen(ch) > 0) {
468             prefs_set_roster_private_char(ch[0]);
469             g_free(ch);
470         }
471     }
472 
473     if (g_key_file_has_key(theme, "ui", "otr.char", NULL)) {
474         gchar* ch = g_key_file_get_string(theme, "ui", "otr.char", NULL);
475         if (ch && g_utf8_strlen(ch, 4) == 1) {
476             prefs_set_otr_char(ch);
477             g_free(ch);
478         }
479     }
480 
481     if (g_key_file_has_key(theme, "ui", "pgp.char", NULL)) {
482         gchar* ch = g_key_file_get_string(theme, "ui", "pgp.char", NULL);
483         if (ch && g_utf8_strlen(ch, 4) == 1) {
484             prefs_set_pgp_char(ch);
485             g_free(ch);
486         }
487     }
488 
489     if (g_key_file_has_key(theme, "ui", "omemo.char", NULL)) {
490         gchar* ch = g_key_file_get_string(theme, "ui", "omemo.char", NULL);
491         if (ch && g_utf8_strlen(ch, 4) == 1) {
492             prefs_set_omemo_char(ch);
493             g_free(ch);
494         }
495     }
496 
497     if (g_key_file_has_key(theme, "ui", "correction.char", NULL)) {
498         gchar* ch = g_key_file_get_string(theme, "ui", "correction.char", NULL);
499         if (ch && strlen(ch) > 0) {
500             prefs_set_correction_char(ch[0]);
501             g_free(ch);
502         }
503     }
504 
505     // load window positions
506     if (g_key_file_has_key(theme, "ui", "titlebar.position", NULL) && g_key_file_has_key(theme, "ui", "mainwin.position", NULL) && g_key_file_has_key(theme, "ui", "statusbar.position", NULL) && g_key_file_has_key(theme, "ui", "inputwin.position", NULL)) {
507 
508         int titlebar_pos = g_key_file_get_integer(theme, "ui", "titlebar.position", NULL);
509         int mainwin_pos = g_key_file_get_integer(theme, "ui", "mainwin.position", NULL);
510         int statusbar_pos = g_key_file_get_integer(theme, "ui", "statusbar.position", NULL);
511         int inputwin_pos = g_key_file_get_integer(theme, "ui", "inputwin.position", NULL);
512 
513         ProfWinPlacement* placement = prefs_create_profwin_placement(titlebar_pos, mainwin_pos, statusbar_pos, inputwin_pos);
514 
515         prefs_save_win_placement(placement);
516         prefs_free_win_placement(placement);
517     }
518 }
519 
520 static void
_theme_list_dir(const gchar * const dir,GSList ** result)521 _theme_list_dir(const gchar* const dir, GSList** result)
522 {
523     GDir* themes = g_dir_open(dir, 0, NULL);
524     if (themes) {
525         const gchar* theme = g_dir_read_name(themes);
526         while (theme) {
527             *result = g_slist_append(*result, strdup(theme));
528             theme = g_dir_read_name(themes);
529         }
530         g_dir_close(themes);
531     }
532 }
533 
534 static GString*
_theme_find(const char * const theme_name)535 _theme_find(const char* const theme_name)
536 {
537     GString* path = NULL;
538     gchar* themes_dir = files_get_config_path(DIR_THEMES);
539 
540     if (themes_dir) {
541         path = g_string_new(themes_dir);
542         g_free(themes_dir);
543         g_string_append(path, "/");
544         g_string_append(path, theme_name);
545         if (!g_file_test(path->str, G_FILE_TEST_EXISTS)) {
546             g_string_free(path, true);
547             path = NULL;
548         }
549     }
550 
551 #ifdef THEMES_PATH
552     if (path == NULL) {
553         path = g_string_new(THEMES_PATH);
554         g_string_append(path, "/");
555         g_string_append(path, theme_name);
556         if (!g_file_test(path->str, G_FILE_TEST_EXISTS)) {
557             g_string_free(path, true);
558             path = NULL;
559         }
560     }
561 #endif /* THEMES_PATH */
562 
563     return path;
564 }
565 
566 theme_item_t
theme_roster_unread_presence_attrs(const char * const presence)567 theme_roster_unread_presence_attrs(const char* const presence)
568 {
569     if (g_strcmp0(presence, "online") == 0) {
570         return THEME_ROSTER_ONLINE_UNREAD;
571     } else if (g_strcmp0(presence, "away") == 0) {
572         return THEME_ROSTER_AWAY_UNREAD;
573     } else if (g_strcmp0(presence, "chat") == 0) {
574         return THEME_ROSTER_CHAT_UNREAD;
575     } else if (g_strcmp0(presence, "dnd") == 0) {
576         return THEME_ROSTER_DND_UNREAD;
577     } else if (g_strcmp0(presence, "xa") == 0) {
578         return THEME_ROSTER_XA_UNREAD;
579     } else {
580         return THEME_ROSTER_OFFLINE_UNREAD;
581     }
582 }
583 
584 theme_item_t
theme_roster_active_presence_attrs(const char * const presence)585 theme_roster_active_presence_attrs(const char* const presence)
586 {
587     if (g_strcmp0(presence, "online") == 0) {
588         return THEME_ROSTER_ONLINE_ACTIVE;
589     } else if (g_strcmp0(presence, "away") == 0) {
590         return THEME_ROSTER_AWAY_ACTIVE;
591     } else if (g_strcmp0(presence, "chat") == 0) {
592         return THEME_ROSTER_CHAT_ACTIVE;
593     } else if (g_strcmp0(presence, "dnd") == 0) {
594         return THEME_ROSTER_DND_ACTIVE;
595     } else if (g_strcmp0(presence, "xa") == 0) {
596         return THEME_ROSTER_XA_ACTIVE;
597     } else {
598         return THEME_ROSTER_OFFLINE_ACTIVE;
599     }
600 }
601 
602 theme_item_t
theme_roster_presence_attrs(const char * const presence)603 theme_roster_presence_attrs(const char* const presence)
604 {
605     if (g_strcmp0(presence, "online") == 0) {
606         return THEME_ROSTER_ONLINE;
607     } else if (g_strcmp0(presence, "away") == 0) {
608         return THEME_ROSTER_AWAY;
609     } else if (g_strcmp0(presence, "chat") == 0) {
610         return THEME_ROSTER_CHAT;
611     } else if (g_strcmp0(presence, "dnd") == 0) {
612         return THEME_ROSTER_DND;
613     } else if (g_strcmp0(presence, "xa") == 0) {
614         return THEME_ROSTER_XA;
615     } else {
616         return THEME_ROSTER_OFFLINE;
617     }
618 }
619 
620 theme_item_t
theme_main_presence_attrs(const char * const presence)621 theme_main_presence_attrs(const char* const presence)
622 {
623     if (g_strcmp0(presence, "online") == 0) {
624         return THEME_ONLINE;
625     } else if (g_strcmp0(presence, "away") == 0) {
626         return THEME_AWAY;
627     } else if (g_strcmp0(presence, "chat") == 0) {
628         return THEME_CHAT;
629     } else if (g_strcmp0(presence, "dnd") == 0) {
630         return THEME_DND;
631     } else if (g_strcmp0(presence, "xa") == 0) {
632         return THEME_XA;
633     } else {
634         return THEME_OFFLINE;
635     }
636 }
637 
638 static void
_theme_prep_bgnd(char * setting,char * def,GString * lookup_str)639 _theme_prep_bgnd(char* setting, char* def, GString* lookup_str)
640 {
641     gchar* val = g_key_file_get_string(theme, "colours", setting, NULL);
642     if (!val) {
643         g_string_append(lookup_str, def);
644     } else {
645         if (g_str_has_prefix(val, "bold_")) {
646             g_string_append(lookup_str, &val[5]);
647         } else {
648             g_string_append(lookup_str, val);
649         }
650     }
651     g_free(val);
652 }
653 
654 /* return value needs to be freed */
655 char*
theme_get_bkgnd(void)656 theme_get_bkgnd(void)
657 {
658     char* val = g_key_file_get_string(theme, "colours", "bkgnd", NULL);
659     return val;
660 }
661 
662 /* gets the foreground color from the theme. or uses the one defined in 'defaults' */
663 static void
_theme_prep_fgnd(char * setting,GString * lookup_str,gboolean * bold)664 _theme_prep_fgnd(char* setting, GString* lookup_str, gboolean* bold)
665 {
666     gchar* val = g_key_file_get_string(theme, "colours", setting, NULL);
667     if (!val) {
668         char* def = g_hash_table_lookup(defaults, setting);
669         g_string_append(lookup_str, def);
670     } else {
671         if (g_str_has_prefix(val, "bold_")) {
672             g_string_append(lookup_str, &val[5]);
673             *bold = TRUE;
674         } else {
675             g_string_append(lookup_str, val);
676             *bold = FALSE;
677         }
678     }
679     g_free(val);
680 }
681 
682 char*
theme_get_string(char * str)683 theme_get_string(char* str)
684 {
685     char* res = g_key_file_get_string(theme, "colours", str, NULL);
686     if (!res) {
687         return strdup(g_hash_table_lookup(defaults, str));
688     } else {
689         return res;
690     }
691 }
692 
693 void
theme_free_string(char * str)694 theme_free_string(char* str)
695 {
696     if (str) {
697         g_free(str);
698     }
699 }
700 
701 int
theme_hash_attrs(const char * str)702 theme_hash_attrs(const char* str)
703 {
704     color_profile profile = COLOR_PROFILE_DEFAULT;
705 
706     char* color_pref = prefs_get_string(PREF_COLOR_NICK);
707     if (strcmp(color_pref, "redgreen") == 0) {
708         profile = COLOR_PROFILE_REDGREEN_BLINDNESS;
709     } else if (strcmp(color_pref, "blue") == 0) {
710         profile = COLOR_PROFILE_BLUE_BLINDNESS;
711     }
712     g_free(color_pref);
713 
714     return COLOR_PAIR(color_pair_cache_hash_str(str, profile));
715 }
716 
717 /* returns the colours (fgnd and bknd) for a certain attribute ie main.text */
718 int
theme_attrs(theme_item_t attrs)719 theme_attrs(theme_item_t attrs)
720 {
721     int result = 0;
722 
723     GString* lookup_str = g_string_new("");
724     gboolean bold = FALSE;
725 
726     // get foreground colour
727     switch (attrs) {
728     case THEME_TEXT:
729         _theme_prep_fgnd("main.text", lookup_str, &bold);
730         break;
731     case THEME_TEXT_HISTORY:
732         _theme_prep_fgnd("main.text.history", lookup_str, &bold);
733         break;
734     case THEME_TEXT_ME:
735         _theme_prep_fgnd("main.text.me", lookup_str, &bold);
736         break;
737     case THEME_TEXT_THEM:
738         _theme_prep_fgnd("main.text.them", lookup_str, &bold);
739         break;
740     case THEME_SPLASH:
741         _theme_prep_fgnd("main.splash", lookup_str, &bold);
742         break;
743     case THEME_TRACKBAR:
744         _theme_prep_fgnd("main.trackbar", lookup_str, &bold);
745         break;
746     case THEME_HELP_HEADER:
747         _theme_prep_fgnd("main.help.header", lookup_str, &bold);
748         break;
749     case THEME_ERROR:
750         _theme_prep_fgnd("error", lookup_str, &bold);
751         break;
752     case THEME_INCOMING:
753         _theme_prep_fgnd("incoming", lookup_str, &bold);
754         break;
755     case THEME_MENTION:
756         _theme_prep_fgnd("mention", lookup_str, &bold);
757         break;
758     case THEME_TRIGGER:
759         _theme_prep_fgnd("trigger", lookup_str, &bold);
760         break;
761     case THEME_INPUT_TEXT:
762         _theme_prep_fgnd("input.text", lookup_str, &bold);
763         break;
764     case THEME_TIME:
765         _theme_prep_fgnd("main.time", lookup_str, &bold);
766         break;
767     case THEME_TITLE_TEXT:
768         _theme_prep_fgnd("titlebar.text", lookup_str, &bold);
769         break;
770     case THEME_TITLE_BRACKET:
771         _theme_prep_fgnd("titlebar.brackets", lookup_str, &bold);
772         break;
773     case THEME_TITLE_SCROLLED:
774         _theme_prep_fgnd("titlebar.scrolled", lookup_str, &bold);
775         break;
776     case THEME_TITLE_UNENCRYPTED:
777         _theme_prep_fgnd("titlebar.unencrypted", lookup_str, &bold);
778         break;
779     case THEME_TITLE_ENCRYPTED:
780         _theme_prep_fgnd("titlebar.encrypted", lookup_str, &bold);
781         break;
782     case THEME_TITLE_UNTRUSTED:
783         _theme_prep_fgnd("titlebar.untrusted", lookup_str, &bold);
784         break;
785     case THEME_TITLE_TRUSTED:
786         _theme_prep_fgnd("titlebar.trusted", lookup_str, &bold);
787         break;
788     case THEME_TITLE_ONLINE:
789         _theme_prep_fgnd("titlebar.online", lookup_str, &bold);
790         break;
791     case THEME_TITLE_OFFLINE:
792         _theme_prep_fgnd("titlebar.offline", lookup_str, &bold);
793         break;
794     case THEME_TITLE_AWAY:
795         _theme_prep_fgnd("titlebar.away", lookup_str, &bold);
796         break;
797     case THEME_TITLE_CHAT:
798         _theme_prep_fgnd("titlebar.chat", lookup_str, &bold);
799         break;
800     case THEME_TITLE_DND:
801         _theme_prep_fgnd("titlebar.dnd", lookup_str, &bold);
802         break;
803     case THEME_TITLE_XA:
804         _theme_prep_fgnd("titlebar.xa", lookup_str, &bold);
805         break;
806     case THEME_STATUS_TEXT:
807         _theme_prep_fgnd("statusbar.text", lookup_str, &bold);
808         break;
809     case THEME_STATUS_BRACKET:
810         _theme_prep_fgnd("statusbar.brackets", lookup_str, &bold);
811         break;
812     case THEME_STATUS_ACTIVE:
813         _theme_prep_fgnd("statusbar.active", lookup_str, &bold);
814         break;
815     case THEME_STATUS_CURRENT:
816         _theme_prep_fgnd("statusbar.current", lookup_str, &bold);
817         break;
818     case THEME_STATUS_NEW:
819         _theme_prep_fgnd("statusbar.new", lookup_str, &bold);
820         break;
821     case THEME_STATUS_TIME:
822         _theme_prep_fgnd("statusbar.time", lookup_str, &bold);
823         break;
824     case THEME_ME:
825         _theme_prep_fgnd("me", lookup_str, &bold);
826         break;
827     case THEME_THEM:
828         _theme_prep_fgnd("them", lookup_str, &bold);
829         break;
830     case THEME_RECEIPT_SENT:
831         _theme_prep_fgnd("receipt.sent", lookup_str, &bold);
832         break;
833     case THEME_ROOMINFO:
834         _theme_prep_fgnd("roominfo", lookup_str, &bold);
835         break;
836     case THEME_ROOMMENTION:
837         _theme_prep_fgnd("roommention", lookup_str, &bold);
838         break;
839     case THEME_ROOMMENTION_TERM:
840         _theme_prep_fgnd("roommention.term", lookup_str, &bold);
841         break;
842     case THEME_ROOMTRIGGER:
843         _theme_prep_fgnd("roomtrigger", lookup_str, &bold);
844         break;
845     case THEME_ROOMTRIGGER_TERM:
846         _theme_prep_fgnd("roomtrigger.term", lookup_str, &bold);
847         break;
848     case THEME_ONLINE:
849         _theme_prep_fgnd("online", lookup_str, &bold);
850         break;
851     case THEME_OFFLINE:
852         _theme_prep_fgnd("offline", lookup_str, &bold);
853         break;
854     case THEME_AWAY:
855         _theme_prep_fgnd("away", lookup_str, &bold);
856         break;
857     case THEME_CHAT:
858         _theme_prep_fgnd("chat", lookup_str, &bold);
859         break;
860     case THEME_DND:
861         _theme_prep_fgnd("dnd", lookup_str, &bold);
862         break;
863     case THEME_XA:
864         _theme_prep_fgnd("xa", lookup_str, &bold);
865         break;
866     case THEME_TYPING:
867         _theme_prep_fgnd("typing", lookup_str, &bold);
868         break;
869     case THEME_GONE:
870         _theme_prep_fgnd("gone", lookup_str, &bold);
871         break;
872     case THEME_SUBSCRIBED:
873         _theme_prep_fgnd("subscribed", lookup_str, &bold);
874         break;
875     case THEME_UNSUBSCRIBED:
876         _theme_prep_fgnd("unsubscribed", lookup_str, &bold);
877         break;
878     case THEME_OTR_STARTED_TRUSTED:
879         _theme_prep_fgnd("otr.started.trusted", lookup_str, &bold);
880         break;
881     case THEME_OTR_STARTED_UNTRUSTED:
882         _theme_prep_fgnd("otr.started.untrusted", lookup_str, &bold);
883         break;
884     case THEME_OTR_ENDED:
885         _theme_prep_fgnd("otr.ended", lookup_str, &bold);
886         break;
887     case THEME_OTR_TRUSTED:
888         _theme_prep_fgnd("otr.trusted", lookup_str, &bold);
889         break;
890     case THEME_OTR_UNTRUSTED:
891         _theme_prep_fgnd("otr.untrusted", lookup_str, &bold);
892         break;
893     case THEME_ROSTER_HEADER:
894         _theme_prep_fgnd("roster.header", lookup_str, &bold);
895         break;
896     case THEME_ROSTER_ONLINE:
897         _theme_prep_fgnd("roster.online", lookup_str, &bold);
898         break;
899     case THEME_ROSTER_OFFLINE:
900         _theme_prep_fgnd("roster.offline", lookup_str, &bold);
901         break;
902     case THEME_ROSTER_CHAT:
903         _theme_prep_fgnd("roster.chat", lookup_str, &bold);
904         break;
905     case THEME_ROSTER_AWAY:
906         _theme_prep_fgnd("roster.away", lookup_str, &bold);
907         break;
908     case THEME_ROSTER_DND:
909         _theme_prep_fgnd("roster.dnd", lookup_str, &bold);
910         break;
911     case THEME_ROSTER_XA:
912         _theme_prep_fgnd("roster.xa", lookup_str, &bold);
913         break;
914     case THEME_ROSTER_ONLINE_ACTIVE:
915         _theme_prep_fgnd("roster.online.active", lookup_str, &bold);
916         break;
917     case THEME_ROSTER_OFFLINE_ACTIVE:
918         _theme_prep_fgnd("roster.offline.active", lookup_str, &bold);
919         break;
920     case THEME_ROSTER_CHAT_ACTIVE:
921         _theme_prep_fgnd("roster.chat.active", lookup_str, &bold);
922         break;
923     case THEME_ROSTER_AWAY_ACTIVE:
924         _theme_prep_fgnd("roster.away.active", lookup_str, &bold);
925         break;
926     case THEME_ROSTER_DND_ACTIVE:
927         _theme_prep_fgnd("roster.dnd.active", lookup_str, &bold);
928         break;
929     case THEME_ROSTER_XA_ACTIVE:
930         _theme_prep_fgnd("roster.xa.active", lookup_str, &bold);
931         break;
932     case THEME_ROSTER_ONLINE_UNREAD:
933         _theme_prep_fgnd("roster.online.unread", lookup_str, &bold);
934         break;
935     case THEME_ROSTER_OFFLINE_UNREAD:
936         _theme_prep_fgnd("roster.offline.unread", lookup_str, &bold);
937         break;
938     case THEME_ROSTER_CHAT_UNREAD:
939         _theme_prep_fgnd("roster.chat.unread", lookup_str, &bold);
940         break;
941     case THEME_ROSTER_AWAY_UNREAD:
942         _theme_prep_fgnd("roster.away.unread", lookup_str, &bold);
943         break;
944     case THEME_ROSTER_DND_UNREAD:
945         _theme_prep_fgnd("roster.dnd.unread", lookup_str, &bold);
946         break;
947     case THEME_ROSTER_XA_UNREAD:
948         _theme_prep_fgnd("roster.xa.unread", lookup_str, &bold);
949         break;
950     case THEME_ROSTER_ROOM:
951         _theme_prep_fgnd("roster.room", lookup_str, &bold);
952         break;
953     case THEME_ROSTER_ROOM_UNREAD:
954         _theme_prep_fgnd("roster.room.unread", lookup_str, &bold);
955         break;
956     case THEME_ROSTER_ROOM_TRIGGER:
957         _theme_prep_fgnd("roster.room.trigger", lookup_str, &bold);
958         break;
959     case THEME_ROSTER_ROOM_MENTION:
960         _theme_prep_fgnd("roster.room.mention", lookup_str, &bold);
961         break;
962     case THEME_OCCUPANTS_HEADER:
963         _theme_prep_fgnd("occupants.header", lookup_str, &bold);
964         break;
965     case THEME_UNTRUSTED:
966         _theme_prep_fgnd("untrusted", lookup_str, &bold);
967         break;
968     case THEME_CMD_WINS_UNREAD:
969         _theme_prep_fgnd("cmd.wins.unread", lookup_str, &bold);
970         break;
971     case THEME_WHITE:
972         g_string_append(lookup_str, "white");
973         bold = FALSE;
974         break;
975     case THEME_WHITE_BOLD:
976         g_string_append(lookup_str, "white");
977         bold = TRUE;
978         break;
979     case THEME_GREEN:
980         g_string_append(lookup_str, "green");
981         bold = FALSE;
982         break;
983     case THEME_GREEN_BOLD:
984         g_string_append(lookup_str, "green");
985         bold = TRUE;
986         break;
987     case THEME_RED:
988         g_string_append(lookup_str, "red");
989         bold = FALSE;
990         break;
991     case THEME_RED_BOLD:
992         g_string_append(lookup_str, "red");
993         bold = TRUE;
994         break;
995     case THEME_YELLOW:
996         g_string_append(lookup_str, "yellow");
997         bold = FALSE;
998         break;
999     case THEME_YELLOW_BOLD:
1000         g_string_append(lookup_str, "yellow");
1001         bold = TRUE;
1002         break;
1003     case THEME_BLUE:
1004         g_string_append(lookup_str, "blue");
1005         bold = FALSE;
1006         break;
1007     case THEME_BLUE_BOLD:
1008         g_string_append(lookup_str, "blue");
1009         bold = TRUE;
1010         break;
1011     case THEME_CYAN:
1012         g_string_append(lookup_str, "cyan");
1013         bold = FALSE;
1014         break;
1015     case THEME_CYAN_BOLD:
1016         g_string_append(lookup_str, "cyan");
1017         bold = TRUE;
1018         break;
1019     case THEME_BLACK:
1020         g_string_append(lookup_str, "black");
1021         bold = FALSE;
1022         break;
1023     case THEME_BLACK_BOLD:
1024         g_string_append(lookup_str, "black");
1025         bold = TRUE;
1026         break;
1027     case THEME_MAGENTA:
1028         g_string_append(lookup_str, "magenta");
1029         bold = FALSE;
1030         break;
1031     case THEME_MAGENTA_BOLD:
1032         g_string_append(lookup_str, "magenta");
1033         bold = TRUE;
1034         break;
1035     default:
1036         g_string_append(lookup_str, "default");
1037         bold = FALSE;
1038         break;
1039     }
1040 
1041     g_string_append(lookup_str, "_");
1042 
1043     // append background str
1044     switch (attrs) {
1045     case THEME_TITLE_TEXT:
1046     case THEME_TITLE_BRACKET:
1047     case THEME_TITLE_SCROLLED:
1048     case THEME_TITLE_UNENCRYPTED:
1049     case THEME_TITLE_ENCRYPTED:
1050     case THEME_TITLE_UNTRUSTED:
1051     case THEME_TITLE_TRUSTED:
1052     case THEME_TITLE_ONLINE:
1053     case THEME_TITLE_OFFLINE:
1054     case THEME_TITLE_AWAY:
1055     case THEME_TITLE_CHAT:
1056     case THEME_TITLE_DND:
1057     case THEME_TITLE_XA:
1058         _theme_prep_bgnd("titlebar", "blue", lookup_str);
1059         break;
1060     case THEME_STATUS_TEXT:
1061     case THEME_STATUS_BRACKET:
1062     case THEME_STATUS_ACTIVE:
1063     case THEME_STATUS_CURRENT:
1064     case THEME_STATUS_NEW:
1065     case THEME_STATUS_TIME:
1066         _theme_prep_bgnd("statusbar", "blue", lookup_str);
1067         break;
1068     default:
1069         _theme_prep_bgnd("bkgnd", "default", lookup_str);
1070         break;
1071     }
1072 
1073     // lookup colour pair
1074     result = color_pair_cache_get(lookup_str->str);
1075     if (result < 0) {
1076         log_error("Unable to load colour theme");
1077         result = 0;
1078     }
1079 
1080     g_string_free(lookup_str, TRUE);
1081 
1082     if (bold) {
1083         return COLOR_PAIR(result) | A_BOLD;
1084     } else {
1085         return COLOR_PAIR(result);
1086     }
1087 }
1088