dark mode

This commit is contained in:
2026-01-11 12:02:58 -07:00
parent 6d276ab0c5
commit c1cbe14871
10 changed files with 193 additions and 0 deletions

View File

@@ -433,6 +433,7 @@ const struct prefs vars[] =
{"gui_tab_dialogs", P_OFFINT (hex_gui_tab_dialogs), TYPE_BOOL}, {"gui_tab_dialogs", P_OFFINT (hex_gui_tab_dialogs), TYPE_BOOL},
{"gui_tab_dots", P_OFFINT (hex_gui_tab_dots), TYPE_BOOL}, {"gui_tab_dots", P_OFFINT (hex_gui_tab_dots), TYPE_BOOL},
{"gui_tab_icons", P_OFFINT (hex_gui_tab_icons), TYPE_BOOL}, {"gui_tab_icons", P_OFFINT (hex_gui_tab_icons), TYPE_BOOL},
{"gui_dark_mode", P_OFFINT (hex_gui_dark_mode), TYPE_BOOL},
{"gui_tab_layout", P_OFFINT (hex_gui_tab_layout), TYPE_INT}, {"gui_tab_layout", P_OFFINT (hex_gui_tab_layout), TYPE_INT},
{"gui_tab_middleclose", P_OFFINT (hex_gui_tab_middleclose), TYPE_BOOL}, {"gui_tab_middleclose", P_OFFINT (hex_gui_tab_middleclose), TYPE_BOOL},
{"gui_tab_newtofront", P_OFFINT (hex_gui_tab_newtofront), TYPE_INT}, {"gui_tab_newtofront", P_OFFINT (hex_gui_tab_newtofront), TYPE_INT},

View File

@@ -131,6 +131,7 @@ struct zoitechatprefs
unsigned int hex_gui_tab_dialogs; unsigned int hex_gui_tab_dialogs;
unsigned int hex_gui_tab_dots; unsigned int hex_gui_tab_dots;
unsigned int hex_gui_tab_icons; unsigned int hex_gui_tab_icons;
unsigned int hex_gui_dark_mode;
unsigned int hex_gui_tab_scrollchans; unsigned int hex_gui_tab_scrollchans;
unsigned int hex_gui_tab_server; unsigned int hex_gui_tab_server;
unsigned int hex_gui_tab_sort; unsigned int hex_gui_tab_sort;

View File

@@ -182,6 +182,7 @@ cv_tree_init (chanview *cv)
((treeview *)cv)->tree = GTK_TREE_VIEW (view); ((treeview *)cv)->tree = GTK_TREE_VIEW (view);
((treeview *)cv)->scrollw = win; ((treeview *)cv)->scrollw = win;
chanview_apply_theme (cv);
gtk_widget_show (view); gtk_widget_show (view);
} }

View File

@@ -28,6 +28,7 @@
#include "maingui.h" #include "maingui.h"
#include "gtkutil.h" #include "gtkutil.h"
#include "chanview.h" #include "chanview.h"
#include "palette.h"
/* treeStore columns */ /* treeStore columns */
#define COL_NAME 0 /* (char *) */ #define COL_NAME 0 /* (char *) */
@@ -103,6 +104,37 @@ static int cv_find_number_of_chan (chanview *cv, chan *find_ch);
/* ==== ABSTRACT CHANVIEW ==== */ /* ==== ABSTRACT CHANVIEW ==== */
void
chanview_apply_theme (chanview *cv)
{
GtkWidget *w;
treeview *tv;
if (cv == NULL)
return;
/* Only the tree implementation has a GtkTreeView we can explicitly style. */
if (cv->func_init != cv_tree_init)
return;
tv = (treeview *) cv;
if (tv->tree == NULL)
return;
w = GTK_WIDGET (tv->tree);
if (prefs.hex_gui_dark_mode)
{
gtk_widget_modify_base (w, GTK_STATE_NORMAL, &colors[COL_BG]);
gtk_widget_modify_text (w, GTK_STATE_NORMAL, &colors[COL_FG]);
}
else
{
/* Revert back to theme defaults. */
gtk_widget_modify_base (w, GTK_STATE_NORMAL, NULL);
gtk_widget_modify_text (w, GTK_STATE_NORMAL, NULL);
}
}
static char * static char *
truncate_tab_name (char *name, int max) truncate_tab_name (char *name, int max)
{ {

View File

@@ -48,6 +48,13 @@ gboolean chan_remove (chan *ch, gboolean force);
gboolean chan_is_collapsed (chan *ch); gboolean chan_is_collapsed (chan *ch);
chan * chan_get_parent (chan *ch); chan * chan_get_parent (chan *ch);
/*
* Apply ZoiteChat's optional "dark mode" styling to the channel list.
*
* This is a no-op for implementations that don't have a tree view.
*/
void chanview_apply_theme (chanview *cv);
#define FOCUS_NEW_ALL 1 #define FOCUS_NEW_ALL 1
#define FOCUS_NEW_ONLY_ASKED 2 #define FOCUS_NEW_ONLY_ASKED 2
#define FOCUS_NEW_NONE 0 #define FOCUS_NEW_NONE 0

View File

@@ -317,6 +317,7 @@ void
fe_init (void) fe_init (void)
{ {
palette_load (); palette_load ();
palette_apply_dark_mode (prefs.hex_gui_dark_mode);
key_init (); key_init ();
pixmaps_init (); pixmaps_init ();

View File

@@ -2480,7 +2480,20 @@ mg_create_userlist (session_gui *gui, GtkWidget *box)
if (prefs.hex_gui_ulist_style) if (prefs.hex_gui_ulist_style)
{ {
gtk_widget_set_style (ulist, input_style); gtk_widget_set_style (ulist, input_style);
}
/*
* Keep the user list in sync with the chat palette.
*
* - When "Use the text box font and colors" is enabled, we already want the
* palette background.
* - When "Dark mode" is enabled, we also force the user list to use the
* palette colors so it doesn't stay blindingly white on GTK2 themes.
*/
if (prefs.hex_gui_ulist_style || prefs.hex_gui_dark_mode)
{
gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]); gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]);
gtk_widget_modify_text (ulist, GTK_STATE_NORMAL, &colors[COL_FG]);
} }
mg_create_meters (gui, vbox); mg_create_meters (gui, vbox);

