From 269afa53b4167223e1b7ebd8b3b4e8ef8402d959 Mon Sep 17 00:00:00 2001 From: Aurelien Aptel Date: Fri, 23 Aug 2019 12:32:55 +0200 Subject: [PATCH] Add 256 colors support Themes can now use color names from the xterm color name list [1]. 1: https://jonasjacek.github.io/colors/ --- Makefile.am | 1 + src/config/color.c | 418 +++++++++++++++++++++++++++++++++++++++++++++ src/config/color.h | 12 ++ src/config/theme.c | 111 +----------- 4 files changed, 438 insertions(+), 104 deletions(-) create mode 100644 src/config/color.c create mode 100644 src/config/color.h diff --git a/Makefile.am b/Makefile.am index 76351f22..09bd5542 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,6 +48,7 @@ core_sources = \ src/config/account.c src/config/account.h \ src/config/preferences.c src/config/preferences.h \ src/config/theme.c src/config/theme.h \ + src/config/color.c src/config/color.h \ src/config/scripts.c src/config/scripts.h \ src/plugins/plugins.h src/plugins/plugins.c \ src/plugins/api.h src/plugins/api.c \ diff --git a/src/config/color.c b/src/config/color.c new file mode 100644 index 00000000..f09da244 --- /dev/null +++ b/src/config/color.c @@ -0,0 +1,418 @@ +/* + * color.c + * + * Copyright (C) 2019 Aurelien Aptel + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see . + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_NCURSESW_NCURSES_H +#include +#elif HAVE_NCURSES_H +#include +#endif + +#include "config/color.h" + +static +struct color_pair_cache +{ + struct { int16_t fg, bg; } *pairs; + int size; + int capacity; +} cache = {0}; + +/* + * xterm default 256 colors + * XXX: there are many duplicates... (eg blue3) + */ +const char *color_names[COLOR_NAME_SIZE] = { + [0] = "black", + [1] = "red", + [2] = "green", + [3] = "yellow", + [4] = "blue", + [5] = "magenta", + [6] = "cyan", + [7] = "white", + [8] = "lightblack", + [9] = "lightred", + [10] = "lightgreen", + [11] = "lightyellow", + [12] = "lightblue", + [13] = "lightmagenta", + [14] = "lightcyan", + [15] = "lightwhite", + [16] = "grey0", + [17] = "navyblue", + [18] = "darkblue", + [19] = "blue3", + [20] = "blue3", + [21] = "blue1", + [22] = "darkgreen", + [23] = "deepskyblue4", + [24] = "deepskyblue4", + [25] = "deepskyblue4", + [26] = "dodgerblue3", + [27] = "dodgerblue2", + [28] = "green4", + [29] = "springgreen4", + [30] = "turquoise4", + [31] = "deepskyblue3", + [32] = "deepskyblue3", + [33] = "dodgerblue1", + [34] = "green3", + [35] = "springgreen3", + [36] = "darkcyan", + [37] = "lightseagreen", + [38] = "deepskyblue2", + [39] = "deepskyblue1", + [40] = "green3", + [41] = "springgreen3", + [42] = "springgreen2", + [43] = "cyan3", + [44] = "darkturquoise", + [45] = "turquoise2", + [46] = "green1", + [47] = "springgreen2", + [48] = "springgreen1", + [49] = "mediumspringgreen", + [50] = "cyan2", + [51] = "cyan1", + [52] = "darkred", + [53] = "deeppink4", + [54] = "purple4", + [55] = "purple4", + [56] = "purple3", + [57] = "blueviolet", + [58] = "orange4", + [59] = "grey37", + [60] = "mediumpurple4", + [61] = "slateblue3", + [62] = "slateblue3", + [63] = "royalblue1", + [64] = "chartreuse4", + [65] = "darkseagreen4", + [66] = "paleturquoise4", + [67] = "steelblue", + [68] = "steelblue3", + [69] = "cornflowerblue", + [70] = "chartreuse3", + [71] = "darkseagreen4", + [72] = "cadetblue", + [73] = "cadetblue", + [74] = "skyblue3", + [75] = "steelblue1", + [76] = "chartreuse3", + [77] = "palegreen3", + [78] = "seagreen3", + [79] = "aquamarine3", + [80] = "mediumturquoise", + [81] = "steelblue1", + [82] = "chartreuse2", + [83] = "seagreen2", + [84] = "seagreen1", + [85] = "seagreen1", + [86] = "aquamarine1", + [87] = "darkslategray2", + [88] = "darkred", + [89] = "deeppink4", + [90] = "darkmagenta", + [91] = "darkmagenta", + [92] = "darkviolet", + [93] = "purple", + [94] = "orange4", + [95] = "lightpink4", + [96] = "plum4", + [97] = "mediumpurple3", + [98] = "mediumpurple3", + [99] = "slateblue1", + [100] = "yellow4", + [101] = "wheat4", + [102] = "grey53", + [103] = "lightslategrey", + [104] = "mediumpurple", + [105] = "lightslateblue", + [106] = "yellow4", + [107] = "darkolivegreen3", + [108] = "darkseagreen", + [109] = "lightskyblue3", + [110] = "lightskyblue3", + [111] = "skyblue2", + [112] = "chartreuse2", + [113] = "darkolivegreen3", + [114] = "palegreen3", + [115] = "darkseagreen3", + [116] = "darkslategray3", + [117] = "skyblue1", + [118] = "chartreuse1", + [119] = "lightgreen", + [120] = "lightgreen", + [121] = "palegreen1", + [122] = "aquamarine1", + [123] = "darkslategray1", + [124] = "red3", + [125] = "deeppink4", + [126] = "mediumvioletred", + [127] = "magenta3", + [128] = "darkviolet", + [129] = "purple", + [130] = "darkorange3", + [131] = "indianred", + [132] = "hotpink3", + [133] = "mediumorchid3", + [134] = "mediumorchid", + [135] = "mediumpurple2", + [136] = "darkgoldenrod", + [137] = "lightsalmon3", + [138] = "rosybrown", + [139] = "grey63", + [140] = "mediumpurple2", + [141] = "mediumpurple1", + [142] = "gold3", + [143] = "darkkhaki", + [144] = "navajowhite3", + [145] = "grey69", + [146] = "lightsteelblue3", + [147] = "lightsteelblue", + [148] = "yellow3", + [149] = "darkolivegreen3", + [150] = "darkseagreen3", + [151] = "darkseagreen2", + [152] = "lightcyan3", + [153] = "lightskyblue1", + [154] = "greenyellow", + [155] = "darkolivegreen2", + [156] = "palegreen1", + [157] = "darkseagreen2", + [158] = "darkseagreen1", + [159] = "paleturquoise1", + [160] = "red3", + [161] = "deeppink3", + [162] = "deeppink3", + [163] = "magenta3", + [164] = "magenta3", + [165] = "magenta2", + [166] = "darkorange3", + [167] = "indianred", + [168] = "hotpink3", + [169] = "hotpink2", + [170] = "orchid", + [171] = "mediumorchid1", + [172] = "orange3", + [173] = "lightsalmon3", + [174] = "lightpink3", + [175] = "pink3", + [176] = "plum3", + [177] = "violet", + [178] = "gold3", + [179] = "lightgoldenrod3", + [180] = "tan", + [181] = "mistyrose3", + [182] = "thistle3", + [183] = "plum2", + [184] = "yellow3", + [185] = "khaki3", + [186] = "lightgoldenrod2", + [187] = "lightyellow3", + [188] = "grey84", + [189] = "lightsteelblue1", + [190] = "yellow2", + [191] = "darkolivegreen1", + [192] = "darkolivegreen1", + [193] = "darkseagreen1", + [194] = "honeydew2", + [195] = "lightcyan1", + [196] = "red1", + [197] = "deeppink2", + [198] = "deeppink1", + [199] = "deeppink1", + [200] = "magenta2", + [201] = "magenta1", + [202] = "orangered1", + [203] = "indianred1", + [204] = "indianred1", + [205] = "hotpink", + [206] = "hotpink", + [207] = "mediumorchid1", + [208] = "darkorange", + [209] = "salmon1", + [210] = "lightcoral", + [211] = "palevioletred1", + [212] = "orchid2", + [213] = "orchid1", + [214] = "orange1", + [215] = "sandybrown", + [216] = "lightsalmon1", + [217] = "lightpink1", + [218] = "pink1", + [219] = "plum1", + [220] = "gold1", + [221] = "lightgoldenrod2", + [222] = "lightgoldenrod2", + [223] = "navajowhite1", + [224] = "mistyrose1", + [225] = "thistle1", + [226] = "yellow1", + [227] = "lightgoldenrod1", + [228] = "khaki1", + [229] = "wheat1", + [230] = "cornsilk1", + [231] = "grey100", + [232] = "grey3", + [233] = "grey7", + [234] = "grey11", + [235] = "grey15", + [236] = "grey19", + [237] = "grey23", + [238] = "grey27", + [239] = "grey30", + [240] = "grey35", + [241] = "grey39", + [242] = "grey42", + [243] = "grey46", + [244] = "grey50", + [245] = "grey54", + [246] = "grey58", + [247] = "grey62", + [248] = "grey66", + [249] = "grey70", + [250] = "grey74", + [251] = "grey78", + [252] = "grey82", + [253] = "grey85", + [254] = "grey89", + [255] = "grey93", +}; + +/* -1 is valid curses color */ +#define COL_ERR -2 + +static int find_col(const char *col_name, int n) +{ + int i; + char name[32] = {0}; + + /* + * make a null terminated version of col_name. we don't want to + * use strNcasecmp because we could end up matching blue3 with + * blue. + */ + + if (n >= sizeof(name)) { + /* truncate */ + g_warning("<%s,%d> bigger than %zu", col_name, n, sizeof(name)); + n = sizeof(name)-1; + } + memcpy(name, col_name, n); + + if (g_ascii_strcasecmp(name, "default") == 0) + return -1; + + for (i = 0; i < COLOR_NAME_SIZE; i++) + if (g_ascii_strcasecmp(name, color_names[i]) == 0) + return i; + + return COL_ERR; +} + +void color_pair_cache_reset(void) +{ + if (cache.pairs) { + free(cache.pairs); + memset(&cache, 0, sizeof(cache)); + } + + /* + * COLOR_PAIRS is actually not a macro and is thus not a + * compile-time constant + */ + cache.capacity = COLOR_PAIRS; + cache.pairs = g_malloc0(sizeof(*cache.pairs)*cache.capacity); + + /* default_default */ + cache.pairs[0].fg = -1; + cache.pairs[0].bg = -1; + cache.size = 1; +} + +/** + * color_pair_cache_get - parse color pair "fg_bg" and returns curses id + * + * if the pair doesn't exist it will allocate it in curses with init_pair + * if the pair exists it returns its id + */ +int color_pair_cache_get(const char *pair_name) +{ + int i; + const char *sep; + int fg, bg; + + sep = strchr(pair_name, '_'); + if (!sep) { + g_warning("color pair %s missing _", pair_name); + return -1; + } + + fg = find_col(pair_name, sep - pair_name); + bg = find_col(sep+1, strlen(sep)); + if (fg == COL_ERR || bg == COL_ERR) { + g_warning("bad color name %s", pair_name); + return -1; + } + + /* try to find pair in cache */ + for (i = 0; i < cache.size; i++) + if (fg == cache.pairs[i].fg && bg == cache.pairs[i].bg) + return i; + + /* otherwise cache new pair */ + + if (cache.size >= cache.capacity) { + g_warning("reached ncurses color pair cache of %d", COLOR_PAIRS); + return -1; + } + + i = cache.size; + cache.pairs[i].fg = fg; + cache.pairs[i].bg = bg; + /* (re-)define the new pair in curses */ + init_pair(i, fg, bg); + + cache.size++; + + return i; +} diff --git a/src/config/color.h b/src/config/color.h new file mode 100644 index 00000000..4075313f --- /dev/null +++ b/src/config/color.h @@ -0,0 +1,12 @@ +#ifndef _COLOR_H_ +#define _COLOR_H_ + +/* to access color names */ +#define COLOR_NAME_SIZE 256 +extern const char *color_names[]; + +/* to add or clear cache */ +int color_pair_cache_get(const char *pair_name); +void color_pair_cache_reset(void); + +#endif diff --git a/src/config/theme.c b/src/config/theme.c index 71f31842..a5fdf064 100644 --- a/src/config/theme.c +++ b/src/config/theme.c @@ -51,11 +51,11 @@ #include "config/files.h" #include "config/theme.h" #include "config/preferences.h" +#include "config/color.h" static GString *theme_loc; static GKeyFile *theme; static GHashTable *bold_items; -static GHashTable *str_to_pair; static GHashTable *defaults; struct colour_string_t { @@ -75,7 +75,6 @@ theme_init(const char *const theme_name) log_error("Theme initialisation failed"); } - str_to_pair = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); defaults = g_hash_table_new_full(g_str_hash, g_str_equal, free, free); g_hash_table_insert(defaults, strdup("main.text"), strdup("white")); @@ -247,117 +246,17 @@ theme_close(void) g_hash_table_destroy(bold_items); bold_items = NULL; } - if (str_to_pair) { - g_hash_table_destroy(str_to_pair); - str_to_pair = NULL; - } if (defaults) { g_hash_table_destroy(defaults); defaults = NULL; } } -static void -_theme_init_pair(short pair, short fgnd, short bgnd, char *pair_str) -{ - init_pair(pair, fgnd, bgnd); - g_hash_table_insert(str_to_pair, strdup(pair_str), GINT_TO_POINTER((int)pair)); -} - void theme_init_colours(void) { assume_default_colors(-1, -1); - g_hash_table_insert(str_to_pair, strdup("default_default"), 0); - - _theme_init_pair(1, -1, COLOR_BLACK, "default_black"); - _theme_init_pair(2, -1, COLOR_BLUE, "default_blue"); - _theme_init_pair(3, -1, COLOR_GREEN, "default_green"); - _theme_init_pair(4, -1, COLOR_RED, "default_red"); - _theme_init_pair(5, -1, COLOR_CYAN, "default_cyan"); - _theme_init_pair(6, -1, COLOR_MAGENTA, "default_magenta"); - _theme_init_pair(7, -1, COLOR_WHITE, "default_white"); - _theme_init_pair(8, -1, COLOR_YELLOW, "default_yellow"); - - _theme_init_pair(9, COLOR_BLACK, -1, "black_default"); - _theme_init_pair(10, COLOR_BLACK, COLOR_BLACK, "black_black"); - _theme_init_pair(11, COLOR_BLACK, COLOR_BLUE, "black_blue"); - _theme_init_pair(12, COLOR_BLACK, COLOR_GREEN, "black_green"); - _theme_init_pair(13, COLOR_BLACK, COLOR_RED, "black_red"); - _theme_init_pair(14, COLOR_BLACK, COLOR_CYAN, "black_cyan"); - _theme_init_pair(15, COLOR_BLACK, COLOR_MAGENTA, "black_magenta"); - _theme_init_pair(16, COLOR_BLACK, COLOR_WHITE, "black_white"); - _theme_init_pair(17, COLOR_BLACK, COLOR_YELLOW, "black_yellow"); - - _theme_init_pair(18, COLOR_BLUE, -1, "blue_default"); - _theme_init_pair(19, COLOR_BLUE, COLOR_BLACK, "blue_black"); - _theme_init_pair(20, COLOR_BLUE, COLOR_BLUE, "blue_blue"); - _theme_init_pair(21, COLOR_BLUE, COLOR_GREEN, "blue_green"); - _theme_init_pair(22, COLOR_BLUE, COLOR_RED, "blue_red"); - _theme_init_pair(23, COLOR_BLUE, COLOR_CYAN, "blue_cyan"); - _theme_init_pair(24, COLOR_BLUE, COLOR_MAGENTA, "blue_magenta"); - _theme_init_pair(25, COLOR_BLUE, COLOR_WHITE, "blue_white"); - _theme_init_pair(26, COLOR_BLUE, COLOR_YELLOW, "blue_yellow"); - - _theme_init_pair(27, COLOR_GREEN, -1, "green_default"); - _theme_init_pair(28, COLOR_GREEN, COLOR_BLACK, "green_black"); - _theme_init_pair(29, COLOR_GREEN, COLOR_BLUE, "green_blue"); - _theme_init_pair(30, COLOR_GREEN, COLOR_GREEN, "green_green"); - _theme_init_pair(31, COLOR_GREEN, COLOR_RED, "green_red"); - _theme_init_pair(32, COLOR_GREEN, COLOR_CYAN, "green_cyan"); - _theme_init_pair(33, COLOR_GREEN, COLOR_MAGENTA, "green_magenta"); - _theme_init_pair(34, COLOR_GREEN, COLOR_WHITE, "green_white"); - _theme_init_pair(35, COLOR_GREEN, COLOR_YELLOW, "green_yellow"); - - _theme_init_pair(36, COLOR_RED, -1, "red_default"); - _theme_init_pair(37, COLOR_RED, COLOR_BLACK, "red_black"); - _theme_init_pair(38, COLOR_RED, COLOR_BLUE, "red_blue"); - _theme_init_pair(39, COLOR_RED, COLOR_GREEN, "red_green"); - _theme_init_pair(40, COLOR_RED, COLOR_RED, "red_red"); - _theme_init_pair(41, COLOR_RED, COLOR_CYAN, "red_cyan"); - _theme_init_pair(42, COLOR_RED, COLOR_MAGENTA, "red_magenta"); - _theme_init_pair(43, COLOR_RED, COLOR_WHITE, "red_white"); - _theme_init_pair(44, COLOR_RED, COLOR_YELLOW, "red_yellow"); - - _theme_init_pair(45, COLOR_CYAN, -1, "cyan_default"); - _theme_init_pair(46, COLOR_CYAN, COLOR_BLACK, "cyan_black"); - _theme_init_pair(47, COLOR_CYAN, COLOR_BLUE, "cyan_blue"); - _theme_init_pair(48, COLOR_CYAN, COLOR_GREEN, "cyan_green"); - _theme_init_pair(49, COLOR_CYAN, COLOR_RED, "cyan_red"); - _theme_init_pair(50, COLOR_CYAN, COLOR_CYAN, "cyan_cyan"); - _theme_init_pair(51, COLOR_CYAN, COLOR_MAGENTA, "cyan_magenta"); - _theme_init_pair(52, COLOR_CYAN, COLOR_WHITE, "cyan_white"); - _theme_init_pair(53, COLOR_CYAN, COLOR_YELLOW, "cyan_yellow"); - - _theme_init_pair(54, COLOR_MAGENTA, -1, "magenta_default"); - _theme_init_pair(55, COLOR_MAGENTA, COLOR_BLACK, "magenta_black"); - _theme_init_pair(56, COLOR_MAGENTA, COLOR_BLUE, "magenta_blue"); - _theme_init_pair(57, COLOR_MAGENTA, COLOR_GREEN, "magenta_green"); - _theme_init_pair(58, COLOR_MAGENTA, COLOR_RED, "magenta_red"); - _theme_init_pair(59, COLOR_MAGENTA, COLOR_CYAN, "magenta_cyan"); - _theme_init_pair(60, COLOR_MAGENTA, COLOR_MAGENTA, "magenta_magenta"); - _theme_init_pair(61, COLOR_MAGENTA, COLOR_WHITE, "magenta_white"); - _theme_init_pair(62, COLOR_MAGENTA, COLOR_YELLOW, "magenta_yellow"); - - _theme_init_pair(63, COLOR_WHITE, -1, "white_default"); - _theme_init_pair(64, COLOR_WHITE, COLOR_BLACK, "white_black"); - _theme_init_pair(65, COLOR_WHITE, COLOR_BLUE, "white_blue"); - _theme_init_pair(66, COLOR_WHITE, COLOR_GREEN, "white_green"); - _theme_init_pair(67, COLOR_WHITE, COLOR_RED, "white_red"); - _theme_init_pair(68, COLOR_WHITE, COLOR_CYAN, "white_cyan"); - _theme_init_pair(69, COLOR_WHITE, COLOR_MAGENTA, "white_magenta"); - _theme_init_pair(70, COLOR_WHITE, COLOR_WHITE, "white_white"); - _theme_init_pair(71, COLOR_WHITE, COLOR_YELLOW, "white_yellow"); - - _theme_init_pair(72, COLOR_YELLOW, -1, "yellow_default"); - _theme_init_pair(73, COLOR_YELLOW, COLOR_BLACK, "yellow_black"); - _theme_init_pair(74, COLOR_YELLOW, COLOR_BLUE, "yellow_blue"); - _theme_init_pair(75, COLOR_YELLOW, COLOR_GREEN, "yellow_green"); - _theme_init_pair(76, COLOR_YELLOW, COLOR_RED, "yellow_red"); - _theme_init_pair(77, COLOR_YELLOW, COLOR_CYAN, "yellow_cyan"); - _theme_init_pair(78, COLOR_YELLOW, COLOR_MAGENTA, "yellow_magenta"); - _theme_init_pair(79, COLOR_YELLOW, COLOR_WHITE, "yellow_white"); - _theme_init_pair(80, COLOR_YELLOW, COLOR_YELLOW, "yellow_yellow"); + color_pair_cache_reset(); } static void @@ -906,7 +805,11 @@ theme_attrs(theme_item_t attrs) } // lookup colour pair - result = GPOINTER_TO_INT(g_hash_table_lookup(str_to_pair, lookup_str->str)); + result = color_pair_cache_get(lookup_str->str); + if (result < 0) { + g_warning("invalid color <%s>", lookup_str->str); + result = 0; + } g_string_free(lookup_str, TRUE); if (bold) { return COLOR_PAIR(result) | A_BOLD;