pidgin/pidgin

Parents c6789ca79811
Children 38d540a251b0
Greatly simplify the nick color generation and properly detect dark themes.

Instead of randomly adjusting colors based on some seed colors we now hash a user's name and use the first 2 bytes of the digest to determine the hue to color their nick. We then adjust it according to the background color.

This isn't perfect as you can see in the screen shot, but there are some tweaks we can do later to account for that. I skipped them for now as this gets us 90% of the way there with like 50% of the work.

Testing Done:
Compiled and ran locally with Adwaita, Adwaita-Dark, and Arc-Dark themes.

Bugs closed: PIDGIN-17463

Reviewed at https://reviews.imfreedom.org/r/423/
--- a/pidgin/gtkblist.c Fri Jan 15 01:17:42 2021 -0600
+++ b/pidgin/gtkblist.c Mon Jan 18 00:05:05 2021 -0600
@@ -3904,7 +3904,7 @@
theme = pidgin_blist_get_theme();
name_color = NULL;
- dim_grey = pidgin_style_context_is_dark(NULL) ? "light slate grey" : "dim grey";
+ dim_grey = pidgin_style_context_is_dark() ? "light slate grey" : "dim grey";
if (theme) {
if (purple_presence_is_idle(presence)) {
@@ -5909,7 +5909,7 @@
textcolor = pidgin_theme_font_get_color_describe(pair);
else
/* If no theme them default to making idle buddy names grey */
- textcolor = pidgin_style_context_is_dark(NULL) ? "light slate grey" : "dim grey";
+ textcolor = pidgin_style_context_is_dark() ? "light slate grey" : "dim grey";
if (textcolor) {
idle = g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
--- a/pidgin/gtkconv.c Fri Jan 15 01:17:42 2021 -0600
+++ b/pidgin/gtkconv.c Mon Jan 18 00:05:05 2021 -0600
@@ -63,8 +63,6 @@
#include "pidgintooltip.h"
#include "pidginwindow.h"
-#include "gtknickcolors.h"
-
#define GTK_TOOLTIPS_VAR gtkconv->tooltips
#define ADD_MESSAGE_HISTORY_AT_ONCE 100
@@ -135,11 +133,6 @@
#define BUDDYICON_SIZE_MIN 32
#define BUDDYICON_SIZE_MAX 96
-#define MIN_LUMINANCE_CONTRAST_RATIO 4.5
-
-#define NICK_COLOR_GENERATE_COUNT 220
-static GArray *generated_nick_colors = NULL;
-
/* These probably won't conflict with any WebKit values. */
#define PIDGIN_DRAG_BLIST_NODE (1337)
#define PIDGIN_DRAG_IM_CONTACT (31337)
@@ -172,9 +165,6 @@
static void update_typing_icon(PidginConversation *gtkconv);
static void update_typing_message(PidginConversation *gtkconv, const char *message);
gboolean pidgin_conv_has_focus(PurpleConversation *conv);
-static GArray* generate_nick_colors(guint numcolors, GdkRGBA background);
-gdouble luminance(GdkRGBA color);
-static gboolean color_is_visible(GdkRGBA foreground, GdkRGBA background, gdouble min_contrast_ratio);
static GtkTextTag *get_buddy_tag(PurpleChatConversation *chat, const char *who, PurpleMessageFlags flag, gboolean create);
static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields);
static void focus_out_from_menubar(GtkWidget *wid, PidginConvWindow *win);
@@ -186,21 +176,52 @@
int width, int height);
static gboolean pidgin_conv_xy_to_right_infopane(PidginConvWindow *win, int x, int y);
-static const GdkRGBA *
-get_nick_color(PidginConversation *gtkconv, const gchar *name)
-{
- static GdkRGBA col;
-
- if (name == NULL) {
- col.red = col.green = col.blue = 0;
- col.alpha = 1;
- return &col;
- }
-
- col = g_array_index(gtkconv->nick_colors, GdkRGBA,
- g_str_hash(name) % gtkconv->nick_colors->len);
-
- return &col;
+/*<private>
+ * get_nick_color:
+ * @name: The name or text to get a color for.
+ * @color: (out): The return address for a #GdkRGBA that will recieve the
+ * color.
+ *
+ * This function is based heavily on the implementation that gajim uses from
+ * python-nbxmpp in nbxmpp.util.text_to_color. However, we don't have an
+ * implementation of HSL let alone HSLuv, so we're using HSV which is why
+ * the value is 1.0 instead of a luminance of 0.5.
+ *
+ * Currently there is no caching as GCache is deprecated and writing a fast LRU
+ * in glib is going to take a bit of finesse. Also we'll need to figure out how
+ * to scale to ginormous Twitch channels which will constantly break the cache.
+ */
+static void
+get_nick_color(const gchar *name, GdkRGBA *color) {
+ GdkRGBA background;
+ GChecksum *checksum = NULL;
+ guchar digest[20];
+ gsize digest_len = sizeof(digest);
+ gdouble hue = 0, red = 0, green = 0, blue = 0;
+
+ pidgin_style_context_get_background_color(&background);
+
+ /* hash the string and get the first 2 bytes of the digest */
+ checksum = g_checksum_new(G_CHECKSUM_SHA1);
+ g_checksum_update(checksum, (const guchar *)name, -1);
+ g_checksum_get_digest(checksum, digest, &digest_len);
+ g_checksum_free(checksum);
+
+ /* Calculate the hue based on the digest. We need a value between 0 and 1
+ * so we divide the value by 65535 which is the maximum value for 2 bytes.
+ */
+ hue = (digest[0] << 8 | digest[1]) / 65535.0;
+
+ /* Get the rgb values for the hue at full saturation and value. */
+ gtk_hsv_to_rgb(hue, 1.0, 1.0, &red, &green, &blue);
+
+ /* Finally calculate the color summing 20% of the inverted background color
+ * with 80% of the color.
+ */
+ color->red = (0.2 * (1 - background.red)) + (0.8 * red);
+ color->green = (0.2 * (1 - background.green)) + (0.8 * green);
+ color->blue = (0.2 * (1 - background.blue)) + (0.8 * blue);
+ color->alpha = 1.0;
}
static PurpleBlistNode *
@@ -3310,7 +3331,7 @@
const gchar *name, *alias;
gchar *tmp, *alias_key;
PurpleChatUserFlags flags;
- GdkRGBA *color = NULL;
+ GdkRGBA color;
alias = purple_chat_user_get_alias(cb);
name = purple_chat_user_get_name(cb);
@@ -3352,7 +3373,8 @@
g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_NORMAL, NULL);
if ((tag = get_buddy_tag(chat, name, PURPLE_MESSAGE_NICK, FALSE)))
g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_NORMAL, NULL);
- color = (GdkRGBA*)get_nick_color(gtkconv, name);
+
+ get_nick_color(name, &color);
}
gtk_list_store_insert_with_values(ls, &iter,
@@ -3369,7 +3391,7 @@
CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
CHAT_USERS_NAME_COLUMN, name,
CHAT_USERS_FLAGS_COLUMN, flags,
- CHAT_USERS_COLOR_COLUMN, color,
+ CHAT_USERS_COLOR_COLUMN, &color,
CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
-1);
@@ -3379,10 +3401,6 @@
(GDestroyNotify)gtk_tree_row_reference_free);
gtk_tree_path_free(newpath);
-#if 0
- if (is_me && color)
- gdk_rgba_free(color);
-#endif
g_free(alias_key);
}
@@ -4156,19 +4174,6 @@
pidgin_conv_window_add_gtkconv(hidden_convwin, gtkconv);
else
pidgin_conv_placement_place(gtkconv);
-
- if (generated_nick_colors == NULL) {
- GdkColor color;
- GdkRGBA rgba;
- color = gtk_widget_get_style(gtkconv->history)->base[GTK_STATE_NORMAL];
- rgba.red = color.red / 65535.0;
- rgba.green = color.green / 65535.0;
- rgba.blue = color.blue / 65535.0;
- rgba.alpha = 1.0;
- generated_nick_colors = generate_nick_colors(NICK_COLOR_GENERATE_COUNT, rgba);
- }
-
- gtkconv->nick_colors = g_array_ref(generated_nick_colors);
}
static void
@@ -4275,8 +4280,6 @@
g_source_remove(gtkconv->attach_timer);
}
- g_array_unref(gtkconv->nick_colors);
-
g_free(gtkconv);
}
@@ -7793,51 +7796,6 @@
return (gtkconv->win == hidden_convwin);
}
-
-gdouble luminance(GdkRGBA color)
-{
- gdouble r, g, b;
- gdouble rr, gg, bb;
- gdouble cutoff = 0.03928, scale = 12.92;
- gdouble a = 0.055, d = 1.055, p = 2.2;
-
- rr = color.red;
- gg = color.green;
- bb = color.blue;
-
- r = (rr > cutoff) ? pow((rr+a)/d, p) : rr/scale;
- g = (gg > cutoff) ? pow((gg+a)/d, p) : gg/scale;
- b = (bb > cutoff) ? pow((bb+a)/d, p) : bb/scale;
-
- return (r*0.2126 + g*0.7152 + b*0.0722);
-}
-
-/* Algorithm from https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml */
-static gboolean
-color_is_visible(GdkRGBA foreground, GdkRGBA background, gdouble min_contrast_ratio)
-{
- gdouble lfg, lbg, lmin, lmax;
- gdouble luminosity_ratio;
- gdouble nr, dr;
-
- lfg = luminance(foreground);
- lbg = luminance(background);
-
- if (lfg > lbg)
- lmax = lfg, lmin = lbg;
- else
- lmax = lbg, lmin = lfg;
-
- nr = lmax + 0.05, dr = lmin - 0.05;
- if (dr < 0.005 && dr > -0.005)
- dr += 0.01;
-
- luminosity_ratio = nr/dr;
- if ( luminosity_ratio < 0)
- luminosity_ratio *= -1.0;
- return (luminosity_ratio > min_contrast_ratio);
-}
-
void
pidgin_conv_placement_place(PidginConversation *conv) {
PidginConvWindow *win;
@@ -7857,71 +7815,3 @@
pidgin_conv_window_add_gtkconv(win, conv);
}
}
-
-static GArray*
-generate_nick_colors(guint numcolors, GdkRGBA background)
-{
- guint i = 0, j = 0;
- GArray *colors = g_array_new(FALSE, FALSE, sizeof(GdkRGBA));
- GdkRGBA nick_highlight;
- GdkRGBA send_color;
- time_t breakout_time;
-
- gdk_rgba_parse(&nick_highlight, DEFAULT_HIGHLIGHT_COLOR);
- gdk_rgba_parse(&send_color, DEFAULT_SEND_COLOR);
-
- pidgin_style_context_adjust_contrast(NULL, &nick_highlight);
- pidgin_style_context_adjust_contrast(NULL, &send_color);
-
- srand(background.red * 65535 + background.green * 65535 + background.blue * 65535 + 1);
-
- breakout_time = time(NULL) + 3;
-
- /* first we look through the list of "good" colors: colors that differ from every other color in the
- * list. only some of them will differ from the background color though. lets see if we can find
- * numcolors of them that do
- */
- while (i < numcolors && j < PIDGIN_NUM_NICK_SEED_COLORS && time(NULL) < breakout_time)
- {
- GdkRGBA color = nick_seed_colors[j];
-
- if (color_is_visible(color, background, MIN_LUMINANCE_CONTRAST_RATIO) &&
- color_is_visible(color, nick_highlight, MIN_LUMINANCE_CONTRAST_RATIO) &&
- color_is_visible(color, send_color, MIN_LUMINANCE_CONTRAST_RATIO))
- {
- g_array_append_val(colors, color);
- i++;
- }
- j++;
- }
-
- /* we might not have found numcolors in the last loop. if we did, we'll never enter this one.
- * if we did not, lets just find some colors that don't conflict with the background. its
- * expensive to find colors that not only don't conflict with the background, but also do not
- * conflict with each other.
- */
- while(i < numcolors && time(NULL) < breakout_time)
- {
- GdkRGBA color = {g_random_double_range(0, 1), g_random_double_range(0, 1), g_random_double_range(0, 1), 1};
-
- if (color_is_visible(color, background, MIN_LUMINANCE_CONTRAST_RATIO) &&
- color_is_visible(color, nick_highlight, MIN_LUMINANCE_CONTRAST_RATIO) &&
- color_is_visible(color, send_color, MIN_LUMINANCE_CONTRAST_RATIO))
- {
- g_array_append_val(colors, color);
- i++;
- }
- }
-
- if (i < numcolors) {
- purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i);
- }
-
- if( i == 0 ) {
- /* To remove errors caused by an empty array. */
- GdkRGBA color = {0.5, 0.5, 0.5, 1.0};
- g_array_append_val(colors, color);
- }
-
- return colors;
-}
--- a/pidgin/gtkconv.h Fri Jan 15 01:17:42 2021 -0600
+++ b/pidgin/gtkconv.h Mon Jan 18 00:05:05 2021 -0600
@@ -100,7 +100,6 @@
GtkWidget *tabby;
GtkWidget *menu_tabby;
- GArray *nick_colors;
PurpleMessageFlags last_flags;
GtkWidget *history_sw;
GtkWidget *history;
--- a/pidgin/gtknickcolors.h Fri Jan 15 01:17:42 2021 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/* pidgin
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program 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 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
- */
-
-#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
-# error "only <pidgin.h> may be included directly"
-#endif
-
-#ifndef _PIDGINNICKCOLORS_H_
-#define _PIDGINNICKCOLORS_H_
-/**
- * SECTION:gtknickcolors
- * @section_id: pidgin-gtknickcolors
- * @short_description: <filename>gtknickcolors.h</filename>
- * @title: Conversation Nick Colors
- */
-
-static const GdkRGBA nick_seed_colors[] = {
- {64764/65535.0, 59881/65535.0, 20303/65535.0, 1}, /* Butter #1 */
- {60909/65535.0, 54484/65535.0, 0/65535.0, 1}, /* Butter #2 */
- {50372/65535.0, 41120/65535.0, 0/65535.0, 1}, /* Butter #3 */
- {64764/65535.0, 44975/65535.0, 15934/65535.0, 1}, /* Orange #1 */
- {62965/65535.0, 31097/65535.0, 0/65535.0, 1}, /* Orange #2 */
- {52942/65535.0, 23644/65535.0, 0/65535.0, 1}, /* Orange #3 */
- {59811/65535.0, 47545/65535.0, 28270/65535.0, 1}, /* Chocolate #1 */
- {49601/65535.0, 32125/65535.0, 4369/65535.0, 1}, /* Chocolate #2 */
- {36751/65535.0, 22873/65535.0, 514/65535.0, 1}, /* Chocolate #3 */
- {35466/65535.0, 58082/65535.0, 13364/65535.0, 1}, /* Chameleon #1 */
- {29555/65535.0, 53970/65535.0, 5654/65535.0, 1}, /* Chameleon #2 */
- {20046/65535.0, 39578/65535.0, 1542/65535.0, 1}, /* Chameleon #3 */
- {29289/65535.0, 40863/65535.0, 53199/65535.0, 1}, /* Sky Blue #1 */
- {13364/65535.0, 25957/65535.0, 42148/65535.0, 1}, /* Sky Blue #2 */
- { 8224/65535.0, 19018/65535.0, 34695/65535.0, 1}, /* Sky Blue #3 */
- {44461/65535.0, 32639/65535.0, 43167/65535.0, 1}, /* Plum #1 */
- {30069/65535.0, 20560/65535.0, 31611/65535.0, 1}, /* Plum #2 */
- {23644/65535.0, 13621/65535.0, 26214/65535.0, 1}, /* Plum #3 */
- {61423/65535.0, 10537/65535.0, 10537/65535.0, 1}, /* Scarlet Red #1 */
- {52428/65535.0, 0/65535.0, 0/65535.0, 1}, /* Scarlet Red #2 */
- {42148/65535.0, 0/65535.0, 0/65535.0, 1}, /* Scarlet Red #3 */
- {34952/65535.0, 35466/65535.0, 34181/65535.0, 1}, /* Aluminium #4*/
- {21845/65535.0, 22359/65535.0, 21331/65535.0, 1}, /* Aluminium #5*/
- {11822/65535.0, 13364/65535.0, 13878/65535.0, 1} /* Aluminium #6*/
-};
-
-#define PIDGIN_NUM_NICK_SEED_COLORS (sizeof(nick_seed_colors) / sizeof(nick_seed_colors[0]))
-
-#endif
--- a/pidgin/meson.build Fri Jan 15 01:17:42 2021 -0600
+++ b/pidgin/meson.build Mon Jan 18 00:05:05 2021 -0600
@@ -80,7 +80,6 @@
'gtkicon-theme-loader.h',
'gtkidle.h',
'gtkmedia.h',
- 'gtknickcolors.h',
'gtknotify.h',
'gtkpluginpref.h',
'gtkprefs.h',
--- a/pidgin/pidginstylecontext.c Fri Jan 15 01:17:42 2021 -0600
+++ b/pidgin/pidginstylecontext.c Mon Jan 18 00:05:05 2021 -0600
@@ -29,48 +29,53 @@
/******************************************************************************
* Public API
*****************************************************************************/
+void
+pidgin_style_context_get_background_color(GdkRGBA *color) {
+ /* This value will leak, we could put a shutdown function in but right now
+ * that seems like a bit much for a few bytes.
+ */
+ static GdkRGBA *background = NULL;
+
+ if(g_once_init_enter(&background)) {
+ GdkRGBA *bg = NULL;
+ GtkStyleContext *context = NULL;
+ GtkWidget *window = NULL;
+
+ /* We create a window to get its background color from its style
+ * context. This _is_ doable without creating a window, but you still
+ * need the window class and about four times as much code, so that's
+ * why we do it this way.
+ */
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ context = gtk_widget_get_style_context(window);
+
+ gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL,
+ GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &bg,
+ NULL);
+ g_object_unref(G_OBJECT(window));
+
+ g_once_init_leave(&background, bg);
+ }
+
+ *color = *background;
+}
+
gboolean
-pidgin_style_context_is_dark(GtkStyleContext *context) {
+pidgin_style_context_is_dark(void) {
GdkRGBA bg;
gdouble luminance = 0.0;
- if(context == NULL) {
- if(dark_mode_have_cache) {
- return dark_mode_cached_value;
- }
-
- context = gtk_style_context_new();
- } else {
- g_object_ref(G_OBJECT(context));
+ if(dark_mode_have_cache) {
+ return dark_mode_cached_value;
}
- gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL,
- GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &bg,
- NULL);
- g_object_unref(G_OBJECT(context));
+ pidgin_style_context_get_background_color(&bg);
- /* magic values are taken from https://en.wikipedia.org/wiki/Luma_(video)
- * Rec._601_luma_versus_Rec._709_luma_coefficients.
- */
- luminance = (0.299 * bg.red) + (0.587 * bg.green) + (0.114 * bg.blue);
+ /* 709 coefficients from https://en.wikipedia.org/wiki/Luma_(video) */
+ luminance = (0.2126 * bg.red) + (0.7152 * bg.green) + (0.0722 * bg.blue);
- dark_mode_cached_value = (luminance < 0x7FFF);
+ dark_mode_cached_value = (luminance < 0.5);
dark_mode_have_cache = TRUE;
return dark_mode_cached_value;
}
-
-void
-pidgin_style_context_adjust_contrast(GtkStyleContext *context, GdkRGBA *rgba) {
- if(pidgin_style_context_is_dark(context)) {
- gdouble h, s, v;
-
- gtk_rgb_to_hsv(rgba->red, rgba->green, rgba->blue, &h, &s, &v);
-
- v += 0.3;
- v = v > 1.0 ? 1.0 : v;
- s = 0.7;
-
- gtk_hsv_to_rgb(h, s, v, &rgba->red, &rgba->green, &rgba->blue);
- }
-}
--- a/pidgin/pidginstylecontext.h Fri Jan 15 01:17:42 2021 -0600
+++ b/pidgin/pidginstylecontext.h Mon Jan 18 00:05:05 2021 -0600
@@ -41,27 +41,27 @@
G_BEGIN_DECLS
/**
+ * pidgin_style_context_get_background_color:
+ * @color: (out): A return address of a #GdkRGBA for the background color.
+ *
+ * Gets the background color for #GtkWindow in the currently selected theme
+ * and sets @color to that value.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_style_context_get_background_color(GdkRGBA *color);
+
+/**
* pidgin_style_context_is_dark:
- * @context: The #GtkStyleContext to use, or %NULL to use a cached version.
*
* Gets whether or not dark mode is enabled.
*
* Returns: %TRUE if dark mode is enabled and foreground colours should be
* inverted.
+ *
+ * Since: 3.0.0
*/
-
-gboolean pidgin_style_context_is_dark(GtkStyleContext *context);
-
-/**
- * pidgin_style_context_adjust_contrast:
- * @context: The #GtkStyleContext in use.
- * @color: (inout): Color to be lightened. Transformed color will be written
- * here.
- *
- * Lighten a color if dark mode is enabled.
- */
-
-void pidgin_style_context_adjust_contrast(GtkStyleContext *context, GdkRGBA *color);
+gboolean pidgin_style_context_is_dark(void);
G_END_DECLS