View File

@@ -169,3 +169,86 @@ palette_save (void)
close (fh); close (fh);
} }
} }
static gboolean
palette_color_eq (const GdkColor *a, const GdkColor *b)
{
return a->red == b->red && a->green == b->green && a->blue == b->blue;
}
gboolean
palette_apply_dark_mode (gboolean enable)
{
/*
* Stock ZoiteChat defaults from this file (keep them in sync with the
* colors[] initializer above):
* - Foreground: 0x2512/0x29e8/0x2b85
* - Background: 0xfae0/0xfae0/0xf8c4
*/
static const GdkColor light_fg = {0, 0x2512, 0x29e8, 0x2b85};
static const GdkColor light_bg = {0, 0xfae0, 0xfae0, 0xf8c4};
/* Common legacy "defaults" seen in the wild (pure black/white). */
static const GdkColor legacy_light_fg = {0, 0x0000, 0x0000, 0x0000};
static const GdkColor legacy_light_bg = {0, 0xffff, 0xffff, 0xffff};
/* A readable "dark" preset (roughly #D4D4D4 on #1E1E1E). */
static const GdkColor dark_fg = {0, 0xd4d4, 0xd4d4, 0xd4d4};
static const GdkColor dark_bg = {0, 0x1e1e, 0x1e1e, 0x1e1e};
gboolean changed = FALSE;
if (enable)
{
if (palette_color_eq (&colors[COL_FG], &light_fg) ||
palette_color_eq (&colors[COL_FG], &legacy_light_fg))
{
colors[COL_FG].red = dark_fg.red;
colors[COL_FG].green = dark_fg.green;
colors[COL_FG].blue = dark_fg.blue;
changed = TRUE;
}
if (palette_color_eq (&colors[COL_BG], &light_bg) ||
palette_color_eq (&colors[COL_BG], &legacy_light_bg))
{
colors[COL_BG].red = dark_bg.red;
colors[COL_BG].green = dark_bg.green;
colors[COL_BG].blue = dark_bg.blue;
changed = TRUE;
}
}
else
{
if (palette_color_eq (&colors[COL_FG], &dark_fg))
{
/*
* Revert to a predictable light default. Most users who hit this toggle
* are coming from black-on-white palettes.
*/
colors[COL_FG].red = legacy_light_fg.red;
colors[COL_FG].green = legacy_light_fg.green;
colors[COL_FG].blue = legacy_light_fg.blue;
changed = TRUE;
}
if (palette_color_eq (&colors[COL_BG], &dark_bg))
{
colors[COL_BG].red = legacy_light_bg.red;
colors[COL_BG].green = legacy_light_bg.green;
colors[COL_BG].blue = legacy_light_bg.blue;
changed = TRUE;
}
}
/* Ensure the new colors have pixels allocated in the current colormap. */
if (changed)
{
GdkColormap *cmap = gdk_colormap_get_system ();
if (cmap)
{
gdk_colormap_alloc_color (cmap, &colors[COL_FG], TRUE, TRUE);
gdk_colormap_alloc_color (cmap, &colors[COL_BG], TRUE, TRUE);
}
}
return changed;
}

View File

@@ -38,4 +38,15 @@ void palette_alloc (GtkWidget * widget);
void palette_load (void); void palette_load (void);
void palette_save (void); void palette_save (void);
/*
* Apply ZoiteChat's built-in "dark mode" background/foreground overrides.
*
* This is intentionally conservative: it only adjusts the palette if the
* colors are still at ZoiteChat's stock defaults, so user-customized palettes
* continue to take precedence.
*
* Returns TRUE if any palette entries were changed.
*/
gboolean palette_apply_dark_mode (gboolean enable);
#endif #endif

