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