View File

@@ -33,6 +33,7 @@
#include "fe-gtk.h" #include "fe-gtk.h"
#include "gtkutil.h" #include "gtkutil.h"
#include "maingui.h" #include "maingui.h"
#include "chanview.h"
#include "palette.h" #include "palette.h"
#include "pixmaps.h" #include "pixmaps.h"
#include "menu.h" #include "menu.h"
@@ -341,6 +342,17 @@ static const setting color_settings[] =
{ST_END, 0, 0, 0, 0, 0} {ST_END, 0, 0, 0, 0, 0}
}; };
static const setting dark_mode_setting =
{
ST_TOGGLE,
N_("Enable dark mode for chat views"),
P_OFFINTNL(hex_gui_dark_mode),
N_("Makes the chat buffer, channel list, and user list use a dark background/foreground.\n"
"This only changes the stock background/foreground colors. If you've customized them, your palette wins."),
0,
0
};
static const char *const dccaccept[] = static const char *const dccaccept[] =
{ {
N_("Ask for confirmation"), N_("Ask for confirmation"),
@@ -1570,6 +1582,7 @@ setup_create_color_page (void)
setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab); setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab);
setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab); setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab);
setup_create_other_colorR (_("Spell checker:"), COL_SPELL, 11, tab); setup_create_other_colorR (_("Spell checker:"), COL_SPELL, 11, tab);
setup_create_toggleL (tab, 13, &dark_mode_setting);
setup_create_header (tab, 15, N_("Color Stripping")); setup_create_header (tab, 15, N_("Color Stripping"));
@@ -2048,10 +2061,25 @@ static void
setup_apply_to_sess (session_gui *gui) setup_apply_to_sess (session_gui *gui)
{ {
mg_update_xtext (gui->xtext); mg_update_xtext (gui->xtext);
chanview_apply_theme ((chanview *) gui->chanview);
if (prefs.hex_gui_ulist_style) if (prefs.hex_gui_ulist_style)
gtk_widget_set_style (gui->user_tree, input_style); gtk_widget_set_style (gui->user_tree, input_style);
if (prefs.hex_gui_ulist_style || prefs.hex_gui_dark_mode)
{
gtk_widget_modify_base (gui->user_tree, GTK_STATE_NORMAL, &colors[COL_BG]);
if (prefs.hex_gui_dark_mode)
gtk_widget_modify_text (gui->user_tree, GTK_STATE_NORMAL, &colors[COL_FG]);
else
gtk_widget_modify_text (gui->user_tree, GTK_STATE_NORMAL, NULL);
}
else
{
gtk_widget_modify_base (gui->user_tree, GTK_STATE_NORMAL, NULL);
gtk_widget_modify_text (gui->user_tree, GTK_STATE_NORMAL, NULL);
}
if (prefs.hex_gui_input_style) if (prefs.hex_gui_input_style)
{ {
extern char cursor_color_rc[]; extern char cursor_color_rc[];
@@ -2168,6 +2196,7 @@ setup_apply (struct zoitechatprefs *pr)
int do_ulist = FALSE; int do_ulist = FALSE;
int do_layout = FALSE; int do_layout = FALSE;
int do_identd = FALSE; int do_identd = FALSE;
int old_dark_mode = prefs.hex_gui_dark_mode;
if (strcmp (pr->hex_text_background, prefs.hex_text_background) != 0) if (strcmp (pr->hex_text_background, prefs.hex_text_background) != 0)
new_pix = TRUE; new_pix = TRUE;
@@ -2238,6 +2267,20 @@ setup_apply (struct zoitechatprefs *pr)
memcpy (&prefs, pr, sizeof (prefs)); memcpy (&prefs, pr, sizeof (prefs));
/*
* "Dark mode" is mostly about the chat views. Be conservative: only adjust
* the stock Foreground/Background colors so user palettes keep winning.
*
* IMPORTANT: don't short-circuit this call.
* We MUST run palette_apply_dark_mode() when the toggle changes, otherwise
* the preference flips but the palette stays the same (aka: "nothing happens").
*/
{
gboolean pal_changed = palette_apply_dark_mode (prefs.hex_gui_dark_mode);
if (prefs.hex_gui_dark_mode != old_dark_mode || pal_changed)
color_change = TRUE;
}
#ifdef WIN32 #ifdef WIN32
/* merge hex_font_main and hex_font_alternative into hex_font_normal */ /* merge hex_font_main and hex_font_alternative into hex_font_normal */
old_desc = pango_font_description_from_string (prefs.hex_text_font_main); old_desc = pango_font_description_from_string (prefs.hex_text_font_main);